| /* |
| * 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.ignite.internal.processors.cluster; |
| |
| import java.io.IOException; |
| import java.util.Comparator; |
| import java.util.concurrent.RejectedExecutionException; |
| import java.util.concurrent.atomic.AtomicReference; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.IgniteLogger; |
| import org.apache.ignite.internal.IgniteKernal; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.internal.util.worker.GridWorker; |
| import org.jetbrains.annotations.Nullable; |
| |
| /** |
| * This class is responsible for notification about new version availability. |
| * <p> |
| * Note also that this connectivity is not necessary to successfully start the system as it will |
| * gracefully ignore any errors occurred during notification and verification process. |
| */ |
| class GridUpdateNotifier { |
| /** Default encoding. */ |
| private static final String CHARSET = "UTF-8"; |
| |
| /** Version comparator. */ |
| private static final VersionComparator VER_COMPARATOR = new VersionComparator(); |
| |
| /** Throttling for logging out. */ |
| private static final long THROTTLE_PERIOD = 24 * 60 * 60 * 1000; // 1 day. |
| |
| /** Sleep milliseconds time for worker thread. */ |
| private static final int WORKER_THREAD_SLEEP_TIME = 5000; |
| |
| /** Default URL for request Ignite updates. */ |
| private final static String DEFAULT_IGNITE_UPDATES_URL = "https://ignite.apache.org/latest"; |
| |
| /** Default HTTP parameter for request Ignite updates. */ |
| private final static String DEFAULT_IGNITE_UPDATES_PARAMS = "?ver="; |
| |
| /** Grid version. */ |
| private final String ver; |
| |
| /** Error during obtaining data. */ |
| private volatile Exception err; |
| |
| /** Latest version. */ |
| private volatile String latestVer; |
| |
| /** Download url for latest version. */ |
| private volatile String downloadUrl; |
| |
| /** Ignite instance name. */ |
| private final String igniteInstanceName; |
| |
| /** Whether or not to report only new version. */ |
| private volatile boolean reportOnlyNew; |
| |
| /** */ |
| private long lastLog = -1; |
| |
| /** Command for worker thread. */ |
| private final AtomicReference<Runnable> cmd = new AtomicReference<>(); |
| |
| /** Worker thread to process http request. */ |
| private final Thread workerThread; |
| |
| /** Http client for getting Ignite updates */ |
| private final HttpIgniteUpdatesChecker updatesChecker; |
| |
| /** |
| * Creates new notifier with default values. |
| * |
| * @param igniteInstanceName igniteInstanceName |
| * @param ver Compound Ignite version. |
| * @param reportOnlyNew Whether or not to report only new version. |
| * @param updatesChecker Service for getting Ignite updates |
| * @throws IgniteCheckedException If failed. |
| */ |
| GridUpdateNotifier( |
| String igniteInstanceName, |
| String ver, |
| boolean reportOnlyNew, |
| HttpIgniteUpdatesChecker updatesChecker |
| ) throws IgniteCheckedException { |
| this.ver = ver; |
| this.igniteInstanceName = igniteInstanceName == null ? "null" : igniteInstanceName; |
| this.updatesChecker = updatesChecker; |
| this.reportOnlyNew = reportOnlyNew; |
| |
| workerThread = new Thread(new Runnable() { |
| @Override public void run() { |
| try { |
| while(!Thread.currentThread().isInterrupted()) { |
| Runnable cmd0 = cmd.getAndSet(null); |
| |
| if (cmd0 != null) |
| cmd0.run(); |
| else |
| Thread.sleep(WORKER_THREAD_SLEEP_TIME); |
| } |
| } |
| catch (InterruptedException ignore) { |
| // No-op. |
| } |
| } |
| }, "upd-ver-checker"); |
| |
| workerThread.setDaemon(true); |
| |
| workerThread.start(); |
| } |
| |
| /** |
| * Creates new notifier with default Ignite updates URL |
| */ |
| GridUpdateNotifier(String igniteInstanceName, String ver, boolean reportOnlyNew) throws IgniteCheckedException { |
| this(igniteInstanceName, ver, reportOnlyNew, |
| new HttpIgniteUpdatesChecker(DEFAULT_IGNITE_UPDATES_URL, DEFAULT_IGNITE_UPDATES_PARAMS + ver, CHARSET)); |
| } |
| |
| /** |
| * @param reportOnlyNew Whether or not to report only new version. |
| */ |
| void reportOnlyNew(boolean reportOnlyNew) { |
| this.reportOnlyNew = reportOnlyNew; |
| } |
| |
| /** |
| * @return Latest version. |
| */ |
| String latestVersion() { |
| return latestVer; |
| } |
| |
| /** |
| * @return Error. |
| */ |
| Exception error() { |
| return err; |
| } |
| |
| /** |
| * Starts asynchronous process for retrieving latest version data. |
| * |
| * @param log Logger. |
| * @param first First checking. |
| */ |
| void checkForNewVersion(IgniteLogger log, boolean first) { |
| assert log != null; |
| |
| log = log.getLogger(getClass()); |
| |
| try { |
| cmd.set(new UpdateChecker(log, first)); |
| } |
| catch (RejectedExecutionException e) { |
| U.error(log, "Failed to schedule a thread due to execution rejection (safely ignoring): " + |
| e.getMessage()); |
| } |
| } |
| |
| /** |
| * Logs out latest version notification if such was received and available. |
| * |
| * @param log Logger. |
| */ |
| void reportStatus(IgniteLogger log) { |
| assert log != null; |
| |
| log = log.getLogger(getClass()); |
| |
| String latestVer = this.latestVer; |
| String downloadUrl = this.downloadUrl; |
| |
| downloadUrl = downloadUrl != null ? downloadUrl : IgniteKernal.SITE; |
| |
| if (latestVer != null) { |
| int cmp = VER_COMPARATOR.compare(latestVer, ver); |
| |
| if (cmp == 0) { |
| if (!reportOnlyNew) |
| throttle(log, false, "Your version is up to date."); |
| } |
| else if (cmp > 0) |
| throttle(log, true, "New version is available at " + downloadUrl + ": " + latestVer); |
| } |
| else |
| if (!reportOnlyNew) |
| throttle(log, false, "Update status is not available."); |
| } |
| |
| /** |
| * |
| * @param log Logger to use. |
| * @param warn Whether or not this is a warning. |
| * @param msg Message to log. |
| */ |
| private void throttle(IgniteLogger log, boolean warn, String msg) { |
| assert(log != null); |
| assert(msg != null); |
| |
| long now = U.currentTimeMillis(); |
| |
| if (now - lastLog > THROTTLE_PERIOD) { |
| if (!warn) |
| U.log(log, msg); |
| else { |
| U.quiet(true, msg); |
| |
| if (log.isInfoEnabled()) |
| log.warning(msg); |
| } |
| |
| lastLog = now; |
| } |
| } |
| |
| /** |
| * Stops update notifier. |
| */ |
| public void stop() { |
| workerThread.interrupt(); |
| } |
| |
| /** |
| * Asynchronous checker of the latest version available. |
| */ |
| private class UpdateChecker extends GridWorker { |
| /** Logger. */ |
| private final IgniteLogger log; |
| |
| /** First. */ |
| private final boolean first; |
| |
| /** |
| * Creates checked with given logger. |
| * |
| * @param log Logger. |
| */ |
| UpdateChecker(IgniteLogger log, boolean first) { |
| super(igniteInstanceName, "grid-version-checker", log); |
| |
| this.log = log.getLogger(getClass()); |
| this.first = first; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override protected void body() throws InterruptedException { |
| try { |
| if (!isCancelled()) { |
| try { |
| String updatesRes = updatesChecker.getUpdates(first); |
| |
| String[] lines = updatesRes.split("\n"); |
| |
| for (String line : lines) { |
| if (line.contains("version")) |
| latestVer = obtainVersionFrom(line); |
| else if (line.contains("downloadUrl")) |
| downloadUrl = obtainDownloadUrlFrom(line); |
| } |
| |
| err = null; |
| } |
| catch (IOException e) { |
| err = e; |
| |
| if (log.isDebugEnabled()) |
| log.debug("Failed to connect to Ignite update server. " + e.getMessage()); |
| } |
| } |
| } |
| catch (Exception e) { |
| err = e; |
| |
| if (log.isDebugEnabled()) |
| log.debug("Unexpected exception in update checker. " + e.getMessage()); |
| } |
| } |
| |
| /** |
| * Gets the version from the current {@code node}, if one exists. |
| * |
| * @param line Line which contains value for extract. |
| * @param metaName Name for extract. |
| * @return Version or {@code null} if one's not found. |
| */ |
| @Nullable private String obtainMeta(String metaName, String line) { |
| assert line.contains(metaName); |
| |
| return line.substring(line.indexOf(metaName) + metaName.length()).trim(); |
| |
| } |
| |
| /** |
| * Gets the version from the current {@code node}, if one exists. |
| * |
| * @param line Line which contains value for extract. |
| * @return Version or {@code null} if one's not found. |
| */ |
| @Nullable private String obtainVersionFrom(String line) { |
| return obtainMeta("version=", line); |
| } |
| |
| /** |
| * Gets the download url from the current {@code node}, if one exists. |
| * |
| * @param line Which contains value for extract. |
| * @return download url or {@code null} if one's not found. |
| */ |
| @Nullable private String obtainDownloadUrlFrom(String line) { |
| return obtainMeta("downloadUrl=", line); |
| } |
| } |
| |
| /** |
| * Ignite version comparator. |
| */ |
| private static final class VersionComparator implements Comparator<String> { |
| /** Dot pattern. */ |
| private static final String DOT_PATTERN = "\\."; |
| |
| /** Dash pattern. */ |
| private static final String DASH_PATTERN = "-"; |
| |
| /** {@inheritDoc} */ |
| @Override public int compare(String o1, String o2) { |
| if (o1.equals(o2)) |
| return 0; |
| |
| String[] ver1 = o1.split(DOT_PATTERN, 3); |
| String[] ver2 = o2.split(DOT_PATTERN, 3); |
| |
| assert ver1.length == 3; |
| assert ver2.length == 3; |
| |
| if (Integer.valueOf(ver1[0]) >= Integer.valueOf(ver2[0]) && |
| Integer.valueOf(ver1[1]) >= Integer.valueOf(ver2[1]) && |
| Integer.valueOf(ver1[2].split(DASH_PATTERN)[0]) >= Integer.valueOf(ver2[2].split(DASH_PATTERN)[0])) |
| |
| return 1; |
| else |
| return -1; |
| } |
| } |
| } |