Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix pod template ID inconsistency over master reboots #988

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public class PodTemplateStep extends Step implements Serializable {
@CheckForNull
private String name;

private String id;

@CheckForNull
private String namespace;

Expand Down Expand Up @@ -118,11 +120,20 @@ public void setLabel(@CheckForNull String label) {
this.label = Util.fixEmpty(label);
}

@DataBoundSetter
public void setId(String id) {
this.id = Util.fixEmpty(id);
}

@CheckForNull
public String getName() {
return name;
}

public String getId() {
return id;
}

@DataBoundSetter
public void setName(@CheckForNull String name) {
this.name = Util.fixEmpty(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public boolean start() throws Exception {
String name = String.format(NAME_FORMAT, stepName, randString);
String namespace = checkNamespace(cloud, podTemplateContext);

newTemplate = new PodTemplate();
newTemplate = new PodTemplate(step.getId());
newTemplate.setName(name);
newTemplate.setNamespace(namespace);

Expand Down Expand Up @@ -115,7 +115,7 @@ public boolean start() throws Exception {
newTemplate.setAnnotations(step.getAnnotations());
newTemplate.setListener(getContext().get(TaskListener.class));
newTemplate.setYamlMergeStrategy(step.getYamlMergeStrategy());
if(run!=null) {
if(run != null) {
String url = cloud.getJenkinsUrlOrNull();
if(url != null) {
newTemplate.getAnnotations().add(new PodAnnotation("buildUrl", url + run.getUrl()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* The MIT License
*
* Copyright (c) 2017, Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.csanchez.jenkins.plugins.kubernetes.pipeline;

import static java.util.Arrays.asList;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.setupHost;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.setupCloud;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.createSecret;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.createPipelineJobThenScheduleRun;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.assumeWindows;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.assumeKubernetes;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.getLabels;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.deletePods;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.compress.utils.IOUtils;
import org.csanchez.jenkins.plugins.kubernetes.ContainerEnvVar;
import org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate;
import org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud;
import org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave;
import org.csanchez.jenkins.plugins.kubernetes.PodTemplate;
import org.csanchez.jenkins.plugins.kubernetes.model.KeyValueEnvVar;
import org.csanchez.jenkins.plugins.kubernetes.model.SecretEnvVar;
import org.csanchez.jenkins.plugins.kubernetes.model.TemplateEnvVar;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.support.steps.ExecutorStepExecution;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestName;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.LoggerRule;
import org.jvnet.hudson.test.RestartableJenkinsNonLocalhostRule;

import hudson.model.Node;
import hudson.model.Result;
import hudson.slaves.DumbSlave;
import hudson.slaves.JNLPLauncher;
import hudson.slaves.NodeProperty;
import hudson.slaves.RetentionStrategy;

public class RestartPipelinePodTemplateIdConsistencyTest {
protected static final String CONTAINER_ENV_VAR_VALUE = "container-env-var-value";
protected static final String POD_ENV_VAR_VALUE = "pod-env-var-value";
protected static final String SECRET_KEY = "password";
protected static final String CONTAINER_ENV_VAR_FROM_SECRET_VALUE = "container-pa55w0rd";
protected static final String POD_ENV_VAR_FROM_SECRET_VALUE = "pod-pa55w0rd";
protected KubernetesCloud cloud;

@Rule
public RestartableJenkinsNonLocalhostRule story = new RestartableJenkinsNonLocalhostRule(44000);
@Rule
public TemporaryFolder tmp = new TemporaryFolder();

@ClassRule
public static BuildWatcher buildWatcher = new BuildWatcher();

@Rule
public LoggerRule logs = new LoggerRule().record(Logger.getLogger(KubernetesCloud.class.getPackage().getName()),
Level.ALL);
//.record("org.jenkinsci.plugins.durabletask", Level.ALL).record("org.jenkinsci.plugins.workflow.support.concurrent", Level.ALL).record("org.csanchez.jenkins.plugins.kubernetes.pipeline", Level.ALL);

@Rule
public TestName name = new TestName();

@BeforeClass
public static void isKubernetesConfigured() throws Exception {
assumeKubernetes();
}

private static void setEnvVariables(PodTemplate podTemplate) {
TemplateEnvVar podSecretEnvVar = new SecretEnvVar("POD_ENV_VAR_FROM_SECRET", "pod-secret", SECRET_KEY, false);
TemplateEnvVar podSimpleEnvVar = new KeyValueEnvVar("POD_ENV_VAR", POD_ENV_VAR_VALUE);
podTemplate.setEnvVars(asList(podSecretEnvVar, podSimpleEnvVar));
TemplateEnvVar containerEnvVariable = new KeyValueEnvVar("CONTAINER_ENV_VAR", CONTAINER_ENV_VAR_VALUE);
TemplateEnvVar containerEnvVariableLegacy = new ContainerEnvVar("CONTAINER_ENV_VAR_LEGACY",
CONTAINER_ENV_VAR_VALUE);
TemplateEnvVar containerSecretEnvVariable = new SecretEnvVar("CONTAINER_ENV_VAR_FROM_SECRET",
"container-secret", SECRET_KEY, false);
podTemplate.getContainers().get(0)
.setEnvVars(asList(containerEnvVariable, containerEnvVariableLegacy, containerSecretEnvVariable));
}

public void configureCloud() throws Exception {
cloud = setupCloud(this, name);
createSecret(cloud.connect(), cloud.getNamespace());
cloud.getTemplates().clear();

setupHost();

story.j.jenkins.clouds.add(cloud);
}

public void configureAgentListener() throws IOException {
//Take random port and fix it, to be the same after Jenkins restart
int fixedPort = story.j.jenkins.getTcpSlaveAgentListener().getAdvertisedPort();
story.j.jenkins.setSlaveAgentPort(fixedPort);
}

protected String loadPipelineScript(String name) {
try {
return new String(IOUtils.toByteArray(getClass().getResourceAsStream(name)));
} catch (Throwable t) {
throw new RuntimeException("Could not read resource:[" + name + "].");
}
}

@Test
public void dynamicPodTemplateStepSupportsRestart() {
story.then(r -> {
// Do this only once:
configureAgentListener();
configureCloud();
r.jenkins.setNumExecutors(0);
// Do this stuff here and then again in a new story.then() call to simulate restart
WorkflowRun b = getPipelineJobThenScheduleRun(r);
r.waitForMessage("End of Pipeline", b);
r.assertBuildStatus(Result.SUCCESS, r.waitForCompletion(b));
});
story.then(r -> {
// Do what we do in the first one again, except the configureCloud and before bits
WorkflowRun b = getPipelineJobThenScheduleRun(r);
r.waitForMessage("End of Pipeline", b);
r.assertBuildStatus(Result.SUCCESS, r.waitForCompletion(b));
});
}

private PodTemplate waitForTemplate(KubernetesSlave node) throws InterruptedException {
while (node.getTemplateOrNull() == null) {
Thread.sleep(100L);
}
return node.getTemplate();
}

private WorkflowRun getPipelineJobThenScheduleRun(JenkinsRule r) throws InterruptedException, ExecutionException, IOException {
return createPipelineJobThenScheduleRun(r, getClass(), name.getMethodName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@

package org.csanchez.jenkins.plugins.kubernetes.pipeline;

import static java.util.Arrays.*;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.*;
import static java.util.Arrays.asList;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.setupHost;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.setupCloud;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.createSecret;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.createPipelineJobThenScheduleRun;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.assumeWindows;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.assumeKubernetes;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.getLabels;
import static org.csanchez.jenkins.plugins.kubernetes.KubernetesTestUtil.deletePods;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
def call(body) {
def config = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config
milestone()
def yamlPod = '''
apiVersion: v1
kind: Pod
metadata:
labels:
jenkins: agent
spec:
containers:
- name: maven-image
command:
- cat
image: busybox
imagePullPolicy: IfNotPresent
resources:
limits:
cpu: 100m
memory: 8Mi
requests:
cpu: 100m
memory: 8Mi
tty: true
dnsPolicy: ClusterFirst
restartPolicy: Never
securityContext: {}
terminationGracePeriodSeconds: 30
'''
def yamlPodHash = '#{project.artifactId}-#{project.version}-maven-image'

// one build per job to ensure tag consistency
try {
lock(resource: env.JOB_NAME.split('/')[1], inversePrecedence: true) {
milestone()
podTemplate(label: yamlPodHash, instanceCap: 6, idleMinutes: 60, cloud: 'dynamicPodTemplateStepSupportsRestart', yaml: yamlPod) {
node(yamlPodHash) {
Comment on lines +32 to +39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just remove the deprecated label parameter

Suggested change
def yamlPodHash = '#{project.artifactId}-#{project.version}-maven-image'
// one build per job to ensure tag consistency
try {
lock(resource: env.JOB_NAME.split('/')[1], inversePrecedence: true) {
milestone()
podTemplate(label: yamlPodHash, instanceCap: 6, idleMinutes: 60, cloud: 'dynamicPodTemplateStepSupportsRestart', yaml: yamlPod) {
node(yamlPodHash) {
// one build per job to ensure tag consistency
try {
lock(resource: env.JOB_NAME.split('/')[1], inversePrecedence: true) {
milestone()
podTemplate(instanceCap: 6, idleMinutes: 60, cloud: 'dynamicPodTemplateStepSupportsRestart', yaml: yamlPod) {
node(POD_LABEL) {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Jesse, thanks for taking a look! I don't understand how that can function without it in a dynamic way (not hard coded into the org setup).

The class used to work by looking up the pod template and thus available pods by the label. Then it changed to looking up by ID but with enforced randomness in the ID - removing the label would be fine if we could control the ID, but we currently can't control that.

The components that are equivalent to the above snippet are reusable libraries of pod-definition (podTemplate, if you will) and are defined in a totally separate and independent package to the organisation config/setup - this is rewritten completely every boot and is always the same and always works because always the same. On top of the pod template reusable blocks ride a bunch of job types with a many to one relationship to a pod spec, ie, three different build styles might use one pod spec, and another 3 additional build styles might use a second different pod spec. Different sets of build styles and pod specs are available in different packages with the final jenkins build including those that are useful to a particular team/project.

Without putting these pod specs in the organisation where IMO they do not belong how can you get reusable pods across N jobs that share M build styles that share 1 pod spec/template/definition that is implemented by P agent pods?

The real version of this has the body passed in from the out side and run as body() in place of the println on line 42 of that groovy.

How does POD_LABEL provide the same experience for us that we had before this got broken and what is the perceived benefit of these items being IDed randomly at creation without it having any bearing on the yaml or name included? Help! :-D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does POD_LABEL provide the same experience for us that we had before

I am afraid I am unable to follow what you are trying to accomplish or more to the point what you think does not already work. Simple

podTemplate(yaml: whatever) {
  node(POD_LABEL) {
    sh 'true' // whatever body
  }
}

should work reliably, including across controller restarts, regardless of the number of jobs, concurrent builds, different YAML contents, etc. etc. You should not need to pay any attention to “id”, “label”, etc., or have any global configuration beyond the physical connection to the K8s cluster & namespace (not even that, if the controller itself is running inside K8s and using a service account with appropriate permissions). The above snippet will create a Pod with the definition you specify, wait for its agent to connect to the controller, do work, then delete the pod and clean up, with no interference with other builds or jobs or global settings.

In the future we could introduce a separate step that bypasses the Jenkins queue altogether and makes things even simpler:

pod(yaml: whatever) {
  sh 'true'
}

by analogy with https://www.jenkins.io/doc/pipeline/steps/docker-slaves/#dockernode-allocate-a-docker-node but for now the POD_LABEL idiom is the glue needed to make dynamic pod configuration work with the stock node step.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Jesse, we're somewhat in the same boat here :-) You don't understand what we're trying to do and we don't understand why you're not letting us do it :-D But the above clarifies your angle so hopefully I can build on that and explain what our intent is:

The above snippet will create a Pod with the definition you specify, wait for its agent to connect to the controller, do work, then delete the pod and clean up, with no interference with other builds or jobs or global settings.

This is 100% not what we want :-D The idea is that we specify the definition in such a way that it matches existing pods and can then just use them. The result of the change Vincent made was that it doesn't match or not match, it just fails.

It seems like you've interpreted that groovy file as having the intent to create a bespoke single use pod, use it, and dump it - is that right? That's not the intent, and this setup worked flawlessly for years before the commit that changed from:

set label in unique to def way
lookup by label in KubeSlave class
just work

to

set label in unique to def way, get random id
lookup by id in KubeSlave class
fail

Allowing us to set that ID would restore perfect clean functionality for us - but both of you seem pretty heavily against that, so I need to understand what a solution that meets your idea of how it should be used AND meets our idea of an acceptable way to use it is, and if that can be found, rework our stuff and roll it out everywhere. And if not, maybe just fork it, add the one line, and hope no further breakage occurs. Though I really really really don't want to do that for a lot of reasons.

One of the reasons we don't want brand new pods every time is the cache that is held in the pod between builds will be empty every time and every build will be ultra slow, not just those first thing in the working day. Even if/when we do something to improve the cache seeding situation that seeding will be a fairly heavy overhead (largeish data copy) for a new pod and not something we'd want to do hundreds or thousands of times per day.

Our goal is:

  1. Our jenkins(actually many of them) is fresh/new every time it boots, always the same, eg - those random IDs appear to break that - but perhaps there's another way
  2. Our builds specify the pod style they use in a self contained way
  3. Pods are reused throughout the day until their inactivity timeout is reached and they're killed and the cluster can down scale - hot caches, fast builds - set to 1 hour inactivity so if used regularly through the day, life span of approx 9 or 10 hours for a single-time-zone team
  4. The jenkins assembly can be packaged together as a composition including sets of rules/pod-defs that can immediately be used
  5. After a restart IF the pod defs are the same, the old pods should be successfully reused
  6. Ideally minimal rework of our stack as we're pretty happy with how it is at the moment - however we're open to change, it's just not clear what the new acceptable solution would look like yet.

I don't understand how any fresh-on-boot system can be consistent and can match the pod template when it runs if it's not possible to create those and specify the ID or have the ID derived from a hash of the contents? That would be fine, too.

Maybe it IS possible but in a different way and not in the build command spec like now where it goes via the PodTemplateStep object (created confusingly by podTemplate() call) and not directly into a PodTemplate object where that ID can already be set.

I hope that clarified a few things for you as your post did for me, and I look forward to your response! :-D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't want brand new pods every time

The kubernetes plugin is designed to create a pod for every build (or more precisely, every node block), and then throw it away once the build (block) exits. Reusing pods is not a supported use case. (There is an option to keep a pod after exiting, but this is solely for diagnostic purposes, and even that is a dubious practice.)

the cache that is held in the pod between builds will be empty every time

You can specify a persistent volume to reuse between pods, assuming the storage class supports concurrent mounts. This is generally a quite fragile setup, though (special requirements on volume drivers, UID mismatches, file locking issues, ad nauseam) so you are generally better off using a mirror (if dependency downloads are the problem) or a distributed-friendly build system with a cache server (if incremental builds are what you are looking for).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My take on this, with the given plugin state:

  • Static pod templates defined in the Jenkins UI or through JCasc can be reused (I think this is actually the original use case introduced in the plugin)
  • Dynamic pod templates defined in Jenkinsfiles or shared libraries through the DSL are single-use. They're not intended to be reused or shared with other projects or builds.

The fact dynamic pod templates (or corresponding pods) are accessible to other jobs is a undesirable side-effect of using the Cloud API. Unfortunately this is what we have today, and as Jesse mentioned in his earlier response this is something we want to eliminate if we have the opportunity.

For your use case, I think it can fit with the current plugin if you manage static pod templates using the jcasc aproach. I'm not in favour to make any plugin change that would facilicate re-use of pods if they have been defined in a Jenkinsfile.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For your use case, I think it can fit with the current plugin if you manage static pod templates

IIUC no, the request was to reuse pods, not just pod templates, for performance reasons. Certainly reusing pod templates across jobs is well supported (even if we would be inclined to recommend the podTemplate step going forward), but that is still assuming one pod per node block.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See

private RetentionStrategy determineRetentionStrategy() {
if (podTemplate.getIdleMinutes() == 0) {
return new OnceRetentionStrategy(cloud.getRetentionTimeout());
} else {
return new CloudRetentionStrategy(podTemplate.getIdleMinutes());
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OnceRetentionStrategy is the normal usage, but the option to use CloudRetentionStrategy suggests that the long-lived-agent style was deliberately added—albeit without tests, documentation, or the ability to use multiple executors? Bisecting says in #91, whose description seems to match this request.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Jesse, Vincent, long time no talk! I did see all of these updates but have been busy with other stuff.

This is still burning everyone, however, and the number of people it's burning keeps growing. Please stop calling it a rare demand. I speak for 50+ people across 7 or so organisations when I comment here. I'd rather not repeat that a third or subsequent time. This is a big deal for a lot of people across two banks and various other companies.

More efficient for very short builds, at the cost of a higher risk of cross-build interference and other maintenance overhead.

Please, this is your personal perception and not any part of our reality. These aspects are managed properly and never give us any trouble.

this VM-like use case with reusable agents is outside the design space of this plugin

It's not "vm like" it's normal for well composed software that is assembled from small reusable pieces.

You have personally, and I commend you for your curiosity and diligence in researching it, proven that that's not the case. It was added to this plugin by the founder of the plugin, and for good reason. Let's call that the design space. There are another dozen people wanting it on the various links you provided, in addition to the multiple dozens I bring to the table.

I'm not in favour to make any plugin change that would facilicate re-use of pods if they have been defined in a Jenkinsfile.

Our Jenkinsfiles are one or two line, paraphrasing:

buildACertainWayWithACertainAmountOfResources
triggerOtherDownstreamBuilds(["X", "Y", "Z"])

And to your other comment:

Dynamic pod templates defined in Jenkinsfiles or shared libraries through the DSL are single-use. They're not intended to be reused or shared with other projects or builds.

How are pod templates defined in shared libraries that are static with respect to a thousand builds not static in reality?

I understand that you can't guarantee that, but can you make the assumption that someone neck deep in configuring this has the talent to ensure they don't hang themselves with the rope they're given, and just give them the rope?

All of our pod templates are named and versioned, unique and immutable. If they don't match, you get a new pod. If they do, during a master/controller run, or even after a restart before the fateful change, they get reused.

I'd like to take this moment to remind you that this is ONLY across restarts that there is now an issue. It STILL works as required during a single master node execution cycle. Please do not cripple it any further.

My change just lets me control the ID such that it's not random and is deliberate and meaningful in a controlled and safe way. For existing users, this makes no difference unless they're currently seeing a "field doesn't exist" of the same name by random chance. Only then could behaviour change.

My change simply matches the available-to-me API with the underlying object API, it doesn't add anything new, it just exposes a TINY bit more control that wasn't previous required when the default behaviour made sense and wasn't broken. I understand the desire to make them unique for your throw-away style of builds, but please understand our desire to keep them matching and reusable and look-up-able for our builds that work the same exact way, guaranteed by their own semantics, whether there is cache available, or not, just MUCH slower when not.

Please let us move on and let this PR die in a good way.

container('maven-image') {
timeout(20) {
println "dynamic pod template step success"
}
}
}
}
}

println "${JOB_NAME} ${currentBuild.displayName} see ${JOB_URL}"

}
catch (e) {
println e
println "${JOB_NAME} ${currentBuild.displayName} see ${JOB_URL}"
throw e
}
}
2 changes: 2 additions & 0 deletions test-in-k8s.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ kubectl exec jenkins -- mkdir /checkout
kubectl cp pom.xml jenkins:/checkout/pom.xml
kubectl cp .mvn jenkins:/checkout/.mvn
kubectl cp src jenkins:/checkout/src

TEST='RestartPipelinePodTemplateIdConsistencyTest,RestartPipelineTest'
if [ -v TEST ]
then
args="-Dtest=$TEST test"
Expand Down