From 45fd606cb1d979f072fbcccb3ae3d146fbb9d3a1 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 4 Oct 2024 12:32:36 -0400 Subject: [PATCH 1/8] feat(versions): record own build info/version as properties, validate server version --- pom.xml | 27 ++++ src/main/java/io/cryostat/agent/Agent.java | 23 ++- .../java/io/cryostat/agent/BuildInfo.java | 61 +++++++ .../io/cryostat/agent/CryostatClient.java | 17 ++ .../java/io/cryostat/agent/Registration.java | 25 +++ .../java/io/cryostat/agent/VersionInfo.java | 150 ++++++++++++++++++ .../io/cryostat/agent/model/ServerHealth.java | 102 ++++++++++++ src/main/resources/versions.properties | 3 + .../java/io/cryostat/agent/SemverTest.java | 44 +++++ .../io/cryostat/agent/VersionInfoTest.java | 62 ++++++++ .../agent/model/ServerHealthTest.java | 36 +++++ 11 files changed, 543 insertions(+), 7 deletions(-) create mode 100644 src/main/java/io/cryostat/agent/BuildInfo.java create mode 100644 src/main/java/io/cryostat/agent/VersionInfo.java create mode 100644 src/main/java/io/cryostat/agent/model/ServerHealth.java create mode 100644 src/main/resources/versions.properties create mode 100644 src/test/java/io/cryostat/agent/SemverTest.java create mode 100644 src/test/java/io/cryostat/agent/VersionInfoTest.java create mode 100644 src/test/java/io/cryostat/agent/model/ServerHealthTest.java diff --git a/pom.xml b/pom.xml index b9f069f5..37448703 100644 --- a/pom.xml +++ b/pom.xml @@ -104,8 +104,12 @@ 3.3.1 3.10.1 1.14.0 + 3.4.1 io.cryostat.agent.shaded + + 4.0.0 + 5.0.0 @@ -299,6 +303,29 @@ + + org.codehaus.mojo + exec-maven-plugin + ${org.codehaus.mojo.exec.plugin.version} + + + generate-git-version + generate-resources + + exec + + + git + + rev-parse + --verify + HEAD + + ${project.build.directory}/classes/META-INF/gitinfo + + + + org.apache.maven.plugins maven-shade-plugin diff --git a/src/main/java/io/cryostat/agent/Agent.java b/src/main/java/io/cryostat/agent/Agent.java index e127bad2..95ece949 100644 --- a/src/main/java/io/cryostat/agent/Agent.java +++ b/src/main/java/io/cryostat/agent/Agent.java @@ -200,12 +200,21 @@ static URI selfJarLocation() throws URISyntaxException { @Override public void accept(AgentArgs args) { - args.getProperties() - .forEach( - (k, v) -> { - log.trace("Set system property {} = {}", k, v); - System.setProperty(k, v); - }); + Map properties = new HashMap<>(); + properties.putAll(args.getProperties()); + properties.put("gitCommitHash", new BuildInfo().getGitInfo().getHash()); + String agentVersion = "unknown"; + try { + VersionInfo versionInfo = VersionInfo.load(); + properties.putAll(versionInfo.asMap()); + } catch (IOException ioe) { + log.warn("Unable to read versions.properties file", ioe); + } + properties.forEach( + (k, v) -> { + log.trace("Set system property {} = {}", k, v); + System.setProperty(k, v); + }); AgentExitHandler agentExitHandler = null; try { final Client client = DaggerAgent_Client.builder().build(); @@ -271,7 +280,7 @@ public void accept(AgentArgs args) { webServer.start(); registration.start(); client.triggerEvaluator().start(args.getSmartTriggers()); - log.info("Startup complete"); + log.info("Cryostat Agent {} startup complete", agentVersion); } catch (Exception e) { log.error(Agent.class.getSimpleName() + " startup failure", e); if (agentExitHandler != null) { diff --git a/src/main/java/io/cryostat/agent/BuildInfo.java b/src/main/java/io/cryostat/agent/BuildInfo.java new file mode 100644 index 00000000..33677b17 --- /dev/null +++ b/src/main/java/io/cryostat/agent/BuildInfo.java @@ -0,0 +1,61 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed 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 io.cryostat.agent; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// FIXME this is adapted from Cryostat. Extract this to a reusable utility in libcryostat? +public class BuildInfo { + + private static Logger log = LoggerFactory.getLogger(BuildInfo.class); + private static final String RESOURCE_LOCATION = "META-INF/gitinfo"; + + private final GitInfo gitinfo = new GitInfo(); + + public GitInfo getGitInfo() { + return gitinfo; + } + + public static class GitInfo { + public String getHash() { + try (BufferedReader br = + new BufferedReader( + new InputStreamReader( + Thread.currentThread() + .getContextClassLoader() + .getResourceAsStream(RESOURCE_LOCATION), + StandardCharsets.UTF_8))) { + return br.lines() + .findFirst() + .orElseThrow( + () -> + new IllegalStateException( + String.format( + "Resource file %s is empty", + RESOURCE_LOCATION))) + .trim(); + } catch (Exception e) { + log.warn("Version retrieval exception", e); + return "unknown"; + } + } + } +} diff --git a/src/main/java/io/cryostat/agent/CryostatClient.java b/src/main/java/io/cryostat/agent/CryostatClient.java index 3f1577c9..3b4fb043 100644 --- a/src/main/java/io/cryostat/agent/CryostatClient.java +++ b/src/main/java/io/cryostat/agent/CryostatClient.java @@ -44,6 +44,7 @@ import io.cryostat.agent.model.DiscoveryNode; import io.cryostat.agent.model.PluginInfo; import io.cryostat.agent.model.RegistrationInfo; +import io.cryostat.agent.model.ServerHealth; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -111,6 +112,22 @@ public class CryostatClient { log.info("Using Cryostat baseuri {}", baseUri); } + public CompletableFuture serverHealth() { + HttpGet req = new HttpGet(baseUri.resolve("/health")); + log.trace("{}", req); + return supply(req, res -> logResponse(req, res)) + .thenApply( + res -> { + try (InputStream is = res.getEntity().getContent()) { + return mapper.readValue(is, ServerHealth.class); + } catch (IOException e) { + log.error("Unable to parse response as JSON", e); + throw new RegistrationException(e); + } + }) + .whenComplete((v, t) -> req.reset()); + } + public CompletableFuture checkRegistration(PluginInfo pluginInfo) { if (!pluginInfo.isInitialized()) { return CompletableFuture.completedFuture(false); diff --git a/src/main/java/io/cryostat/agent/Registration.java b/src/main/java/io/cryostat/agent/Registration.java index fd529c89..8e608a15 100644 --- a/src/main/java/io/cryostat/agent/Registration.java +++ b/src/main/java/io/cryostat/agent/Registration.java @@ -15,6 +15,7 @@ */ package io.cryostat.agent; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; @@ -33,6 +34,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import io.cryostat.agent.VersionInfo.Semver; import io.cryostat.agent.model.DiscoveryNode; import io.cryostat.agent.model.PluginInfo; import io.cryostat.agent.util.StringUtils; @@ -197,6 +199,29 @@ void tryRegister() { return; } try { + cryostat.serverHealth() + .thenAccept( + health -> { + Semver cryostatSemver = health.cryostatSemver(); + log.debug( + "Connected to Cryostat server: version {} , build {}", + cryostatSemver, + health.buildInfo().git().hash()); + try { + VersionInfo version = VersionInfo.load(); + if (!version.validateServerVersion(cryostatSemver)) { + log.warn( + "Cryostat server version {} is outside of expected" + + " range [{}, {})", + cryostatSemver, + version.getServerMin(), + version.getServerMax()); + } + } catch (IOException ioe) { + log.error("Unable to read versions.properties file", ioe); + } + }) + .get(); URI credentialedCallback = new URIBuilder(callback) .setUserInfo("storedcredentials", String.valueOf(credentialId)) diff --git a/src/main/java/io/cryostat/agent/VersionInfo.java b/src/main/java/io/cryostat/agent/VersionInfo.java new file mode 100644 index 00000000..c226641c --- /dev/null +++ b/src/main/java/io/cryostat/agent/VersionInfo.java @@ -0,0 +1,150 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed 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 io.cryostat.agent; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Comparator; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; + +public class VersionInfo { + + private static final String RESOURCE_LOCATION = "versions.properties"; + static final String AGENT_VERSION_KEY = "cryostat.agent.version"; + static final String MIN_VERSION_KEY = "cryostat.server.version.min"; + static final String MAX_VERSION_KEY = "cryostat.server.version.max"; + + private final String agentVersion; + private final Semver serverMin; + private final Semver serverMax; + + // testing only + VersionInfo(String agentVersion, Semver serverMin, Semver serverMax) { + this.agentVersion = agentVersion; + this.serverMin = serverMin; + this.serverMax = serverMax; + } + + public static VersionInfo load() throws IOException { + Properties prop = new Properties(); + try (InputStream is = + Thread.currentThread() + .getContextClassLoader() + .getResourceAsStream(RESOURCE_LOCATION)) { + prop.load(is); + } + String agentVersion = prop.getProperty(AGENT_VERSION_KEY); + Semver serverMin = Semver.fromString(prop.getProperty(MIN_VERSION_KEY)); + Semver serverMax = Semver.fromString(prop.getProperty(MAX_VERSION_KEY)); + return new VersionInfo(agentVersion, serverMin, serverMax); + } + + public Map asMap() { + return Map.of( + AGENT_VERSION_KEY, getAgentVersion(), + MIN_VERSION_KEY, getServerMin().toString(), + MAX_VERSION_KEY, getServerMax().toString()); + } + + public String getAgentVersion() { + return agentVersion; + } + + public Semver getServerMin() { + return serverMin; + } + + public Semver getServerMax() { + return serverMax; + } + + public boolean validateServerVersion(Semver actual) { + boolean greaterEqualMin = getServerMin().compareTo(actual) <= 0; + boolean lesserMax = getServerMax().compareTo(actual) > 0; + return greaterEqualMin && lesserMax; + } + + public static class Semver implements Comparable { + + private final int major; + private final int minor; + private final int patch; + + public Semver(int major, int minor, int patch) { + this.major = major; + this.minor = minor; + this.patch = patch; + } + + public static Semver fromString(String in) { + String[] parts = in.split("\\."); + if (parts.length != 3) { + throw new IllegalArgumentException(); + } + return new Semver( + Integer.parseInt(parts[0]), + Integer.parseInt(parts[1]), + Integer.parseInt(parts[2])); + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getPatch() { + return patch; + } + + @Override + public String toString() { + return String.format("%d.%d.%d", major, minor, patch); + } + + @Override + public int compareTo(Semver o) { + return Comparator.comparingInt(Semver::getMajor) + .thenComparing(Semver::getMinor) + .thenComparing(Semver::getPatch) + .compare(this, o); + } + + @Override + public int hashCode() { + return Objects.hash(major, minor, patch); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Semver other = (Semver) obj; + return major == other.major && minor == other.minor && patch == other.patch; + } + } +} diff --git a/src/main/java/io/cryostat/agent/model/ServerHealth.java b/src/main/java/io/cryostat/agent/model/ServerHealth.java new file mode 100644 index 00000000..ae5f0af0 --- /dev/null +++ b/src/main/java/io/cryostat/agent/model/ServerHealth.java @@ -0,0 +1,102 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed 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 io.cryostat.agent.model; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.cryostat.agent.VersionInfo.Semver; + +public class ServerHealth { + + private static final Pattern VERSION_PATTERN = + Pattern.compile( + "^v(?[\\d]+)\\.(?[\\d]+)\\.(?[\\d]+)(?:-[a-z0-9\\._-]*)?", + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + + private String cryostatVersion; + private BuildInfo build; + + public ServerHealth() {} + + public ServerHealth(String cryostatVersion, BuildInfo build) { + this.cryostatVersion = cryostatVersion; + this.build = build; + } + + public void setCryostatVersion(String cryostatVersion) { + this.cryostatVersion = cryostatVersion; + } + + public void setBuild(BuildInfo build) { + this.build = build; + } + + public String cryostatVersion() { + return cryostatVersion; + } + + public Semver cryostatSemver() { + Matcher m = VERSION_PATTERN.matcher(cryostatVersion()); + if (!m.matches()) { + return Semver.fromString("0.0.0"); + } + return new Semver( + Integer.parseInt(m.group("major")), + Integer.parseInt(m.group("minor")), + Integer.parseInt(m.group("patch"))); + } + + public BuildInfo buildInfo() { + return build; + } + + public static class BuildInfo { + private GitInfo git; + + public BuildInfo() {} + + public BuildInfo(GitInfo git) { + this.git = git; + } + + public void setGit(GitInfo git) { + this.git = git; + } + + public GitInfo git() { + return git; + } + } + + public static class GitInfo { + private String hash; + + public GitInfo() {} + + public GitInfo(String hash) { + this.hash = hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public String hash() { + return hash; + } + } +} diff --git a/src/main/resources/versions.properties b/src/main/resources/versions.properties new file mode 100644 index 00000000..fdf8c0c0 --- /dev/null +++ b/src/main/resources/versions.properties @@ -0,0 +1,3 @@ +cryostat.agent.version=${version} +cryostat.server.version.min=${cryostat.server.version.min} +cryostat.server.version.max=${cryostat.server.version.max} diff --git a/src/test/java/io/cryostat/agent/SemverTest.java b/src/test/java/io/cryostat/agent/SemverTest.java new file mode 100644 index 00000000..74e8ef90 --- /dev/null +++ b/src/test/java/io/cryostat/agent/SemverTest.java @@ -0,0 +1,44 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed 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 io.cryostat.agent; + +import io.cryostat.agent.VersionInfo.Semver; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class SemverTest { + + @ParameterizedTest + @CsvSource({ + "1.0.0, 1.0.0, 0", + "2.0.0, 1.0.0, 1", + "1.0.0, 2.0.0, -1", + "1.0.0, 1.1.0, -1", + "1.1.0, 1.0.0, 1", + "1.0.1, 1.0.0, 1", + "2.0.0, 1.1.0, 1", + "1.0.1, 1.1.0, -1", + "1.1.1, 1.0.1, 1", + }) + public void test(String first, String second, int result) { + Semver a = Semver.fromString(first); + Semver b = Semver.fromString(second); + MatcherAssert.assertThat(a.compareTo(b), Matchers.equalTo(result)); + } +} diff --git a/src/test/java/io/cryostat/agent/VersionInfoTest.java b/src/test/java/io/cryostat/agent/VersionInfoTest.java new file mode 100644 index 00000000..b057813e --- /dev/null +++ b/src/test/java/io/cryostat/agent/VersionInfoTest.java @@ -0,0 +1,62 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed 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 io.cryostat.agent; + +import io.cryostat.agent.VersionInfo.Semver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class VersionInfoTest { + + static final Semver serverMin = Semver.fromString("1.0.0"); + static final Semver serverMax = Semver.fromString("2.0.0"); + + @Test + public void testActualEqualsMin() { + VersionInfo info = new VersionInfo("", serverMin, serverMax); + Semver actual = Semver.fromString("1.0.0"); + Assertions.assertTrue(info.validateServerVersion(actual)); + } + + @Test + public void testActualEqualsMax() { + VersionInfo info = new VersionInfo("", serverMin, serverMax); + Semver actual = Semver.fromString("2.0.0"); + Assertions.assertFalse(info.validateServerVersion(actual)); + } + + @Test + public void testActualGreaterMax() { + VersionInfo info = new VersionInfo("", serverMin, serverMax); + Semver actual = Semver.fromString("3.0.0"); + Assertions.assertFalse(info.validateServerVersion(actual)); + } + + @Test + public void testActualLesserMin() { + VersionInfo info = new VersionInfo("", serverMin, serverMax); + Semver actual = Semver.fromString("0.1.0"); + Assertions.assertFalse(info.validateServerVersion(actual)); + } + + @Test + public void testActualInRange() { + VersionInfo info = new VersionInfo("", serverMin, serverMax); + Semver actual = Semver.fromString("1.1.0"); + Assertions.assertTrue(info.validateServerVersion(actual)); + } +} diff --git a/src/test/java/io/cryostat/agent/model/ServerHealthTest.java b/src/test/java/io/cryostat/agent/model/ServerHealthTest.java new file mode 100644 index 00000000..a63a6f3c --- /dev/null +++ b/src/test/java/io/cryostat/agent/model/ServerHealthTest.java @@ -0,0 +1,36 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed 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 io.cryostat.agent.model; + +import io.cryostat.agent.VersionInfo.Semver; +import io.cryostat.agent.model.ServerHealth.BuildInfo; +import io.cryostat.agent.model.ServerHealth.GitInfo; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +public class ServerHealthTest { + + @Test + public void test() { + GitInfo git = new GitInfo("abcd1234"); + BuildInfo build = new BuildInfo(git); + ServerHealth health = new ServerHealth("v1.2.3-snapshot", build); + MatcherAssert.assertThat( + health.cryostatSemver(), Matchers.equalTo(Semver.fromString("1.2.3"))); + } +} From 15be1148689801d0b3f6398e7350c6f646324ea5 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 4 Oct 2024 12:38:58 -0400 Subject: [PATCH 2/8] version string fixup --- src/main/java/io/cryostat/agent/Agent.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/cryostat/agent/Agent.java b/src/main/java/io/cryostat/agent/Agent.java index 95ece949..7103e0d0 100644 --- a/src/main/java/io/cryostat/agent/Agent.java +++ b/src/main/java/io/cryostat/agent/Agent.java @@ -202,10 +202,11 @@ static URI selfJarLocation() throws URISyntaxException { public void accept(AgentArgs args) { Map properties = new HashMap<>(); properties.putAll(args.getProperties()); - properties.put("gitCommitHash", new BuildInfo().getGitInfo().getHash()); + properties.put("build.git.commit-hash", new BuildInfo().getGitInfo().getHash()); String agentVersion = "unknown"; try { VersionInfo versionInfo = VersionInfo.load(); + agentVersion = versionInfo.getAgentVersion(); properties.putAll(versionInfo.asMap()); } catch (IOException ioe) { log.warn("Unable to read versions.properties file", ioe); From aa93ed94e8726d37ee67e2d026a3d529d8799877 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 4 Oct 2024 12:46:19 -0400 Subject: [PATCH 3/8] ignore mutability warnings --- src/main/java/io/cryostat/agent/model/ServerHealth.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/io/cryostat/agent/model/ServerHealth.java b/src/main/java/io/cryostat/agent/model/ServerHealth.java index ae5f0af0..2ec06f70 100644 --- a/src/main/java/io/cryostat/agent/model/ServerHealth.java +++ b/src/main/java/io/cryostat/agent/model/ServerHealth.java @@ -20,6 +20,9 @@ import io.cryostat.agent.VersionInfo.Semver; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) public class ServerHealth { private static final Pattern VERSION_PATTERN = @@ -64,6 +67,7 @@ public BuildInfo buildInfo() { return build; } + @SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) public static class BuildInfo { private GitInfo git; @@ -82,6 +86,7 @@ public GitInfo git() { } } + @SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) public static class GitInfo { private String hash; From 587518af80d6b40240191ac66329f0f45440282a Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 4 Oct 2024 14:06:59 -0400 Subject: [PATCH 4/8] include version string in startup message --- src/main/java/io/cryostat/agent/Agent.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/cryostat/agent/Agent.java b/src/main/java/io/cryostat/agent/Agent.java index 7103e0d0..65c967ba 100644 --- a/src/main/java/io/cryostat/agent/Agent.java +++ b/src/main/java/io/cryostat/agent/Agent.java @@ -155,7 +155,6 @@ public static void agentmain(String args, Instrumentation instrumentation) { Thread t = new Thread( () -> { - log.info("Cryostat Agent starting..."); agent.accept(aa); }); t.setDaemon(true); @@ -211,6 +210,7 @@ public void accept(AgentArgs args) { } catch (IOException ioe) { log.warn("Unable to read versions.properties file", ioe); } + log.info("Cryostat Agent version {} starting...", agentVersion); properties.forEach( (k, v) -> { log.trace("Set system property {} = {}", k, v); @@ -281,7 +281,7 @@ public void accept(AgentArgs args) { webServer.start(); registration.start(); client.triggerEvaluator().start(args.getSmartTriggers()); - log.info("Cryostat Agent {} startup complete", agentVersion); + log.info("startup complete"); } catch (Exception e) { log.error(Agent.class.getSimpleName() + " startup failure", e); if (agentExitHandler != null) { From 24bf7d851d7439658f0a82d2b2c3e3395fc21112 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 4 Oct 2024 14:14:41 -0400 Subject: [PATCH 5/8] fixup! include version string in startup message --- src/main/java/io/cryostat/agent/Agent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/cryostat/agent/Agent.java b/src/main/java/io/cryostat/agent/Agent.java index 65c967ba..4cde25e0 100644 --- a/src/main/java/io/cryostat/agent/Agent.java +++ b/src/main/java/io/cryostat/agent/Agent.java @@ -281,7 +281,7 @@ public void accept(AgentArgs args) { webServer.start(); registration.start(); client.triggerEvaluator().start(args.getSmartTriggers()); - log.info("startup complete"); + log.info("Startup complete"); } catch (Exception e) { log.error(Agent.class.getSimpleName() + " startup failure", e); if (agentExitHandler != null) { From da0389518dabc59f36c1ec765d8ec91886da6773 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 7 Oct 2024 10:59:08 -0400 Subject: [PATCH 6/8] handle nullable context classloader --- src/main/java/io/cryostat/agent/Agent.java | 11 +++++- .../java/io/cryostat/agent/BuildInfo.java | 6 ++-- .../java/io/cryostat/agent/VersionInfo.java | 9 ++--- .../io/cryostat/agent/util/ResourcesUtil.java | 35 +++++++++++++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 src/main/java/io/cryostat/agent/util/ResourcesUtil.java diff --git a/src/main/java/io/cryostat/agent/Agent.java b/src/main/java/io/cryostat/agent/Agent.java index 4cde25e0..f1b9a9ec 100644 --- a/src/main/java/io/cryostat/agent/Agent.java +++ b/src/main/java/io/cryostat/agent/Agent.java @@ -38,6 +38,7 @@ import javax.inject.Singleton; import io.cryostat.agent.ConfigModule.URIRange; +import io.cryostat.agent.VersionInfo.Semver; import io.cryostat.agent.harvest.Harvester; import io.cryostat.agent.insights.InsightsAgentHelper; import io.cryostat.agent.model.PluginInfo; @@ -49,6 +50,7 @@ import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import dagger.Component; +import org.apache.commons.lang3.tuple.Pair; import org.eclipse.microprofile.config.Config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -203,14 +205,21 @@ public void accept(AgentArgs args) { properties.putAll(args.getProperties()); properties.put("build.git.commit-hash", new BuildInfo().getGitInfo().getHash()); String agentVersion = "unknown"; + Pair serverVersionRange = Pair.of(Semver.UNKNOWN, Semver.UNKNOWN); try { VersionInfo versionInfo = VersionInfo.load(); + serverVersionRange = Pair.of(versionInfo.getServerMin(), versionInfo.getServerMax()); agentVersion = versionInfo.getAgentVersion(); properties.putAll(versionInfo.asMap()); } catch (IOException ioe) { log.warn("Unable to read versions.properties file", ioe); } - log.info("Cryostat Agent version {} starting...", agentVersion); + log.info( + "Cryostat Agent version {} (for Cryostat server version range [{}, {}) )" + + " starting...", + agentVersion, + serverVersionRange.getLeft(), + serverVersionRange.getRight()); properties.forEach( (k, v) -> { log.trace("Set system property {} = {}", k, v); diff --git a/src/main/java/io/cryostat/agent/BuildInfo.java b/src/main/java/io/cryostat/agent/BuildInfo.java index 33677b17..d1543149 100644 --- a/src/main/java/io/cryostat/agent/BuildInfo.java +++ b/src/main/java/io/cryostat/agent/BuildInfo.java @@ -19,6 +19,8 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import io.cryostat.agent.util.ResourcesUtil; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,9 +41,7 @@ public String getHash() { try (BufferedReader br = new BufferedReader( new InputStreamReader( - Thread.currentThread() - .getContextClassLoader() - .getResourceAsStream(RESOURCE_LOCATION), + ResourcesUtil.getResourceAsStream(RESOURCE_LOCATION), StandardCharsets.UTF_8))) { return br.lines() .findFirst() diff --git a/src/main/java/io/cryostat/agent/VersionInfo.java b/src/main/java/io/cryostat/agent/VersionInfo.java index c226641c..6b74d781 100644 --- a/src/main/java/io/cryostat/agent/VersionInfo.java +++ b/src/main/java/io/cryostat/agent/VersionInfo.java @@ -22,6 +22,8 @@ import java.util.Objects; import java.util.Properties; +import io.cryostat.agent.util.ResourcesUtil; + public class VersionInfo { private static final String RESOURCE_LOCATION = "versions.properties"; @@ -42,10 +44,7 @@ public class VersionInfo { public static VersionInfo load() throws IOException { Properties prop = new Properties(); - try (InputStream is = - Thread.currentThread() - .getContextClassLoader() - .getResourceAsStream(RESOURCE_LOCATION)) { + try (InputStream is = ResourcesUtil.getResourceAsStream(RESOURCE_LOCATION)) { prop.load(is); } String agentVersion = prop.getProperty(AGENT_VERSION_KEY); @@ -81,6 +80,8 @@ public boolean validateServerVersion(Semver actual) { public static class Semver implements Comparable { + public static final Semver UNKNOWN = new Semver(0, 0, 0); + private final int major; private final int minor; private final int patch; diff --git a/src/main/java/io/cryostat/agent/util/ResourcesUtil.java b/src/main/java/io/cryostat/agent/util/ResourcesUtil.java new file mode 100644 index 00000000..44c76e85 --- /dev/null +++ b/src/main/java/io/cryostat/agent/util/ResourcesUtil.java @@ -0,0 +1,35 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed 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 io.cryostat.agent.util; + +import java.io.InputStream; + +public class ResourcesUtil { + + private ResourcesUtil() {} + + public static ClassLoader getClassLoader() { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + cl = ClassLoader.getSystemClassLoader(); + } + return cl; + } + + public static InputStream getResourceAsStream(String location) { + return getClassLoader().getResourceAsStream(location); + } +} From 27f3858f8b28f0c96a3ed41a0fae9223d4ee32a5 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 8 Oct 2024 14:19:22 -0400 Subject: [PATCH 7/8] semver cleanup --- src/main/java/io/cryostat/agent/Agent.java | 2 +- .../java/io/cryostat/agent/VersionInfo.java | 18 ++++++++++++------ .../io/cryostat/agent/VersionInfoTest.java | 10 +++++----- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/cryostat/agent/Agent.java b/src/main/java/io/cryostat/agent/Agent.java index f1b9a9ec..e9a9a166 100644 --- a/src/main/java/io/cryostat/agent/Agent.java +++ b/src/main/java/io/cryostat/agent/Agent.java @@ -204,7 +204,7 @@ public void accept(AgentArgs args) { Map properties = new HashMap<>(); properties.putAll(args.getProperties()); properties.put("build.git.commit-hash", new BuildInfo().getGitInfo().getHash()); - String agentVersion = "unknown"; + Semver agentVersion = Semver.UNKNOWN; Pair serverVersionRange = Pair.of(Semver.UNKNOWN, Semver.UNKNOWN); try { VersionInfo versionInfo = VersionInfo.load(); diff --git a/src/main/java/io/cryostat/agent/VersionInfo.java b/src/main/java/io/cryostat/agent/VersionInfo.java index 6b74d781..b6f98aee 100644 --- a/src/main/java/io/cryostat/agent/VersionInfo.java +++ b/src/main/java/io/cryostat/agent/VersionInfo.java @@ -31,12 +31,12 @@ public class VersionInfo { static final String MIN_VERSION_KEY = "cryostat.server.version.min"; static final String MAX_VERSION_KEY = "cryostat.server.version.max"; - private final String agentVersion; + private final Semver agentVersion; private final Semver serverMin; private final Semver serverMax; // testing only - VersionInfo(String agentVersion, Semver serverMin, Semver serverMax) { + VersionInfo(Semver agentVersion, Semver serverMin, Semver serverMax) { this.agentVersion = agentVersion; this.serverMin = serverMin; this.serverMax = serverMax; @@ -47,7 +47,7 @@ public static VersionInfo load() throws IOException { try (InputStream is = ResourcesUtil.getResourceAsStream(RESOURCE_LOCATION)) { prop.load(is); } - String agentVersion = prop.getProperty(AGENT_VERSION_KEY); + Semver agentVersion = Semver.fromString(prop.getProperty(AGENT_VERSION_KEY)); Semver serverMin = Semver.fromString(prop.getProperty(MIN_VERSION_KEY)); Semver serverMax = Semver.fromString(prop.getProperty(MAX_VERSION_KEY)); return new VersionInfo(agentVersion, serverMin, serverMax); @@ -55,12 +55,12 @@ public static VersionInfo load() throws IOException { public Map asMap() { return Map.of( - AGENT_VERSION_KEY, getAgentVersion(), + AGENT_VERSION_KEY, getAgentVersion().toString(), MIN_VERSION_KEY, getServerMin().toString(), MAX_VERSION_KEY, getServerMax().toString()); } - public String getAgentVersion() { + public Semver getAgentVersion() { return agentVersion; } @@ -80,7 +80,13 @@ public boolean validateServerVersion(Semver actual) { public static class Semver implements Comparable { - public static final Semver UNKNOWN = new Semver(0, 0, 0); + public static final Semver UNKNOWN = + new Semver(0, 0, 0) { + @Override + public String toString() { + return "unknown"; + } + }; private final int major; private final int minor; diff --git a/src/test/java/io/cryostat/agent/VersionInfoTest.java b/src/test/java/io/cryostat/agent/VersionInfoTest.java index b057813e..a2a27c96 100644 --- a/src/test/java/io/cryostat/agent/VersionInfoTest.java +++ b/src/test/java/io/cryostat/agent/VersionInfoTest.java @@ -27,35 +27,35 @@ public class VersionInfoTest { @Test public void testActualEqualsMin() { - VersionInfo info = new VersionInfo("", serverMin, serverMax); + VersionInfo info = new VersionInfo(Semver.UNKNOWN, serverMin, serverMax); Semver actual = Semver.fromString("1.0.0"); Assertions.assertTrue(info.validateServerVersion(actual)); } @Test public void testActualEqualsMax() { - VersionInfo info = new VersionInfo("", serverMin, serverMax); + VersionInfo info = new VersionInfo(Semver.UNKNOWN, serverMin, serverMax); Semver actual = Semver.fromString("2.0.0"); Assertions.assertFalse(info.validateServerVersion(actual)); } @Test public void testActualGreaterMax() { - VersionInfo info = new VersionInfo("", serverMin, serverMax); + VersionInfo info = new VersionInfo(Semver.UNKNOWN, serverMin, serverMax); Semver actual = Semver.fromString("3.0.0"); Assertions.assertFalse(info.validateServerVersion(actual)); } @Test public void testActualLesserMin() { - VersionInfo info = new VersionInfo("", serverMin, serverMax); + VersionInfo info = new VersionInfo(Semver.UNKNOWN, serverMin, serverMax); Semver actual = Semver.fromString("0.1.0"); Assertions.assertFalse(info.validateServerVersion(actual)); } @Test public void testActualInRange() { - VersionInfo info = new VersionInfo("", serverMin, serverMax); + VersionInfo info = new VersionInfo(Semver.UNKNOWN, serverMin, serverMax); Semver actual = Semver.fromString("1.1.0"); Assertions.assertTrue(info.validateServerVersion(actual)); } From 0a6a57818d23fc491e3f14180ddd1689d393bb36 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 8 Oct 2024 14:22:08 -0400 Subject: [PATCH 8/8] parameterize --- .../io/cryostat/agent/VersionInfoTest.java | 43 ++++--------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/src/test/java/io/cryostat/agent/VersionInfoTest.java b/src/test/java/io/cryostat/agent/VersionInfoTest.java index a2a27c96..6ad7cb55 100644 --- a/src/test/java/io/cryostat/agent/VersionInfoTest.java +++ b/src/test/java/io/cryostat/agent/VersionInfoTest.java @@ -17,46 +17,21 @@ import io.cryostat.agent.VersionInfo.Semver; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; public class VersionInfoTest { static final Semver serverMin = Semver.fromString("1.0.0"); static final Semver serverMax = Semver.fromString("2.0.0"); - @Test - public void testActualEqualsMin() { + @ParameterizedTest + @CsvSource({"1.0.0, true", "2.0.0, false", "3.0.0, false", "0.1.0, false", "1.1.0, true"}) + public void test(String serverVersion, boolean inRange) { VersionInfo info = new VersionInfo(Semver.UNKNOWN, serverMin, serverMax); - Semver actual = Semver.fromString("1.0.0"); - Assertions.assertTrue(info.validateServerVersion(actual)); - } - - @Test - public void testActualEqualsMax() { - VersionInfo info = new VersionInfo(Semver.UNKNOWN, serverMin, serverMax); - Semver actual = Semver.fromString("2.0.0"); - Assertions.assertFalse(info.validateServerVersion(actual)); - } - - @Test - public void testActualGreaterMax() { - VersionInfo info = new VersionInfo(Semver.UNKNOWN, serverMin, serverMax); - Semver actual = Semver.fromString("3.0.0"); - Assertions.assertFalse(info.validateServerVersion(actual)); - } - - @Test - public void testActualLesserMin() { - VersionInfo info = new VersionInfo(Semver.UNKNOWN, serverMin, serverMax); - Semver actual = Semver.fromString("0.1.0"); - Assertions.assertFalse(info.validateServerVersion(actual)); - } - - @Test - public void testActualInRange() { - VersionInfo info = new VersionInfo(Semver.UNKNOWN, serverMin, serverMax); - Semver actual = Semver.fromString("1.1.0"); - Assertions.assertTrue(info.validateServerVersion(actual)); + Semver actual = Semver.fromString(serverVersion); + MatcherAssert.assertThat(info.validateServerVersion(actual), Matchers.equalTo(inRange)); } }