From 99a5e21917a6869588d236a878d839a2783cd333 Mon Sep 17 00:00:00 2001 From: woodywoodse <11844554+ClaudioWaldvogel@users.noreply.github.com> Date: Mon, 5 Sep 2022 16:43:18 +0200 Subject: [PATCH] Fix/agenthealthmanager 2.1.1 (#1521) * fix: AgentHealthManger does not handle negative duration while scheduling health checks Co-authored-by: Claudio Waldvogel --- .github/workflows/agent_test.yml | 2 + build.gradle | 4 - .../build.gradle | 5 +- .../build.gradle | 6 +- inspectit-ocelot-agent/build.gradle | 5 +- inspectit-ocelot-bootstrap/build.gradle | 4 +- inspectit-ocelot-config/build.gradle | 4 +- .../selfmonitoring/AgentHealthSettings.java | 12 + .../ocelot/config/default/self-monitoring.yml | 5 + inspectit-ocelot-core/build.gradle | 21 +- .../java/com/mindprod/jarcheck/JarCheck.java | 222 ++++++++++++++++++ .../selfmonitoring/AgentHealthManager.java | 11 +- .../AgentHealthManagerTest.java | 2 + inspectit-ocelot-sdk/build.gradle | 4 +- 14 files changed, 284 insertions(+), 23 deletions(-) create mode 100644 inspectit-ocelot-core/src/main/java/com/mindprod/jarcheck/JarCheck.java diff --git a/.github/workflows/agent_test.yml b/.github/workflows/agent_test.yml index 4b72cccd39..02d9dd5194 100644 --- a/.github/workflows/agent_test.yml +++ b/.github/workflows/agent_test.yml @@ -7,6 +7,8 @@ on: pull_request: branches: - master + - 1.*.* + - 2.*.* paths-ignore: - 'components/**' - 'inspectit-ocelot-documentation/**' diff --git a/build.gradle b/build.gradle index 981ec7bb36..35f4a1d7d6 100644 --- a/build.gradle +++ b/build.gradle @@ -14,10 +14,6 @@ allprojects { repositories { mavenCentral() - maven { - name 'Nexus@NT' - url "https://repository.novatec-gmbh.de/content/repositories/3rd_party_libs/" - } } apply plugin: 'java' diff --git a/components/inspectit-ocelot-configdocsgenerator/build.gradle b/components/inspectit-ocelot-configdocsgenerator/build.gradle index 11cf3b1550..4f35629d3d 100644 --- a/components/inspectit-ocelot-configdocsgenerator/build.gradle +++ b/components/inspectit-ocelot-configdocsgenerator/build.gradle @@ -52,5 +52,6 @@ tasks.named('test') { // Use JUnit Platform for unit tests. useJUnitPlatform() } - -sourceCompatibility = 1.8 +//to guarantee that the Configuration Server is compatible with Java 8 runtime environments +sourceCompatibility = 1.8 // Java version compatibility to use when compiling Java source. +targetCompatibility = 1.8 // Java version to generate classes for. diff --git a/components/inspectit-ocelot-configurationserver/build.gradle b/components/inspectit-ocelot-configurationserver/build.gradle index 71d46c203e..04f05a5ea3 100644 --- a/components/inspectit-ocelot-configurationserver/build.gradle +++ b/components/inspectit-ocelot-configurationserver/build.gradle @@ -87,9 +87,9 @@ cyclonedxBom { repositories { mavenCentral() } - -sourceCompatibility = 1.8 - +//to guarantee that the Configuration Server is compatible with Java 8 runtime environments +sourceCompatibility = 1.8 // Java version compatibility to use when compiling Java source. +targetCompatibility = 1.8 // Java version to generate classes for. test { useJUnitPlatform() diff --git a/inspectit-ocelot-agent/build.gradle b/inspectit-ocelot-agent/build.gradle index 90f83bf47e..0e5acaf220 100644 --- a/inspectit-ocelot-agent/build.gradle +++ b/inspectit-ocelot-agent/build.gradle @@ -16,7 +16,10 @@ test { } group = 'rocks.inspectit.ocelot' -sourceCompatibility = 1.8 + +sourceCompatibility = 1.8 // Java version compatibility to use when compiling Java source. +targetCompatibility = 1.8 // Java version to generate classes for. + configurations { opentelemetry diff --git a/inspectit-ocelot-bootstrap/build.gradle b/inspectit-ocelot-bootstrap/build.gradle index 117a5aa110..a42e6a00d6 100644 --- a/inspectit-ocelot-bootstrap/build.gradle +++ b/inspectit-ocelot-bootstrap/build.gradle @@ -16,8 +16,8 @@ test { } } -sourceCompatibility = 1.8 - +sourceCompatibility = 1.8 // Java version compatibility to use when compiling Java source. +targetCompatibility = 1.8 // Java version to generate classes for. jar { archiveName = "${project.name}.jar" diff --git a/inspectit-ocelot-config/build.gradle b/inspectit-ocelot-config/build.gradle index 6c5a6e1d7a..9ae14d5169 100644 --- a/inspectit-ocelot-config/build.gradle +++ b/inspectit-ocelot-config/build.gradle @@ -15,8 +15,8 @@ repositories { } group = 'rocks.inspectit.ocelot' -sourceCompatibility = 1.8 - +sourceCompatibility = 1.8 // Java version compatibility to use when compiling Java source. +targetCompatibility = 1.8 // Java version to generate classes for. dependencies { compileOnly "org.projectlombok:lombok:${lombokVersion}" annotationProcessor "org.projectlombok:lombok:${lombokVersion}" diff --git a/inspectit-ocelot-config/src/main/java/rocks/inspectit/ocelot/config/model/selfmonitoring/AgentHealthSettings.java b/inspectit-ocelot-config/src/main/java/rocks/inspectit/ocelot/config/model/selfmonitoring/AgentHealthSettings.java index 783c461d36..3751782af1 100644 --- a/inspectit-ocelot-config/src/main/java/rocks/inspectit/ocelot/config/model/selfmonitoring/AgentHealthSettings.java +++ b/inspectit-ocelot-config/src/main/java/rocks/inspectit/ocelot/config/model/selfmonitoring/AgentHealthSettings.java @@ -5,6 +5,7 @@ import lombok.NonNull; import javax.validation.constraints.AssertFalse; +import javax.validation.constraints.AssertTrue; import java.time.Duration; /** @@ -21,6 +22,17 @@ public class AgentHealthSettings { @NonNull private Duration validityPeriod; + /** + * The minimum delay how often the AgentHealthManager checks for invalid agent health events to clear health status. + */ + @NonNull + private Duration minHealthCheckDelay; + + @AssertTrue(message = "minHealthCheckDelay must be at least 60 seconds") + public boolean isMin60SecondsDelay() { + return minHealthCheckDelay.toMinutes() >= 1; + } + @AssertFalse(message = "The specified period should not be negative!") public boolean isNegativeDuration() { return validityPeriod != null && validityPeriod.isNegative(); diff --git a/inspectit-ocelot-config/src/main/resources/rocks/inspectit/ocelot/config/default/self-monitoring.yml b/inspectit-ocelot-config/src/main/resources/rocks/inspectit/ocelot/config/default/self-monitoring.yml index 5912b02f20..05a914cfb9 100644 --- a/inspectit-ocelot-config/src/main/resources/rocks/inspectit/ocelot/config/default/self-monitoring.yml +++ b/inspectit-ocelot-config/src/main/resources/rocks/inspectit/ocelot/config/default/self-monitoring.yml @@ -15,6 +15,11 @@ inspectit: # health changes due to instrumentation errors are valid until the next re-instrumentation validity-period: 1h + # The minimum delay how often the AgentHealthManager checks for invalid agent health events to clear health status + # By default the delay is calculated based on the last agent health event + # Minimum value is 1m + min-health-check-delay: 1m + # the action tracing mode to use # options are: OFF, ONLY_ENABLED, ALL_WITHOUT_DEFAULT, ALL_WITH_DEFAULT action-tracing: ONLY_ENABLED diff --git a/inspectit-ocelot-core/build.gradle b/inspectit-ocelot-core/build.gradle index ae88965da9..07255f8f42 100644 --- a/inspectit-ocelot-core/build.gradle +++ b/inspectit-ocelot-core/build.gradle @@ -21,7 +21,10 @@ test { } } -sourceCompatibility = 1.8 + +sourceCompatibility = 1.8 // Java version compatibility to use when compiling Java source. +targetCompatibility = 1.8 // Java version to generate classes for. + dependencies { compileOnly( @@ -39,9 +42,6 @@ dependencies { "io.opentelemetry:opentelemetry-opencensus-shim", - ) - buildTools( - 'jarcheck:jarcheck:1.5' ) annotationProcessor "org.projectlombok:lombok:${lombokVersion}" @@ -159,9 +159,17 @@ dependencies { } +apply plugin: 'java' +task compileJarCheck(type: JavaCompile){ + source = sourceSets.main.java.srcDirs + include 'com/mindprod/jarcheck/JarCheck.java' + classpath = sourceSets.main.compileClasspath + destinationDir = new File("${buildDir}/classes/java/main") +} // use jarCheck to make sure all classes in our dependencies are at maximum in version 1.8 task checkDependencyJavaVersions { + def excludes = ["byte-buddy", // exclude OpenTelemetry as they guarantee JDK 8 support "opentelemetry", @@ -183,8 +191,8 @@ task checkDependencyJavaVersions { }) if (!isExcluded && file.exists()) { javaexec { - classpath configurations.buildTools - main = 'com.mindprod.jarcheck.JarCheck' + mainClass = 'com.mindprod.jarcheck.JarCheck' + classpath = sourceSets.main.runtimeClasspath args = ["$file", "1.0", "1.8"] standardOutput = new File(jarCheckOutput, "$name-check.log").newOutputStream() } @@ -192,6 +200,7 @@ task checkDependencyJavaVersions { } } } +checkDependencyJavaVersions.dependsOn compileJarCheck task generateVersionFile { ext.versionFile = new File(buildDir, "ocelot-version.info") diff --git a/inspectit-ocelot-core/src/main/java/com/mindprod/jarcheck/JarCheck.java b/inspectit-ocelot-core/src/main/java/com/mindprod/jarcheck/JarCheck.java new file mode 100644 index 0000000000..1ce2b8fe18 --- /dev/null +++ b/inspectit-ocelot-core/src/main/java/com/mindprod/jarcheck/JarCheck.java @@ -0,0 +1,222 @@ +package com.mindprod.jarcheck; + +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import static java.lang.System.*; +/* +TODO: check class minor version as well. + */ + +/** + * Ensures javac -target versions of the class files in a jar are as expected. + * + * @author Roedy Green, Canadian Mind Products + * @version 1.5 2014-03-23 add support for Java 1.8 + * @since 2006-01-16 + */ +public final class JarCheck +{ + /** + * how many bytes at beginning of class file we read
4=ca-fe-ba-be + 2=minor + 2=major + */ + private static final int chunkLength = 8; + + private static final int FIRST_COPYRIGHT_YEAR = 2006; + + /** + * undisplayed copyright notice + */ + private static final String EMBEDDED_COPYRIGHT = + "Copyright: (c) 2006-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; + + private static final String RELEASE_DATE = "2014-03-23"; + + /** + * embedded version string. + */ + private static final String VERSION_STRING = "1.5"; + + /** + * translate class file major version number to human JVM version + */ + private static final HashMap convertMachineToHuman = + new HashMap<>( 23 ); + + /** + * translate from human JDK version to class file major version number + */ + private static final HashMap convertHumanToMachine = + new HashMap<>( 23 ); + + /** + * expected first 4 bytes of a class file + */ + private static final byte[] expectedMagicNumber = + { ( byte ) 0xca, ( byte ) 0xfe, ( byte ) 0xba, ( byte ) 0xbe }; + + static + { + convertHumanToMachine.put( "1.0", 44 ); + convertHumanToMachine.put( "1.1", 45 ); + convertHumanToMachine.put( "1.2", 46 ); + convertHumanToMachine.put( "1.3", 47 ); + convertHumanToMachine.put( "1.4", 48 ); + convertHumanToMachine.put( "1.5", 49 ); + convertHumanToMachine.put( "1.6", 50 ); + convertHumanToMachine.put( "1.7", 51 ); + convertHumanToMachine.put( "1.8", 52 ); + } + + static + { + convertMachineToHuman.put( 44, "1.0" ); + convertMachineToHuman.put( 45, "1.1" ); + convertMachineToHuman.put( 46, "1.2" ); + convertMachineToHuman.put( 47, "1.3" ); + convertMachineToHuman.put( 48, "1.4" ); + convertMachineToHuman.put( 49, "1.5" ); + convertMachineToHuman.put( 50, "1.6" ); + convertMachineToHuman.put( 51, "1.7" ); + convertMachineToHuman.put( 52, "1.8" ); + } + + /** + * check one jar to make sure all class files have compatible versions. + * + * @param jarFilename name of jar file whose classes are to be tested. + * @param low low bound for major version e.g. 44 + * @param high high bound for major version. e.g. 50 + * + * @return true if all is ok. False if not, with long on System.err of problems. + */ + private static boolean checkJar( String jarFilename, int low, int high ) + { + out.println( "\nChecking jar " + jarFilename ); + boolean success = true; + FileInputStream fis; + ZipInputStream zip = null; + try + { + try + { + fis = new FileInputStream( jarFilename ); + zip = new ZipInputStream( fis ); + // loop for each jar entry + entryLoop: + while ( true ) + { + ZipEntry entry = zip.getNextEntry(); + if ( entry == null ) + { + break; + } + // relative name with slashes to separate dirnames. + String elementName = entry.getName(); + if ( !elementName.endsWith( ".class" ) ) + { + // ignore anything but a .final class file + continue; + } + byte[] chunk = new byte[ chunkLength ]; + int bytesRead = zip.read( chunk, 0, chunkLength ); + zip.closeEntry(); + if ( bytesRead != chunkLength ) + { + err.println( ">> Corrupt class file: " + + elementName ); + success = false; + continue; + } + // make sure magic number signature is as expected. + for ( int i = 0; i < expectedMagicNumber.length; i++ ) + { + if ( chunk[ i ] != expectedMagicNumber[ i ] ) + { + err.println( ">> Bad magic number in " + + elementName ); + success = false; + continue entryLoop; + } + } + /* + * pick out big-endian ushort major version in last two + * bytes of chunk + */ + int major = + ( ( chunk[ chunkLength - 2 ] & 0xff ) << 8 ) + ( + chunk[ chunkLength - 1 ] + & 0xff ); + /* F I N A L L Y. All this has been leading up to this TEST */ + if ( low <= major && major <= high ) + { + out.print( " OK " ); + out.println( convertMachineToHuman.get( major ) + + " (" + + major + + ") " + + elementName ); + // leave success set as previously + } + else + { + err.println( ">> Wrong Version " ); + err.println( convertMachineToHuman.get( major ) + + " (" + + major + + ") " + + elementName ); + success = false; + } + } + // end while + } + catch ( EOFException e ) + { + // normal exit + } + zip.close(); + return success; + } + catch ( IOException e ) + { + err.println( ">> Problem reading jar file." ); + return false; + } + } + + /** + * Main command line jarfileName lowVersion highVersion e.g. myjar.jar 1.0 1.8 + * + * @param args rot used + */ + public static void main( String[] args ) + { + try + { + if ( args.length != 3 ) + { + err.println( "usage: java -ea -jar jarcheck.jar jarFileName.jar 1.1 1.7" ); + System.exit( 2 ); + } + String jarFilename = args[ 0 ]; + int low = convertHumanToMachine.get( args[ 1 ] ); + int high = convertHumanToMachine.get( args[ 2 ] ); + boolean success = checkJar( jarFilename, low, high ); + if ( !success ) + { + err.println("JarCheck failed for " + jarFilename+". See error messages above."); + System.exit( 1 ); + } + } + catch ( NullPointerException e ) + { + err.println( "usage: java -ea -jar jarcheck.jar jarFileName.jar 1.1 1.8" ); + System.exit( 2 ); + } + } +} diff --git a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/selfmonitoring/AgentHealthManager.java b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/selfmonitoring/AgentHealthManager.java index ec4db1373b..4dff1da3d8 100644 --- a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/selfmonitoring/AgentHealthManager.java +++ b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/selfmonitoring/AgentHealthManager.java @@ -151,9 +151,18 @@ private void checkHealthAndSchedule() { .stream() .filter(d -> d.isAfter(LocalDateTime.now())) .max(Comparator.naturalOrder()) - .map(d -> Duration.between(d, LocalDateTime.now())) + .map(d -> Duration.between(d, LocalDateTime.now()).abs()) .orElse(validityPeriod); + delay = Duration.ofMillis(Math.max(delay.toMillis(), env.getCurrentConfig() + .getSelfMonitoring() + .getAgentHealth() + .getMinHealthCheckDelay() + .toMillis())); + if (log.isDebugEnabled()) { + log.debug("Schedule health check in {} minutes", delay.toMinutes()); + } + executor.schedule(this::checkHealthAndSchedule, delay.toMillis(), TimeUnit.MILLISECONDS); } diff --git a/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/selfmonitoring/AgentHealthManagerTest.java b/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/selfmonitoring/AgentHealthManagerTest.java index 1ade277b13..eaa0f731d6 100644 --- a/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/selfmonitoring/AgentHealthManagerTest.java +++ b/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/selfmonitoring/AgentHealthManagerTest.java @@ -50,6 +50,7 @@ static void createInspectitConfig() { config = new InspectitConfig(); AgentHealthSettings agentHealth = new AgentHealthSettings(); agentHealth.setValidityPeriod(Duration.ofMillis(VALIDITY_PERIOD_MILLIS)); + agentHealth.setMinHealthCheckDelay(Duration.ofMillis(0)); SelfMonitoringSettings selfMonitoring = new SelfMonitoringSettings(); selfMonitoring.setAgentHealth(agentHealth); config.setSelfMonitoring(selfMonitoring); @@ -57,6 +58,7 @@ static void createInspectitConfig() { @BeforeEach void setupStatusManager() { + executorService = new ScheduledThreadPoolExecutor(1); environment = mock(InspectitEnvironment.class); diff --git a/inspectit-ocelot-sdk/build.gradle b/inspectit-ocelot-sdk/build.gradle index f7186d0810..df29d91f2c 100644 --- a/inspectit-ocelot-sdk/build.gradle +++ b/inspectit-ocelot-sdk/build.gradle @@ -7,8 +7,8 @@ repositories { } group = 'rocks.inspectit.ocelot' -sourceCompatibility = 1.8 - +sourceCompatibility = 1.8 // Java version compatibility to use when compiling Java source. +targetCompatibility = 1.8 // Java version to generate classes for. dependencies { implementation project(':inspectit-ocelot-config') implementation 'org.slf4j:slf4j-api:1.7.25'