/** * 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.core.server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.URL; import java.net.URLConnection; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.activemq.artemis.logs.ActiveMQUtilLogger; import org.apache.activemq.artemis.utils.ActiveMQThreadFactory; import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet; import org.jboss.logging.Logger; /** * This will use {@link InetAddress#isReachable(int)} to determine if the network is alive. * It will have a set of addresses, and if any address is reached the network will be considered alive. */ public class NetworkHealthCheck extends ActiveMQScheduledComponent { private static final Logger logger = Logger.getLogger(NetworkHealthCheck.class); private final Set<ActiveMQComponent> componentList = new ConcurrentHashSet<>(); private final Set<InetAddress> addresses = new ConcurrentHashSet<>(); private final Set<URL> urls = new ConcurrentHashSet<>(); private NetworkInterface networkInterface; public static final String IPV6_DEFAULT_COMMAND = "ping6 -c 1 %2$s"; public static final String IPV4_DEFAULT_COMMAND = "ping -c 1 -t %d %s"; private String ipv4Command = IPV4_DEFAULT_COMMAND; private String ipv6Command = IPV6_DEFAULT_COMMAND; // To be used on tests. As we use the loopback as a valid address on tests. private boolean ignoreLoopback = false; /** * The timeout to be used on isReachable */ private int networkTimeout; public NetworkHealthCheck() { this(null, 1000, 1000); } public NetworkHealthCheck(String nicName, long checkPeriod, int networkTimeout) { super(null, null, checkPeriod, TimeUnit.MILLISECONDS, false); this.networkTimeout = networkTimeout; this.setNICName(nicName); } public NetworkHealthCheck setNICName(String nicName) { NetworkInterface netToUse; try { if (nicName != null) { netToUse = NetworkInterface.getByName(nicName); } else { netToUse = null; } } catch (Exception e) { logger.warn(e.getMessage(), e); netToUse = null; } this.networkInterface = netToUse; return this; } public boolean isIgnoreLoopback() { return ignoreLoopback; } public NetworkHealthCheck setIgnoreLoopback(boolean ignoreLoopback) { this.ignoreLoopback = ignoreLoopback; return this; } public Set<InetAddress> getAddresses() { return addresses; } public Set<URL> getUrls() { return urls; } public String getNICName() { if (networkInterface != null) { return networkInterface.getName(); } else { return null; } } public NetworkHealthCheck parseAddressList(String addressList) { if (addressList != null) { String[] addresses = addressList.split(","); for (String address : addresses) { if (!address.trim().isEmpty()) { try { this.addAddress(InetAddress.getByName(address.trim())); } catch (Exception e) { logger.warn(e.getMessage(), e); } } } } return this; } public NetworkHealthCheck parseURIList(String addressList) { if (addressList != null) { String[] addresses = addressList.split(","); for (String address : addresses) { if (!address.trim().isEmpty()) { try { this.addURL(new URL(address.trim())); } catch (Exception e) { logger.warn(e.getMessage(), e); } } } } return this; } @Override protected ActiveMQThreadFactory getThreadFactory() { return new ActiveMQThreadFactory("NetworkChecker", "Network-Checker-", false, getThisClassLoader()); } private ClassLoader getThisClassLoader() { return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { @Override public ClassLoader run() { return NetworkHealthCheck.this.getClass().getClassLoader(); } }); } public int getNetworkTimeout() { return networkTimeout; } @Override public NetworkHealthCheck setPeriod(long period) { super.setPeriod(period); return this; } @Override public NetworkHealthCheck setTimeUnit(TimeUnit timeUnit) { super.setTimeUnit(timeUnit); return this; } public NetworkHealthCheck setNetworkTimeout(int networkTimeout) { this.networkTimeout = networkTimeout; return this; } public NetworkHealthCheck addComponent(ActiveMQComponent component) { componentList.add(component); checkStart(); return this; } public NetworkHealthCheck clearComponents() { componentList.clear(); return this; } public NetworkHealthCheck addAddress(InetAddress address) { if (!check(address)) { logger.warn("Ping Address " + address + " wasn't reacheable"); } if (!ignoreLoopback && address.isLoopbackAddress()) { ActiveMQUtilLogger.LOGGER.addressloopback(address.toString()); } else { addresses.add(address); checkStart(); } return this; } public NetworkHealthCheck removeAddress(InetAddress address) { addresses.remove(address); return this; } public NetworkHealthCheck clearAddresses() { addresses.clear(); return this; } public NetworkHealthCheck addURL(URL url) { if (!check(url)) { logger.warn("Ping url " + url + " wasn't reacheable"); } urls.add(url); checkStart(); return this; } public NetworkHealthCheck removeURL(URL url) { urls.remove(url); return this; } public NetworkHealthCheck clearURL() { urls.clear(); return this; } public String getIpv4Command() { return ipv4Command; } public NetworkHealthCheck setIpv4Command(String ipv4Command) { this.ipv4Command = ipv4Command; return this; } public String getIpv6Command() { return ipv6Command; } public NetworkHealthCheck setIpv6Command(String ipv6Command) { this.ipv6Command = ipv6Command; return this; } private void checkStart() { if (!isStarted() && (!addresses.isEmpty() || !urls.isEmpty()) && !componentList.isEmpty()) { start(); } } @Override public void run() { boolean healthy = check(); if (healthy) { for (ActiveMQComponent component : componentList) { if (!component.isStarted()) { try { logger.info("Network is healthy, starting service " + component); component.start(); } catch (Exception e) { logger.warn("Error starting component " + component, e); } } } } else { for (ActiveMQComponent component : componentList) { if (component.isStarted()) { try { logger.info("Network is unhealthy, stopping service " + component); component.stop(); } catch (Exception e) { logger.warn("Error stopping component " + component, e); } } } } } /** * @return true if no checks were done or if one address/url responds; false if all addresses/urls fail */ public boolean check() { if (isEmpty()) { return true; } for (InetAddress address : addresses) { if (check(address)) { return true; } } for (URL url : urls) { if (check(url)) { return true; } } return false; } public boolean check(InetAddress address) { try { if (address.isReachable(networkInterface, 0, networkTimeout)) { if (logger.isTraceEnabled()) { logger.tracef(address + " OK"); } return true; } else { return purePing(address); } } catch (Exception e) { logger.warn(e.getMessage(), e); return false; } } public boolean purePing(InetAddress address) throws IOException, InterruptedException { long timeout = Math.max(1, TimeUnit.MILLISECONDS.toSeconds(networkTimeout)); // it did not work with a simple isReachable, it could be because there's no root access, so we will try ping executable if (logger.isTraceEnabled()) { logger.trace("purePing on canonical address " + address.getCanonicalHostName()); } ProcessBuilder processBuilder; if (address instanceof Inet6Address) { processBuilder = buildProcess(ipv6Command, timeout, address.getCanonicalHostName()); } else { processBuilder = buildProcess(ipv4Command, timeout, address.getCanonicalHostName()); } Process pingProcess = processBuilder.start(); readStream(pingProcess.getInputStream(), false); readStream(pingProcess.getErrorStream(), true); return pingProcess.waitFor() == 0; } private ProcessBuilder buildProcess(String expressionCommand, long timeout, String host) { String command = String.format(expressionCommand, timeout, host); if (logger.isDebugEnabled()) { logger.debug("executing ping:: " + command); } ProcessBuilder builder = new ProcessBuilder(command.split(" ")); return builder; } private void readStream(InputStream stream, boolean error) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); String inputLine; while ((inputLine = reader.readLine()) != null) { if (error) { logger.warn(inputLine); } else { logger.trace(inputLine); } } reader.close(); } public boolean check(URL url) { try { URLConnection connection = url.openConnection(); connection.setReadTimeout(networkTimeout); InputStream is = connection.getInputStream(); is.close(); return true; } catch (Exception e) { logger.warn(e.getMessage(), e); return false; } } public boolean isEmpty() { return addresses.isEmpty() && urls.isEmpty(); } }