blob: ff834f6b0fc85f943625d45f8daf5cf265c29d96 [file] [log] [blame]
/*
* 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;
}
}
}