diff --git a/pom.xml b/pom.xml index 8f073c45ad3..ded0599965b 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.elasticsearch elasticsearch-alerts - 1.0.0-beta2i + 1.0.0-beta2-SNAPSHOT org.sonatype.oss @@ -154,6 +154,12 @@ + + + src/main/resources + true + + org.apache.maven.plugins @@ -389,6 +395,25 @@ true + + + org.codehaus.mojo + buildnumber-maven-plugin + 1.3 + + + validate + + create + + + + + false + false + + + diff --git a/src/main/java/org/elasticsearch/alerts/AlertsBuild.java b/src/main/java/org/elasticsearch/alerts/AlertsBuild.java new file mode 100644 index 00000000000..558bc8aeb25 --- /dev/null +++ b/src/main/java/org/elasticsearch/alerts/AlertsBuild.java @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.alerts; + +import org.elasticsearch.common.io.FastStringReader; +import org.elasticsearch.common.io.Streams; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.joda.time.DateTimeZone; +import org.elasticsearch.common.joda.time.format.ISODateTimeFormat; + +import java.io.IOException; +import java.util.Properties; + +/** + */ +public class AlertsBuild { + + public static final AlertsBuild CURRENT; + + static { + String hash = "NA"; + String hashShort = "NA"; + String timestamp = "NA"; + + try { + String properties = Streams.copyToStringFromClasspath("/alerts-build.properties"); + Properties props = new Properties(); + props.load(new FastStringReader(properties)); + hash = props.getProperty("hash", hash); + if (!hash.equals("NA")) { + hashShort = hash.substring(0, 7); + } + String gitTimestampRaw = props.getProperty("timestamp"); + if (gitTimestampRaw != null) { + timestamp = ISODateTimeFormat.dateTimeNoMillis().withZone(DateTimeZone.UTC).print(Long.parseLong(gitTimestampRaw)); + } + } catch (Exception e) { + // just ignore... + } + + CURRENT = new AlertsBuild(hash, hashShort, timestamp); + } + + private final String hash; + private final String hashShort; + private final String timestamp; + + AlertsBuild(String hash, String hashShort, String timestamp) { + this.hash = hash; + this.hashShort = hashShort; + this.timestamp = timestamp; + } + + public String hash() { + return hash; + } + + public String hashShort() { + return hashShort; + } + + public String timestamp() { + return timestamp; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AlertsBuild that = (AlertsBuild) o; + + if (!hash.equals(that.hash)) return false; + if (!hashShort.equals(that.hashShort)) return false; + if (!timestamp.equals(that.timestamp)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = hash.hashCode(); + result = 31 * result + hashShort.hashCode(); + result = 31 * result + timestamp.hashCode(); + return result; + } + + public static AlertsBuild readBuild(StreamInput in) throws IOException { + String hash = in.readString(); + String hashShort = in.readString(); + String timestamp = in.readString(); + return new AlertsBuild(hash, hashShort, timestamp); + } + + public static void writeBuild(AlertsBuild build, StreamOutput out) throws IOException { + out.writeString(build.hash()); + out.writeString(build.hashShort()); + out.writeString(build.timestamp()); + } +} diff --git a/src/main/java/org/elasticsearch/alerts/AlertsVersion.java b/src/main/java/org/elasticsearch/alerts/AlertsVersion.java new file mode 100644 index 00000000000..b715211fe45 --- /dev/null +++ b/src/main/java/org/elasticsearch/alerts/AlertsVersion.java @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.alerts; + +import org.elasticsearch.Version; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; + +import java.io.IOException; +import java.io.Serializable; + +/** + */ +@SuppressWarnings("deprecation") +public class AlertsVersion implements Serializable { + + // The logic for ID is: XXYYZZAA, where XX is major version, YY is minor version, ZZ is revision, and AA is Beta/RC indicator + // AA values below 50 are beta builds, and below 99 are RC builds, with 99 indicating a release + // the (internal) format of the id is there so we can easily do after/before checks on the id + + // The first internal beta has already been released, without this class being here, so we start version version 2. + public static final int V_1_0_0_Beta2_ID = /*00*/1000002; + public static final AlertsVersion V_1_0_0_Beta2 = new AlertsVersion(V_1_0_0_Beta2_ID, true, Version.V_1_4_0); + + public static final AlertsVersion CURRENT = V_1_0_0_Beta2; + + public static AlertsVersion readVersion(StreamInput in) throws IOException { + return fromId(in.readVInt()); + } + + public static AlertsVersion fromId(int id) { + switch (id) { + case V_1_0_0_Beta2_ID: + return V_1_0_0_Beta2; + + default: + return new AlertsVersion(id, null, Version.CURRENT); + } + } + + public static void writeVersion(AlertsVersion version, StreamOutput out) throws IOException { + out.writeVInt(version.id); + } + + /** + * Returns the smallest version between the 2. + */ + public static AlertsVersion smallest(AlertsVersion version1, AlertsVersion version2) { + return version1.id < version2.id ? version1 : version2; + } + + /** + * Returns the version given its string representation, current version if the argument is null or empty + */ + public static AlertsVersion fromString(String version) { + if (!Strings.hasLength(version)) { + return AlertsVersion.CURRENT; + } + + String[] parts = version.split("\\."); + if (parts.length < 3 || parts.length > 4) { + throw new IllegalArgumentException("the version needs to contain major, minor and revision, and optionally the build"); + } + + try { + //we reverse the version id calculation based on some assumption as we can't reliably reverse the modulo + int major = Integer.parseInt(parts[0]) * 1000000; + int minor = Integer.parseInt(parts[1]) * 10000; + int revision = Integer.parseInt(parts[2]) * 100; + + int build = 99; + if (parts.length == 4) { + String buildStr = parts[3]; + if (buildStr.startsWith("Beta")) { + build = Integer.parseInt(buildStr.substring(4)); + } + if (buildStr.startsWith("RC")) { + build = Integer.parseInt(buildStr.substring(2)) + 50; + } + } + + return fromId(major + minor + revision + build); + + } catch(NumberFormatException e) { + throw new IllegalArgumentException("unable to parse version " + version, e); + } + } + + public final int id; + public final byte major; + public final byte minor; + public final byte revision; + public final byte build; + public final Boolean snapshot; + public final Version minEsCompatibilityVersion; + // TODO: Once licencing integration has been completed license version should be added to + + AlertsVersion(int id, @Nullable Boolean snapshot, Version minEsCompatibilityVersion) { + this.id = id; + this.major = (byte) ((id / 1000000) % 100); + this.minor = (byte) ((id / 10000) % 100); + this.revision = (byte) ((id / 100) % 100); + this.build = (byte) (id % 100); + this.snapshot = snapshot; + this.minEsCompatibilityVersion = minEsCompatibilityVersion; + } + + public boolean snapshot() { + return snapshot != null && snapshot; + } + + public boolean after(AlertsVersion version) { + return version.id < id; + } + + public boolean onOrAfter(AlertsVersion version) { + return version.id <= id; + } + + public boolean before(AlertsVersion version) { + return version.id > id; + } + + public boolean onOrBefore(AlertsVersion version) { + return version.id >= id; + } + + public boolean compatibleWith(AlertsVersion version) { + return version.onOrAfter(minimumCompatibilityVersion()); + } + + public boolean compatibleWith(Version esVersion) { + return esVersion.onOrAfter(minEsCompatibilityVersion); + } + + /** + * Returns the minimum compatible version based on the current + * version. Ie a node needs to have at least the return version in order + * to communicate with a node running the current version. The returned version + * is in most of the cases the smallest major version release unless the current version + * is a beta or RC release then the version itself is returned. + */ + public AlertsVersion minimumCompatibilityVersion() { + return AlertsVersion.smallest(this, fromId(major * 1000000 + 99)); + } + + /** + * @return The minimum elasticsearch version this shield version is compatible with. + */ + public Version minimumEsCompatiblityVersion() { + return minEsCompatibilityVersion; + } + + /** + * Just the version number (without -SNAPSHOT if snapshot). + */ + public String number() { + StringBuilder sb = new StringBuilder(); + sb.append(major).append('.').append(minor).append('.').append(revision); + if (build < 50) { + sb.append(".Beta").append(build); + } else if (build < 99) { + sb.append(".RC").append(build - 50); + } + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(number()); + if (snapshot()) { + sb.append("-SNAPSHOT"); + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AlertsVersion that = (AlertsVersion) o; + + if (id != that.id) return false; + + return true; + } + + @Override + public int hashCode() { + return id; + } +} diff --git a/src/main/java/org/elasticsearch/alerts/rest/RestAlertsStatsAction.java b/src/main/java/org/elasticsearch/alerts/rest/RestAlertsStatsAction.java index 49adcf6e8a6..b7a450c9f54 100644 --- a/src/main/java/org/elasticsearch/alerts/rest/RestAlertsStatsAction.java +++ b/src/main/java/org/elasticsearch/alerts/rest/RestAlertsStatsAction.java @@ -47,6 +47,14 @@ public class RestAlertsStatsAction extends BaseRestHandler { .field("alert_action_queue_size", alertsStatsResponse.getAlertActionManagerQueueSize()) .field("number_of_alerts", alertsStatsResponse.getNumberOfRegisteredAlerts()) .field("alert_action_queue_max_size", alertsStatsResponse.getAlertActionManagerLargestQueueSize()); + + builder.startObject("version") + .field("number", alertsStatsResponse.getVersion().number()) + .field("build_hash", alertsStatsResponse.getBuild().hash()) + .field("build_timestamp", alertsStatsResponse.getBuild().timestamp()) + .field("build_snapshot", alertsStatsResponse.getVersion().snapshot) + .endObject(); + return new BytesRestResponse(OK, builder); } diff --git a/src/main/java/org/elasticsearch/alerts/transport/actions/stats/AlertsStatsResponse.java b/src/main/java/org/elasticsearch/alerts/transport/actions/stats/AlertsStatsResponse.java index 3db78e8daeb..1617feca354 100644 --- a/src/main/java/org/elasticsearch/alerts/transport/actions/stats/AlertsStatsResponse.java +++ b/src/main/java/org/elasticsearch/alerts/transport/actions/stats/AlertsStatsResponse.java @@ -6,6 +6,8 @@ package org.elasticsearch.alerts.transport.actions.stats; import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.alerts.AlertsBuild; +import org.elasticsearch.alerts.AlertsVersion; import org.elasticsearch.alerts.State; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -17,12 +19,12 @@ import java.io.IOException; */ public class AlertsStatsResponse extends ActionResponse { + private AlertsVersion version; + private AlertsBuild build; private long numberOfRegisteredAlerts; private State alertManagerState; private boolean alertActionManagerStarted; private long alertActionManagerQueueSize; - - private long alertActionManagerLargestQueueSize; public AlertsStatsResponse() { @@ -36,11 +38,7 @@ public class AlertsStatsResponse extends ActionResponse { return alertActionManagerQueueSize; } - /** - * Sets the current size of the alert action queue - * @param alertActionManagerQueueSize - */ - public void setAlertActionManagerQueueSize(long alertActionManagerQueueSize) { + void setAlertActionManagerQueueSize(long alertActionManagerQueueSize) { this.alertActionManagerQueueSize = alertActionManagerQueueSize; } @@ -52,11 +50,7 @@ public class AlertsStatsResponse extends ActionResponse { return numberOfRegisteredAlerts; } - /** - * Set the number of alerts currently registered in the system - * @param numberOfRegisteredAlerts - */ - public void setNumberOfRegisteredAlerts(long numberOfRegisteredAlerts) { + void setNumberOfRegisteredAlerts(long numberOfRegisteredAlerts) { this.numberOfRegisteredAlerts = numberOfRegisteredAlerts; } @@ -79,11 +73,7 @@ public class AlertsStatsResponse extends ActionResponse { return alertActionManagerStarted; } - /** - * Sets if the alert action manager is started - * @param alertActionManagerStarted - */ - public void setAlertActionManagerStarted(boolean alertActionManagerStarted) { + void setAlertActionManagerStarted(boolean alertActionManagerStarted) { this.alertActionManagerStarted = alertActionManagerStarted; } @@ -95,14 +85,31 @@ public class AlertsStatsResponse extends ActionResponse { return alertActionManagerLargestQueueSize; } - /** - * Sets the largest alert action manager queue size - * @param alertActionManagerLargestQueueSize - */ - public void setAlertActionManagerLargestQueueSize(long alertActionManagerLargestQueueSize) { + void setAlertActionManagerLargestQueueSize(long alertActionManagerLargestQueueSize) { this.alertActionManagerLargestQueueSize = alertActionManagerLargestQueueSize; } + /** + * @return The alerts plugin version. + */ + public AlertsVersion getVersion() { + return version; + } + + void setVersion(AlertsVersion version) { + this.version = version; + } + + /** + * @return The alerts plugin build information. + */ + public AlertsBuild getBuild() { + return build; + } + + void setBuild(AlertsBuild build) { + this.build = build; + } @Override public void readFrom(StreamInput in) throws IOException { @@ -112,6 +119,8 @@ public class AlertsStatsResponse extends ActionResponse { alertActionManagerLargestQueueSize = in.readLong(); alertManagerState = State.fromId(in.readByte()); alertActionManagerStarted = in.readBoolean(); + version = AlertsVersion.readVersion(in); + build = AlertsBuild.readBuild(in); } @Override @@ -122,5 +131,7 @@ public class AlertsStatsResponse extends ActionResponse { out.writeLong(alertActionManagerLargestQueueSize); out.writeByte(alertManagerState.getId()); out.writeBoolean(alertActionManagerStarted); + AlertsVersion.writeVersion(version, out); + AlertsBuild.writeBuild(build, out); } } diff --git a/src/main/java/org/elasticsearch/alerts/transport/actions/stats/TransportAlertStatsAction.java b/src/main/java/org/elasticsearch/alerts/transport/actions/stats/TransportAlertStatsAction.java index 6740ff78bb5..71229822545 100644 --- a/src/main/java/org/elasticsearch/alerts/transport/actions/stats/TransportAlertStatsAction.java +++ b/src/main/java/org/elasticsearch/alerts/transport/actions/stats/TransportAlertStatsAction.java @@ -11,6 +11,8 @@ import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction; import org.elasticsearch.alerts.AlertService; import org.elasticsearch.alerts.actions.AlertActionService; +import org.elasticsearch.alerts.AlertsBuild; +import org.elasticsearch.alerts.AlertsVersion; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; @@ -60,6 +62,8 @@ public class TransportAlertStatsAction extends TransportMasterNodeOperationActio statsResponse.setAlertActionManagerQueueSize(alertActionService.getQueueSize()); statsResponse.setNumberOfRegisteredAlerts(alertService.getNumberOfAlerts()); statsResponse.setAlertActionManagerLargestQueueSize(alertActionService.getLargestQueueSize()); + statsResponse.setVersion(AlertsVersion.CURRENT); + statsResponse.setBuild(AlertsBuild.CURRENT); listener.onResponse(statsResponse); } diff --git a/src/main/resources/alerts-build.properties b/src/main/resources/alerts-build.properties new file mode 100644 index 00000000000..f3ad519cedb --- /dev/null +++ b/src/main/resources/alerts-build.properties @@ -0,0 +1,3 @@ +version=${project.version} +hash=${buildNumber} +timestamp=${timestamp} \ No newline at end of file diff --git a/src/test/java/org/elasticsearch/alerts/actions/AlertStatsTests.java b/src/test/java/org/elasticsearch/alerts/actions/AlertStatsTests.java index f86bd051bf2..a59dfd16df0 100644 --- a/src/test/java/org/elasticsearch/alerts/actions/AlertStatsTests.java +++ b/src/test/java/org/elasticsearch/alerts/actions/AlertStatsTests.java @@ -7,6 +7,8 @@ package org.elasticsearch.alerts.actions; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.alerts.AbstractAlertingTests; +import org.elasticsearch.alerts.AlertsBuild; +import org.elasticsearch.alerts.AlertsVersion; import org.elasticsearch.alerts.State; import org.elasticsearch.alerts.client.AlertsClient; import org.elasticsearch.alerts.transport.actions.stats.AlertsStatsRequest; @@ -38,6 +40,8 @@ public class AlertStatsTests extends AbstractAlertingTests { assertThat(response.getAlertActionManagerQueueSize(), equalTo(0L)); assertThat(response.getNumberOfRegisteredAlerts(), equalTo(0L)); assertThat(response.getAlertActionManagerLargestQueueSize(), equalTo(0L)); + assertThat(response.getVersion(), equalTo(AlertsVersion.CURRENT)); + assertThat(response.getBuild(), equalTo(AlertsBuild.CURRENT)); } @Test