diff --git a/Jenkinsfile b/Jenkinsfile index af9dd867..0edee54a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,5 +4,4 @@ buildPlugin( configurations: [ [platform: 'linux', jdk: 11], [platform: 'linux', jdk: 17], - [platform: 'windows', jdk: 11], ]) diff --git a/backend-plugin/pom.xml b/backend-plugin/pom.xml index 9340fb7b..4810664a 100644 --- a/backend-plugin/pom.xml +++ b/backend-plugin/pom.xml @@ -19,6 +19,7 @@ org.jenkins-ci.plugins.nodesharing node-sharing-lib + ${project.version} io.jenkins @@ -40,4 +41,4 @@ - + \ No newline at end of file diff --git a/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/Api.java b/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/Api.java index 51e46fa0..c3afc661 100644 --- a/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/Api.java +++ b/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/Api.java @@ -85,7 +85,7 @@ public Api() { // PJ: Not working, during JUnit phase execution there aren't made packages... InputStream resource = this.getClass().getClassLoader().getResourceAsStream("nodesharingbackend.properties"); if (resource == null) { - version = Jenkins.getInstance().pluginManager.whichPlugin(getClass()).getVersion(); + version = Jenkins.get().pluginManager.whichPlugin(getClass()).getVersion(); } else { Properties properties = new Properties(); properties.load(resource); @@ -98,7 +98,7 @@ public Api() { } public static @Nonnull Api getInstance() { - ExtensionList list = Jenkins.getInstance().getExtensionList(Api.class); + ExtensionList list = Jenkins.get().getExtensionList(Api.class); assert list.size() == 1; return list.iterator().next(); } @@ -189,7 +189,7 @@ public NodeStatusResponse.Status nodeStatus(@Nonnull final ExecutorJenkins jenki */ @RequirePOST public void doDiscover(StaplerRequest req, StaplerResponse rsp) throws IOException { - Jenkins.getInstance().checkPermission(RestEndpoint.RESERVE); + Jenkins.get().checkPermission(RestEndpoint.RESERVE); Pool pool = Pool.getInstance(); Collection nodes = pool.getConfig().getNodes().values(); // Fail early when there is no config @@ -240,7 +240,7 @@ public void doDiscover(StaplerRequest req, StaplerResponse rsp) throws IOExcepti */ @RequirePOST public void doReportWorkload(@Nonnull final StaplerRequest req, @Nonnull final StaplerResponse rsp) throws IOException { - Jenkins.getInstance().checkPermission(RestEndpoint.RESERVE); + Jenkins.get().checkPermission(RestEndpoint.RESERVE); Pool pool = Pool.getInstance(); final ConfigRepo.Snapshot config = pool.getConfig(); // Fail early when there is no config @@ -264,7 +264,7 @@ public void doReportWorkload(@Nonnull final StaplerRequest req, @Nonnull final S Queue.withLock(new Runnable() { @Override public void run() { - Queue queue = Jenkins.getInstance().getQueue(); + Queue queue = Jenkins.get().getQueue(); for (Queue.Item item : queue.getItems()) { if (item.task instanceof ReservationTask && ((ReservationTask) item.task).getOwner().equals(executor)) { // Cancel items executor is no longer interested in and keep those it cares for @@ -297,7 +297,7 @@ private String unknownExecutor(String executorUrl, String configRepoUrl) { */ @RequirePOST public void doReturnNode(@Nonnull final StaplerRequest req, @Nonnull final StaplerResponse rsp) throws IOException { - Jenkins.getInstance().checkPermission(RestEndpoint.RESERVE); + Jenkins.get().checkPermission(RestEndpoint.RESERVE); String ocr = Pool.getInstance().getConfigRepoUrl(); // Fail early when there is no config ReturnNodeRequest request = Entity.fromInputStream(req.getInputStream(), ReturnNodeRequest.class); @@ -308,7 +308,7 @@ public void doReturnNode(@Nonnull final StaplerRequest req, @Nonnull final Stapl return; } - Jenkins jenkins = Jenkins.getInstance(); + Jenkins jenkins = Jenkins.get(); Computer c = jenkins.getComputer(request.getNodeName()); if (c == null) { LOGGER.info( diff --git a/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/Dashboard.java b/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/Dashboard.java index 65d02fdc..332c9e13 100644 --- a/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/Dashboard.java +++ b/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/Dashboard.java @@ -51,7 +51,7 @@ public class Dashboard extends View { // It is quite delicate when this is invoked to fit between primary view updates hardcoded in Jenkins class itself @Initializer(after = InitMilestone.JOB_LOADED) public static void registerDashboard() throws IOException { - Jenkins j = Jenkins.getInstance(); + Jenkins j = Jenkins.get(); Dashboard dashboard = new Dashboard(); j.addView(dashboard); j.setPrimaryView(dashboard); diff --git a/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/NoopChannel.java b/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/NoopChannel.java index cd7e601b..8ef6d9d3 100644 --- a/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/NoopChannel.java +++ b/backend-plugin/src/main/java/com/redhat/jenkins/nodesharingbackend/NoopChannel.java @@ -130,7 +130,7 @@ private NoopChannel(ChannelBuilder settings, CommandTransport transport) throws } @Override - public boolean preloadJar(Callable classLoaderRef, Class... classesInJar) { + public boolean preloadJar(Callable classLoaderRef, Class... classesInJar) { throw new UnsupportedOperationException(); // $COVERAGE-IGNORE$ } diff --git a/backend-plugin/src/main/resources/index.jelly b/backend-plugin/src/main/resources/index.jelly new file mode 100644 index 00000000..daffae7c --- /dev/null +++ b/backend-plugin/src/main/resources/index.jelly @@ -0,0 +1,4 @@ + +
+ Share machines as Jenkins agents across multiple Jenkins masters. This plugin maintains the nodes in the share pool of preconfigured machines. +
diff --git a/backend-plugin/src/test/java/com/redhat/jenkins/nodesharingbackend/JCasCCompatibilityTest.java b/backend-plugin/src/test/java/com/redhat/jenkins/nodesharingbackend/JCasCCompatibilityTest.java index e723a5d3..61217af2 100644 --- a/backend-plugin/src/test/java/com/redhat/jenkins/nodesharingbackend/JCasCCompatibilityTest.java +++ b/backend-plugin/src/test/java/com/redhat/jenkins/nodesharingbackend/JCasCCompatibilityTest.java @@ -9,7 +9,7 @@ import org.junit.Test; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; public class JCasCCompatibilityTest { diff --git a/jth-tests/pom.xml b/jth-tests/pom.xml index 706b927d..8719f48f 100644 --- a/jth-tests/pom.xml +++ b/jth-tests/pom.xml @@ -15,14 +15,17 @@ org.jenkins-ci.plugins.nodesharing node-sharing-executor + ${project.version} org.jenkins-ci.plugins.nodesharing node-sharing-orchestrator + ${project.version} org.jenkins-ci.plugins.nodesharing node-sharing-lib + ${project.version} @@ -30,10 +33,7 @@ commons-codec commons-codec
- - com.google.code.gson - gson - + com.offbytwo.jenkins jenkins-client @@ -50,22 +50,19 @@ org.mockito mockito-core - 1.9.5 + 5.1.1 org.hamcrest hamcrest-core - 2.1 org.jenkins-ci.plugins matrix-project - 1.12 org.jenkins-ci.plugins matrix-auth - 2.6.6 org.jenkins-ci.plugins @@ -74,29 +71,11 @@ io.jenkins configuration-as-code - 1.2 - - - io.jenkins.configuration-as-code - configuration-as-code-support - 1.2 org.jenkins-ci.plugins job-dsl - 1.66 - - - - - org.apache.httpcomponents - httpcore - 4.4.4 - - - org.codehaus.groovy - groovy-all - 2.4.8 + 1.81.1 @@ -106,7 +85,7 @@ org.apache.maven.plugins maven-deploy-plugin - 2.8.2 + 3.1.0 true diff --git a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/GridTest.java b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/GridTest.java index 0c5b16b3..855f6815 100644 --- a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/GridTest.java +++ b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/GridTest.java @@ -38,6 +38,9 @@ import com.redhat.jenkins.nodesharing.utils.GridRule.Orchestrator; import com.redhat.jenkins.nodesharing.utils.SlowTest; import hudson.FilePath; + +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -51,13 +54,14 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; +import java.util.regex.Pattern; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @Category(SlowTest.class) @@ -87,9 +91,10 @@ public void smoke() throws Exception { System.out.println('.'); verifyBuildHasRun(fixture, "sol", "win"); return; - } catch (AssertionError ex) { + } catch (AssertionError|org.apache.http.conn.HttpHostConnectException ex) { if (i == 8) { - TimeoutException tex = new TimeoutException("Build not completed in time"); + dumpFixtureLogs(); + TimeoutException tex = new TimeoutException("Build not completed in time by " + new Date()); tex.initCause(ex); throw tex; } @@ -118,9 +123,10 @@ public void smokeNoEnvCredential() throws Exception { System.out.println('.'); verifyBuildHasRun(fixture, "sol", "win"); return; - } catch (AssertionError ex) { + } catch (AssertionError|org.apache.http.conn.HttpHostConnectException ex) { if (i == 8) { - TimeoutException tex = new TimeoutException("Build not completed in time"); + dumpFixtureLogs(); + TimeoutException tex = new TimeoutException("Build not completed in time by " + new Date()); tex.initCause(ex); throw tex; } @@ -152,7 +158,7 @@ public void restartOrchestrator() throws Exception { // Run nr. 1 - will be in building state during Orchestrator restart FileBuildBlocker runningBlocker = new FileBuildBlocker(tmp); BuildWithDetails running = triggerJobAndWaitUntilStarted(executorClient, "running", - job.build(runningBlocker.buildParams())); + job.build(runningBlocker.buildParams(), true)); await(10000, () -> runningBlocker.isRunning(), throwable -> { dumpFixtureLogs(); return "Build not running in time"; @@ -160,7 +166,7 @@ public void restartOrchestrator() throws Exception { // Run nr. 2 - will be in queued state during Orchestrator restart FileBuildBlocker queuedBlocker = new FileBuildBlocker(tmp); - QueueReference qr = job.build(queuedBlocker.buildParams()); + QueueReference qr = job.build(queuedBlocker.buildParams(), true); executorClient.getQueueItem(qr); job = executorClient.getJob("running"); @@ -174,7 +180,7 @@ public void restartOrchestrator() throws Exception { JenkinsServer orchestratorClient = o.getClient("admin", "admin"); // Wait until restarted - orchestratorClient.restart(false); + orchestratorClient.restart(true); // Reservation verifier needs RestEndpoint#TIMEOUT * 2 to recover the state so this is going to take a while await(60000 * 3, orchestratorClient::isRunning, throwable -> { dumpFixtureLog(o); @@ -232,7 +238,7 @@ public void changeOrchestratorUrlSmokeTest() throws Exception { // Run nr. 1 - will be in building state during Orchestrator restart FileBuildBlocker runningBlocker = new FileBuildBlocker(tmp); BuildWithDetails running = triggerJobAndWaitUntilStarted(executorClient0, "running", - job.build(runningBlocker.buildParams())); + job.build(runningBlocker.buildParams(),true)); await(10000, () -> runningBlocker.isRunning(), throwable -> { dumpFixtureLogs(); return "Build not running in time"; @@ -240,7 +246,7 @@ public void changeOrchestratorUrlSmokeTest() throws Exception { // Run nr. 2 - will be in queued state during Orchestrator restart FileBuildBlocker queuedBlocker = new FileBuildBlocker(tmp); - QueueReference qr = job.build(queuedBlocker.buildParams()); + QueueReference qr = job.build(queuedBlocker.buildParams(), true); executorClient0.getQueueItem(qr); job = executorClient0.getJob("running"); @@ -264,19 +270,19 @@ public void changeOrchestratorUrlSmokeTest() throws Exception { assertThat(config.readToString(), containsString("orchestrator.url=" + o1.getUri())); // Make sure updated config is propagated through the grid - executorClient0.runScript("Jenkins.instance.getExtensionList(com.redhat.jenkins.nodesharingfrontend.SharedNodeCloud.ConfigRepoUpdater.class).get(0).doRun();"); + executorClient0.runScript("Jenkins.instance.getExtensionList(com.redhat.jenkins.nodesharingfrontend.SharedNodeCloud.ConfigRepoUpdater.class).get(0).doRun();", true); assertThat(executorClient0.runScript( - "com.redhat.jenkins.nodesharingfrontend.SharedNodeCloud.getAll().get(0).getLatestConfig().getOrchestratorUrl();"), + "com.redhat.jenkins.nodesharingfrontend.SharedNodeCloud.getAll().get(0).getLatestConfig().getOrchestratorUrl();", true), equalTo("Result: " + o1.getUri() + "\n") ); - orchestratorClient1.runScript("com.redhat.jenkins.nodesharingbackend.Pool.Updater.getInstance().doRun();"); + orchestratorClient1.runScript("com.redhat.jenkins.nodesharingbackend.Pool.Updater.getInstance().doRun();", true); assertThat(orchestratorClient1.runScript( - "com.redhat.jenkins.nodesharingbackend.Pool.getInstance().getConfig().getOrchestratorUrl();"), + "com.redhat.jenkins.nodesharingbackend.Pool.getInstance().getConfig().getOrchestratorUrl();", true), equalTo("Result: " + o1.getUri() + "\n") ); - orchestratorClient2.runScript("com.redhat.jenkins.nodesharingbackend.Pool.Updater.getInstance().doRun();"); + orchestratorClient2.runScript("com.redhat.jenkins.nodesharingbackend.Pool.Updater.getInstance().doRun();", true); assertThat(orchestratorClient2.runScript( - "com.redhat.jenkins.nodesharingbackend.Pool.getInstance().getConfig().getOrchestratorUrl();"), + "com.redhat.jenkins.nodesharingbackend.Pool.getInstance().getConfig().getOrchestratorUrl();", true), equalTo("Result: " + o1.getUri() + "\n") ); @@ -312,34 +318,34 @@ public void changeOrchestratorUrlSmokeTest() throws Exception { Thread.sleep(1000); String e0Log = e0.getLog().readToString(); + // Executor should terminate computer twice - assertThat(e0Log, matchesPattern( - "(.+\n)+" - + "INFO: Terminating computer solaris1.acme.com-NodeSharing-.+(.+\n)*" - + "INFO: Terminating computer solaris1.acme.com-NodeSharing-.+(.+\n)*") - ); + String msg = "Terminating computer solaris1.acme.com-NodeSharing-"; + assertThat(e0Log, Matchers.stringContainsInOrder(msg, msg)); String o1Log = o1.getLog().readToString(); // Orchestrator 1 should register one release attempt before it knows that it was reserved, // second reservation should be processed like common case - assertThat(o1Log, matchesPattern( - "(.*\n)*INFO: An attempt to return a node 'solaris1.acme.com' that is not reserved by " + e0.getUri()+"(.*\n)*" - +"INFO: Reservation of solaris1.acme.com by e0 .+ completed(.*\n)*") - ); + String regex = "An attempt to return a node 'solaris1.acme.com' that is not reserved by " + + e0.getUri(); + assertThat(o1Log.split(regex)[1], matchesPattern("((\\n(.*))+)Reservation of solaris1.acme.com by e0 (.*) completed\\n")); String o2Log = o2.getLog().readToString(); + String o2LogRegex = o2Log.split("Jenkins is fully up and running")[1]; + Pattern p = Pattern.compile("(?i)Reservation of solaris1.acme.com by e0 (.*) started", Pattern.DOTALL); + java.util.regex.Matcher m = p.matcher(o2LogRegex); + boolean b = m.find(); + // Orchestrator 2 should register one reservation without release - assertThat(o2Log, matchesPattern( - "(.*\n)*INFO: Reservation of solaris1.acme.com by e0 .+ started(.*\n)*") - ); + assertTrue(b); // Orchestrator 2 shouldn't register two reservations - assertThat(o2Log, matchesPattern( - "(.*\n)*(?!INFO: Reservation of solaris1.acme.com by e0 .+ started(.*\n)*INFO: Reservation of solaris1.acme.com by e0 .+ started)(.*\n)*") - ); + b = m.find(); + org.junit.Assert.assertFalse(b); // Orchestrator 2 shouldn't register any release attempt - assertThat(o2Log, matchesPattern( - "(.*\n)*(?!INFO: Reservation of solaris1.acme.com by e0 .+ completed)(.*\n)*") - ); + p = Pattern.compile("(?i)Reservation of solaris1.acme.com by e0 (.*) completed", Pattern.DOTALL); + m = p.matcher(o2LogRegex); + b = m.find(); + org.junit.Assert.assertFalse(b); } private BuildWithDetails buildDetails(JobWithDetails running, int i) throws IOException { diff --git a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/PoolTest.java b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/PoolTest.java index 43e8c210..dbf2920b 100644 --- a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/PoolTest.java +++ b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/PoolTest.java @@ -54,7 +54,6 @@ import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; -import org.mockito.internal.util.reflection.Whitebox; import javax.servlet.http.HttpServletResponse; import java.io.File; @@ -112,7 +111,14 @@ public void inactiveWithNoProperty() throws Exception { private void eraseLoadConfig() throws IOException { // There is meaningful value set from startup - erase it - Whitebox.setInternalState(Pool.getInstance(), "config", null); + try { + org.apache.commons.lang3.reflect.FieldUtils.writeField(Pool.getInstance(), "config", null, true); + } catch (IllegalArgumentException ex) { + //setInternalState(Pool.getInstance(), "config", null); + } + catch (IllegalAccessException ex) { + //setInternalState(Pool.getInstance(), "config", null); + } j.jenkins.setNodes(Collections.emptyList()); } diff --git a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/ReservationTest.java b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/ReservationTest.java index 1b3c71a5..c833d484 100644 --- a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/ReservationTest.java +++ b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/ReservationTest.java @@ -200,7 +200,7 @@ public void reflectChangesInWorkloadReported() throws Exception { List scheduledReservations = j.getQueuedReservations(); assertThat(scheduledReservations, Matchers.iterableWithSize(2)); - Queue.Item[] items = Jenkins.getInstance().getQueue().getItems(); + Queue.Item[] items = Jenkins.get().getQueue().getItems(); assertThat(items, arrayWithSize(4)); // Executor items assertEquals("remove", items[3].task.getName()); @@ -219,7 +219,7 @@ public void reflectChangesInWorkloadReported() throws Exception { List scheduledReservations = j.getQueuedReservations(); assertThat(scheduledReservations, Matchers.iterableWithSize(2)); - Queue.Item[] items = Jenkins.getInstance().getQueue().getItems(); + Queue.Item[] items = Jenkins.get().getQueue().getItems(); assertThat(items, arrayWithSize(4)); assertEquals("keep", items[3].task.getName()); @@ -232,7 +232,7 @@ public void reflectChangesInWorkloadReported() throws Exception { public void buildWithNoLabelShouldNotBeBuilt() throws Exception { j.singleJvmGrid(j.jenkins); SharedNodeCloud cloud = j.addSharedNodeCloud(Pool.getInstance().getConfigRepoUrl()); - assertFalse(cloud.canProvision(null)); + assertFalse(cloud.canProvision((Label) null)); j.jenkins.setNumExecutors(0); FreeStyleProject p = j.createFreeStyleProject(); diff --git a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/ReservationVerifierTest.java b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/ReservationVerifierTest.java index e137df33..1e0d3257 100644 --- a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/ReservationVerifierTest.java +++ b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/ReservationVerifierTest.java @@ -53,7 +53,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/SharedNodeCloudTest.java b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/SharedNodeCloudTest.java index a24c6dc0..5201973d 100644 --- a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/SharedNodeCloudTest.java +++ b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/SharedNodeCloudTest.java @@ -49,6 +49,8 @@ import hudson.slaves.OfflineCause; import hudson.util.FormValidation; import org.jenkinsci.plugins.gitclient.GitClient; +import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval; +import org.jenkinsci.plugins.scriptsecurity.scripts.languages.SystemCommandLanguage; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.LoggerRule; @@ -300,9 +302,12 @@ public void nodeStatusTestOffline() throws Exception { public void nodeStatusTestConnecting() throws Exception { final GitClient gitClient = j.singleJvmGrid(j.jenkins); final SharedNodeCloud cloud = j.addSharedNodeCloud(gitClient.getWorkTree().getRemote()); - + + String command = ((SimpleCommandLauncher) j.createComputerLauncher(null)).cmd; + + ScriptApproval.get().preapprove(command, SystemCommandLanguage.get()); NodeSharingJenkinsRule.BlockingCommandLauncher blockingLauncher = new NodeSharingJenkinsRule.BlockingCommandLauncher( - ((SimpleCommandLauncher) j.createComputerLauncher(null)).cmd + command ); SharedNode connectingSlave = cloud.createNode(cloud.getLatestConfig().getNodes().get("solaris2.acme.com")); @@ -310,6 +315,7 @@ public void nodeStatusTestConnecting() throws Exception { connectingSlave.setNodeName(cloud.getNodeName("aConnectingNode")); j.jenkins.addNode(connectingSlave); assertTrue(connectingSlave.toComputer().isConnecting()); + blockingLauncher.start.block(); checkNodeStatus(cloud, "aConnectingNode", NodeStatusResponse.Status.CONNECTING); diff --git a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/utils/ExternalJenkinsRule.java b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/utils/ExternalJenkinsRule.java index 673f6a8b..0c26a46d 100644 --- a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/utils/ExternalJenkinsRule.java +++ b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/utils/ExternalJenkinsRule.java @@ -53,21 +53,15 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; +import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.jar.JarInputStream; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.regex.Pattern; /** @@ -76,6 +70,9 @@ * The rule can be subclassed to add custom methods or override callback methods to customize fixture deployment. */ public class ExternalJenkinsRule implements TestRule { + + static final Logger LOGGER = Logger.getLogger(ExternalJenkinsRule.class.getName()); + protected final TemporaryFolder tmp; private Map> fixtures = Collections.emptyMap(); @@ -141,9 +138,10 @@ public boolean hasRole(ExternalFixture fixture, Class future : fixtures.values()) { Fixture f = future.get(); - System.out.println(f.getAnnotation().name() + " is running at " + f.getUri() + " logging to " + f.getLog().getRemote()); + LOGGER.info(f.getAnnotation().name() + " is running at " + f.getUri() + " logging to " + f.getLog().getRemote()); } new BufferedReader(new InputStreamReader(System.in)).readLine(); } catch (IOException | InterruptedException | ExecutionException e) { @@ -370,7 +368,7 @@ private void injectJcascDefinition(ExternalFixture fixture, FilePath jenkinsHome private void installPlugins(ExternalFixture fixture, FilePath jenkinsHome) throws IOException, InterruptedException { Set injectPlugins = new HashSet<>(); - injectPlugins.addAll(Arrays.asList("configuration-as-code", "configuration-as-code-support")); + injectPlugins.addAll(Arrays.asList("configuration-as-code")); injectPlugins = initialPlugins(injectPlugins, fixture); injectPlugins.addAll(Arrays.asList(fixture.injectPlugins())); @@ -501,7 +499,7 @@ public void close() { client.close(); } catch (Exception e) { // Resume the loop so the remaining connections are cleaned too - e.printStackTrace(); + LOGGER.log(Level.WARNING, "Thrown while closing JenkinsServer " + client, e); } } } @@ -512,15 +510,21 @@ private void waitUntilReady(int seconds) throws InterruptedException, TimeoutExc JenkinsServer client = getClient(); for (int i = 0; i < seconds; i++) { if (!process.isAlive()) { - throw new AssertionError("Jenkins " + annotation.name() + " has failed with " + process.exitValue()); + throw new AssertionError(String.format("Jenkins %s has failed with %d", annotation.name(), process.exitValue())); } if (client.isRunning()) { - ready = true; - return; + try { + client.getJobs(); + ready = true; + LOGGER.info("Fixture " + this.annotation.name() + " ready"); + return; + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Thrown while waiting for fixture to be ready", e); + } } Thread.sleep(1000); } - throw new TimeoutException("Fixture " + annotation.name() + " not ready in " + seconds + " seconds"); + throw new TimeoutException(String.format("Fixture %s not ready in %d seconds (%s)", annotation.name(), seconds, new Date())); } } diff --git a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/utils/TestUtils.java b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/utils/TestUtils.java index e52e102f..d5729d9c 100644 --- a/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/utils/TestUtils.java +++ b/jth-tests/src/test/java/com/redhat/jenkins/nodesharing/utils/TestUtils.java @@ -23,16 +23,11 @@ */ package com.redhat.jenkins.nodesharing.utils; -import com.redhat.jenkins.nodesharing.NodeDefinition; -import com.redhat.jenkins.nodesharingfrontend.SharedNode; -import com.redhat.jenkins.nodesharingfrontend.SharedNodeFactory; import hudson.EnvVars; import hudson.FilePath; import hudson.Util; import hudson.remoting.Which; -import hudson.slaves.CommandLauncher; import hudson.util.StreamTaskListener; -import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; import org.jenkinsci.plugins.gitclient.Git; import org.jenkinsci.plugins.gitclient.GitClient; @@ -41,11 +36,11 @@ import javax.annotation.Nonnull; import java.io.File; import java.io.IOException; -import java.io.OutputStream; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.Map; +import java.util.regex.Pattern; public class TestUtils { @@ -64,6 +59,7 @@ public static GitClient createConfigRepo() throws URISyntaxException, IOExceptio EnvVars env = new EnvVars("GIT_AUTHOR_NAME", name, "GIT_AUTHOR_EMAIL", mail, "GIT_COMMITTER_NAME", name, "GIT_COMMITTER_EMAIL", mail); GitClient git = Git.with(listener, env).in(repo).using("git").getClient(); git.init(); + git.checkout().branch("master").execute(); // Enforce branch name so this is not impacted by git config of `init.defaultBranch` git.add("*"); git.commit("Init"); return git; @@ -118,17 +114,16 @@ public static void declareExecutor(GitClient git, String name, String url) throw git.commit("Add Jenkins"); } - // Make the nodes launchable by turning the xml to node, replacing for local launcher and turning it back to xml again + // Make the nodes launchable by replacing for local launcher using default java and jar public static void makeNodesLaunchable(GitClient git) throws IOException, InterruptedException { final File slaveJar = Which.jarFile(hudson.remoting.Launcher.class).getAbsoluteFile(); - for (FilePath xmlNode : git.getWorkTree().child("nodes").list("*.xml")) { - SharedNode node = new SharedNodeFactory.XStreamFactory().create(NodeDefinition.Xml.create(xmlNode)); - node.setLauncher(new CommandLauncher( - System.getProperty("java.home") + "/bin/java -jar " + slaveJar - )); - try (OutputStream out = xmlNode.write()) { - Jenkins.XSTREAM2.toXMLUTF8(node, out); - } + Pattern pattern = Pattern.compile("", Pattern.DOTALL); + for (FilePath file : git.getWorkTree().child("nodes").list("*.xml")) { + String command = System.getProperty("java.home") + "/bin/java -jar " + slaveJar; + String launcherTag = "" + command + ""; + + String xml = pattern.matcher(file.readToString()).replaceAll(launcherTag); + file.write(xml, "UTF-8"); } git.add("nodes"); git.commit("Making nodes in config repo launchable"); diff --git a/jth-tests/src/test/resources/com/redhat/jenkins/nodesharing/dummy_config_repo/nodes/solaris1.acme.com.xml b/jth-tests/src/test/resources/com/redhat/jenkins/nodesharing/dummy_config_repo/nodes/solaris1.acme.com.xml index 9c4fb4d9..b2c5f863 100644 --- a/jth-tests/src/test/resources/com/redhat/jenkins/nodesharing/dummy_config_repo/nodes/solaris1.acme.com.xml +++ b/jth-tests/src/test/resources/com/redhat/jenkins/nodesharing/dummy_config_repo/nodes/solaris1.acme.com.xml @@ -2,7 +2,7 @@ solaris1.acme.com ./var/jenkins-workspace 1 - + 22 diff --git a/nodesharing-lib/pom.xml b/nodesharing-lib/pom.xml index f5326e42..e0366fa2 100644 --- a/nodesharing-lib/pom.xml +++ b/nodesharing-lib/pom.xml @@ -17,10 +17,6 @@ commons-codec commons-codec - - com.google.code.gson - gson - @@ -38,4 +34,4 @@ - + \ No newline at end of file diff --git a/nodesharing-lib/src/test/java/com/redhat/jenkins/nodesharing/transport/ReportWorkloadTest.java b/nodesharing-lib/src/test/java/com/redhat/jenkins/nodesharing/transport/ReportWorkloadTest.java index 32fab6bf..359e2751 100644 --- a/nodesharing-lib/src/test/java/com/redhat/jenkins/nodesharing/transport/ReportWorkloadTest.java +++ b/nodesharing-lib/src/test/java/com/redhat/jenkins/nodesharing/transport/ReportWorkloadTest.java @@ -6,8 +6,8 @@ import java.util.List; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; public class ReportWorkloadTest { diff --git a/plugin/pom.xml b/plugin/pom.xml index 9ccc5504..fb1d1b86 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -21,10 +21,12 @@ org.jenkins-ci.plugins cloud-stats + 267.v577e3742c282 org.jenkins-ci.plugins.nodesharing node-sharing-lib + ${project.version} org.jenkins-ci.plugins @@ -44,6 +46,7 @@ org.jenkins-ci annotation-indexer + 1.17 org.jenkins-ci.plugins @@ -59,10 +62,7 @@ commons-codec commons-codec - - com.google.code.gson - gson - + @@ -73,4 +73,4 @@ - + \ No newline at end of file diff --git a/plugin/src/main/java/com/redhat/jenkins/nodesharingfrontend/SharedNodeFactory.java b/plugin/src/main/java/com/redhat/jenkins/nodesharingfrontend/SharedNodeFactory.java index 374b655c..94558d24 100644 --- a/plugin/src/main/java/com/redhat/jenkins/nodesharingfrontend/SharedNodeFactory.java +++ b/plugin/src/main/java/com/redhat/jenkins/nodesharingfrontend/SharedNodeFactory.java @@ -28,7 +28,11 @@ import hudson.ExtensionList; import hudson.ExtensionPoint; import hudson.model.Node; +import hudson.slaves.CommandLauncher; +import hudson.slaves.ComputerLauncher; import jenkins.model.Jenkins; +import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval; +import org.jenkinsci.plugins.scriptsecurity.scripts.languages.SystemCommandLanguage; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -55,6 +59,14 @@ public static SharedNode transform(@Nonnull NodeDefinition def) throws IllegalAr private static SharedNode decorate(@Nonnull SharedNode node) throws IllegalArgumentException { node.setRetentionStrategy(new SharedOnceRetentionStrategy(1)); node.setMode(Node.Mode.EXCLUSIVE); + + // Approve command launcher's script. It is script from orchestrator, that we trust. + ComputerLauncher launcher = node.getLauncher(); + if (launcher instanceof CommandLauncher) { + CommandLauncher cl = (CommandLauncher) launcher; + ScriptApproval.get().preapprove(cl.getCommand(), SystemCommandLanguage.get()); + } + if (node.getNumExecutors() != 1) { throw new IllegalArgumentException("Shared Nodes must have exactly 1 executor"); } diff --git a/plugin/src/main/java/com/redhat/jenkins/nodesharingfrontend/SharedOnceRetentionStrategy.java b/plugin/src/main/java/com/redhat/jenkins/nodesharingfrontend/SharedOnceRetentionStrategy.java index e4070c04..06e0b103 100644 --- a/plugin/src/main/java/com/redhat/jenkins/nodesharingfrontend/SharedOnceRetentionStrategy.java +++ b/plugin/src/main/java/com/redhat/jenkins/nodesharingfrontend/SharedOnceRetentionStrategy.java @@ -13,8 +13,8 @@ import hudson.slaves.AbstractCloudSlave; import hudson.slaves.CloudRetentionStrategy; import hudson.slaves.OfflineCause; -import hudson.util.TimeUnit2; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -43,7 +43,7 @@ public long check(final AbstractCloudComputer c) { // terminate. If it's not already trying to terminate then lets terminate manually. if (c.isIdle() && !disabled) { final long idleMilliseconds = System.currentTimeMillis() - c.getIdleStartMilliseconds(); - if (idleMilliseconds > TimeUnit2.MINUTES.toMillis(idleMinutes)) { + if (idleMilliseconds > TimeUnit.MINUTES.toMillis(idleMinutes)) { LOGGER.log(Level.INFO, "Disconnecting {0}", c.getName()); done(c); } diff --git a/plugin/src/main/resources/index.jelly b/plugin/src/main/resources/index.jelly index db9975f5..9a3850fb 100644 --- a/plugin/src/main/resources/index.jelly +++ b/plugin/src/main/resources/index.jelly @@ -3,5 +3,5 @@ This view is used to render the installed plugins page. -->
- This plugin consumes nodes shared by pool of preconfigured machines. + Share machines as Jenkins agents across multiple Jenkins masters. This plugin consumes nodes from the share pool of preconfigured machines.
diff --git a/pom.xml b/pom.xml index eb5fd2d1..3ea9472b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,9 +1,22 @@ 4.0.0 + + + + + io.jenkins.tools.bom + bom-2.375.x + 1841.v7b_22c5218e1a + import + pom + + + + org.jenkins-ci.plugins plugin - 3.33 + 4.54 org.jenkins-ci.plugins.nodesharing @@ -41,138 +54,113 @@ - - - - - - org.jenkins-ci.plugins.nodesharing - node-sharing-executor - ${project.version} - - - org.jenkins-ci.plugins.nodesharing - node-sharing-lib - ${project.version} - - - org.jenkins-ci.plugins.nodesharing - node-sharing-orchestrator - ${project.version} - - - - - org.jenkins-ci - annotation-indexer - 1.11 - - - org.jenkins-ci - symbol-annotation - 1.7 - - - org.jenkins-ci.main - jenkins-test-harness - 2.56 - - - org.jenkins-ci.modules - instance-identity - 2.1 - - - org.jenkins-ci.modules - ssh-cli-auth - 1.4 - - - org.jenkins-ci.plugins - cloud-stats - 0.20 - - - org.jenkins-ci.plugins - credentials - 2.1.16 - - - org.jenkins-ci.plugins - resource-disposer - 0.12 - - - org.jenkins-ci.plugins - script-security - 1229.v4880b_b_e905a_6 - - - org.jenkins-ci.plugins - ssh-credentials - 1.13 - - - org.jenkins-ci.plugins - ssh-slaves - 1.10 - - - org.jenkins-ci.plugins - ws-cleanup - 0.36 - - - org.jenkins-ci.plugins - structs - 1.7 - - - - - io.jenkins - configuration-as-code - ${configuration-as-code.version} - - - io.jenkins.configuration-as-code - test-harness - ${configuration-as-code.version} - - - - - com.google.code.gson - gson - 2.8.0 - - - commons-codec - commons-codec - 1.11 - - - org.apache.httpcomponents - httpclient - 4.5.2 - - - - + + + + org.jenkins-ci + annotation-indexer + 1.17 + + + org.jenkins-ci.main + jenkins-test-harness + 1945.v0efff5a_03b_78 + + + org.jenkins-ci.modules + instance-identity + + + org.jenkins-ci.modules + ssh-cli-auth + 1.8 + + + org.jenkins-ci.plugins + cloud-stats + 267.v577e3742c282 + + + org.jenkins-ci.plugins + credentials + + + org.jenkins-ci.plugins + resource-disposer + + + org.jenkins-ci.plugins + script-security + + + org.jenkins-ci.plugins + ssh-credentials + + + org.jenkins-ci.plugins + ssh-slaves + + + org.jenkins-ci.plugins + ws-cleanup + + + org.jenkins-ci.plugins + structs + org.jenkins-ci.plugins git-client - 1.17.1 + + + org.jenkins-ci.plugins + command-launcher - - - org.jenkins-ci.modules - sshd - 2.0 + + + io.jenkins + configuration-as-code test + + io.jenkins.configuration-as-code + test-harness + test + + + + + com.google.code.gson + gson + 2.10.1 + + + commons-codec + commons-codec + + + org.apache.httpcomponents + httpclient + 4.5.13 + + + org.apache.httpcomponents + httpcore + 4.4.15 + + + org.json + json + 20220320 + + + org.slf4j + slf4j-api + 2.0.5 + + @@ -204,17 +192,14 @@ scm:git:git://github.com/jenkinsci/node-sharing-plugin.git scm:git:git@github.com:jenkinsci/node-sharing-plugin.git https://github.com/jenkinsci/node-sharing-plugin - parent-1.2.6 + node-sharing-parent-2.0.8 - 2.60.3 - 8 - 2.40 - UTF-8 + 2.375.3 + 11 + 0.5C false false - 0.5C - 1.36 - + \ No newline at end of file