Skip to content

Commit

Permalink
Multi client support in Pipeline.
Browse files Browse the repository at this point in the history
If two or more `p4sync` operations are called in one Pipeline script,
they MUST have different client names.  During a build, multiple
entries are added to the build history; on subsequent builds they are
used in the three following situations:
 - Polling: largest change number across all the checkouts is used.
 - Change Summary: last change with the same client name is used.
 - Environment: last change recorded (regardless of client).

Added two test cases to cover basic multi sync build/poll situations.

JENKINS-38401
JENKINS-37462
JENKINS-39652
  • Loading branch information
p4paul committed Dec 5, 2016
1 parent 244e52d commit ad88e96
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 41 deletions.
50 changes: 14 additions & 36 deletions src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java
Expand Up @@ -267,37 +267,17 @@ public PollingResult compareRemoteRevisionWith(Job<?, ?> job, Launcher launcher,
listener.getLogger().println("Warning Jenkins instance is null.");
return PollingResult.NO_CHANGES;
}
buildWorkspace = j.getRootPath();

// get last run, if none then build now.
// Get change for last run
Run<?, ?> lastRun = job.getLastBuild();
if (lastRun == null) {
listener.getLogger().println("No previous run found; building...");
return PollingResult.BUILD_NOW;
}
P4Revision last = TagAction.getHighestChange(lastRun, listener);

// get last action, if no previous action then build now.
List<TagAction> actions = lastRun.getActions(TagAction.class);
if (actions.isEmpty()) {
listener.getLogger().println("No previous build found; building...");
// if no previous run then build now.
if (last == null) {
return PollingResult.BUILD_NOW;
}

// found previous build, set last change to the first action found.
P4Revision last = actions.get(0).getBuildChange();

// Find the change with the highest change number, use this for the
// poll. This isn't accurate if multiple workspaces are polled
// and are disjoint, but is accurate if the workspaces are subsets
// of each other.
for (TagAction action : actions) {
if (action.getBuildChange().compareTo(last) > 0) {
last = action.getBuildChange();
}
}

listener.getLogger().println("Found last change " + last.toString() +
" in previous build");
buildWorkspace = j.getRootPath();

if (job instanceof MatrixProject) {
if (isBuildParent(job)) {
Expand Down Expand Up @@ -491,17 +471,15 @@ private List<P4ChangeEntry> calculateChanges(Run<?, ?> run, CheckoutTask task) {

// Look for all changes since the last build
Run<?, ?> lastBuild = run.getPreviousSuccessfulBuild();
if (lastBuild != null) {
TagAction lastTag = lastBuild.getAction(TagAction.class);
if (lastTag != null) {
P4Revision lastChange = lastTag.getBuildChange();
if (lastChange != null) {
List<P4ChangeEntry> changes;
changes = task.getChangesFull(lastChange);
for (P4ChangeEntry c : changes) {
list.add(c);
}
}

String client = task.getClient();
P4Revision lastChange = TagAction.getLastChange(lastBuild, task.getListener(), client);

if (lastChange != null) {
List<P4ChangeEntry> changes;
changes = task.getChangesFull(lastChange);
for (P4ChangeEntry c : changes) {
list.add(c);
}
}

Expand Down
Expand Up @@ -18,9 +18,10 @@ public class P4EnvironmentContributor extends EnvironmentContributor {

@Override
public void buildEnvironmentFor(Run run, EnvVars env, TaskListener listener) throws IOException, InterruptedException {
TagAction tagAction = run.getAction(TagAction.class);

if(tagAction == null) {
TagAction tagAction = TagAction.getLastAction(run, listener);

if (tagAction == null) {
return;
}

Expand Down Expand Up @@ -61,6 +62,4 @@ public void buildEnvironmentFor(Run run, EnvVars env, TaskListener listener) thr
}
}
}


}
Expand Up @@ -39,6 +39,7 @@ public abstract class P4BaseCredentials extends BaseStandardCredentials implemen
* @param username Perforce username
* @param retry Perforce connection retry option
* @param timeout Perforce connection timeout option
* @param p4host Perforce HOST (optional)
*/
public P4BaseCredentials(CredentialsScope scope, String id,
String description, @NonNull String p4port,
Expand Down
87 changes: 87 additions & 0 deletions src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java
Expand Up @@ -175,4 +175,91 @@ public Label getLabel(String tag) throws Exception {
Label label = p4.getLabel(tag);
return label;
}

/**
* Change reporting...
*
* @param run The current build
* @param listener Listener for logging
* @return Perforce change
*/
public static P4Revision getLastChange(Run<?, ?> run, TaskListener listener, String client) {
P4Revision last = null;

List<TagAction> actions = lastActions(run, listener);
if(actions == null || client == null || client.isEmpty()) {
return last;
}

// look for action matching view
for (TagAction action : actions) {
if(client.equals(action.getClient())) {
last = action.getBuildChange();
}
}

listener.getLogger().println("Found last change " + last.toString() + " on client " + client);
return last;
}

/**
* Find the last action; use this for environment variable as the last action has the latest values.
*
* @param run The current build
* @param listener Listener for logging
* @return Action
*/
public static TagAction getLastAction(Run<?, ?> run, TaskListener listener) {
List<TagAction> actions = lastActions(run, listener);
if(actions == null) {
return null;
}

// #Review 21165
TagAction last = actions.get(actions.size() - 1);
return last;
}

/**
* Find the change with the highest change number, use this for polling. This isn't accurate if multiple
* workspaces are polled and are disjoint, but is accurate if the workspaces are subsets of each other.
*
* @param run The current build
* @param listener Listener for logging
* @return Perforce change
*/
public static P4Revision getHighestChange(Run<?, ?> run, TaskListener listener) {
List<TagAction> actions = lastActions(run, listener);
if(actions == null) {
return null;
}

// found previous build, set last change to the first action found.
TagAction last = actions.get(0);

// look for highest change number
for (TagAction action : actions) {
if (action.getBuildChange().compareTo(last.getBuildChange()) > 0) {
last = action;
}
}
return last.getBuildChange();
}

private static List<TagAction> lastActions(Run<?, ?> run, TaskListener listener) {
// get last run, if none then build now.
if (run == null) {
listener.getLogger().println("No previous run found...");
return null;
}

// get last action, if no previous action then build now.
List<TagAction> actions = run.getActions(TagAction.class);
if (actions.isEmpty()) {
listener.getLogger().println("No previous build found...");
return null;
}

return actions;
}
}
Expand Up @@ -142,7 +142,7 @@ private static String getHostName(FilePath buildWorkspace) {
}
}

protected String getClient() {
public String getClient() {
return client;
}

Expand Down
125 changes: 125 additions & 0 deletions src/test/java/org/jenkinsci/plugins/p4/client/JenkinsfileTest.java
Expand Up @@ -104,6 +104,131 @@ public void testBasicJenkinsfile() throws Exception {
assertEquals(2, job.getLastBuild().getChangeSets().size());
}

@Test
public void testDiffClients() throws Exception {

String content = ""
+ "node {\n"
+ " checkout([$class: 'PerforceScm', credential: '" + CREDENTIAL + "',"
+ " populate: [$class: 'AutoCleanImpl'],\n"
+ " workspace: [$class: 'ManualWorkspaceImpl', name: 'jenkins-${NODE_NAME}-${JOB_NAME}-src',\n"
+ " spec: [view: '//depot/Data/... //jenkins-${NODE_NAME}-${JOB_NAME}-src/...']]])\n"
+ " println \"P4_CHANGELIST: ${env.P4_CHANGELIST}\"\n"
+ "}";

submitFile("//depot/Data/Jenkinsfile", content);

submitFile("//depot/Data/j001", "Content");

// Manual workspace spec definition
String client = "jenkins-${NODE_NAME}-${JOB_NAME}-script";
String stream = null;
String line = "LOCAL";
String view = "//depot/Data/Jenkinsfile //" + client + "/Jenkinsfile";
WorkspaceSpec spec = new WorkspaceSpec(false, false, false, false, false, false, stream, line, view);
ManualWorkspaceImpl workspace = new ManualWorkspaceImpl("none", true, client, spec);

// SCM and Populate options
Populate populate = new AutoCleanImpl();
PerforceScm scm = new PerforceScm(CREDENTIAL, workspace, populate);

// SCM Jenkinsfile job
WorkflowJob job = jenkins.jenkins.createProject(WorkflowJob.class, "diffClientsJenkinsfile");
job.setDefinition(new CpsScmFlowDefinition(scm, "Jenkinsfile"));

// Build 1
WorkflowRun run = job.scheduleBuild2(0).get();
jenkins.assertBuildStatusSuccess(run);
jenkins.assertLogContains("P4_CHANGELIST: 42", run);

// Make changes for trigger
submitFile("//depot/Data/j002", "Content");

// Add a trigger
P4Trigger trigger = new P4Trigger();
trigger.start(job, false);
job.addTrigger(trigger);
job.save();

assertEquals(1, job.getLastBuild().getNumber());

// Test trigger
trigger.poke(job, auth.getP4port());

TimeUnit.SECONDS.sleep(job.getQuietPeriod());
jenkins.waitUntilNoActivity();

assertEquals(2, job.getLastBuild().getNumber());
List<String> log = job.getLastBuild().getLog(1000);
assertTrue(log.contains("P4_CHANGELIST: 43"));

assertEquals(2, job.getLastBuild().getChangeSets().size());
}

@Test
public void testMulitSync() throws Exception {

String content1 = ""
+ "node {\n"
+ " p4sync charset: 'none', credential: '" + CREDENTIAL + "',\n"
+ " format: 'jenkins-${NODE_NAME}-${JOB_NAME}-1',\n"
+ " depotPath: '//depot/Data',\n"
+ " populate: [$class: 'AutoCleanImpl', pin: '17', quiet: true]\n"
+ " p4sync charset: 'none', credential: '" + CREDENTIAL + "',\n"
+ " format: 'jenkins-${NODE_NAME}-${JOB_NAME}-2',\n"
+ " depotPath: '//depot/Main',\n"
+ " populate: [$class: 'AutoCleanImpl', pin: '13', quiet: true]\n"
+ "}";

submitFile("//depot/Data/Jenkinsfile", content1);

// Manual workspace spec definition
String client = "jenkins-${NODE_NAME}-${JOB_NAME}-script";
String stream = null;
String line = "LOCAL";
String view = "//depot/Data/Jenkinsfile //" + client + "/Jenkinsfile";
WorkspaceSpec spec = new WorkspaceSpec(false, false, false, false, false, false, stream, line, view);
ManualWorkspaceImpl workspace = new ManualWorkspaceImpl("none", true, client, spec);

// SCM and Populate options
Populate populate = new AutoCleanImpl();
PerforceScm scm = new PerforceScm(CREDENTIAL, workspace, populate);

// SCM Jenkinsfile job
WorkflowJob job = jenkins.jenkins.createProject(WorkflowJob.class, "multiSync");
job.setDefinition(new CpsScmFlowDefinition(scm, "Jenkinsfile"));

// Build 1
WorkflowRun run1 = job.scheduleBuild2(0).get();
jenkins.assertBuildStatusSuccess(run1);
assertEquals(1, job.getLastBuild().getNumber());
jenkins.assertLogContains("P4 Task: syncing files at change: 17", run1);
jenkins.assertLogContains("P4 Task: syncing files at change: 13", run1);

String content2 = ""
+ "node {\n"
+ " p4sync charset: 'none', credential: '" + CREDENTIAL + "',\n"
+ " format: 'jenkins-${NODE_NAME}-${JOB_NAME}-1',\n"
+ " depotPath: '//depot/Data',\n"
+ " populate: [$class: 'AutoCleanImpl', pin: '18', quiet: true]\n"
+ " p4sync charset: 'none', credential: '" + CREDENTIAL + "',\n"
+ " format: 'jenkins-${NODE_NAME}-${JOB_NAME}-2',\n"
+ " depotPath: '//depot/Main',\n"
+ " populate: [$class: 'AutoCleanImpl', pin: '40', quiet: true]\n"
+ "}";

submitFile("//depot/Data/Jenkinsfile", content2);

// Build 2
WorkflowRun run2 = job.scheduleBuild2(0).get();
jenkins.assertBuildStatusSuccess(run2);
assertEquals(2, job.getLastBuild().getNumber());
jenkins.assertLogContains("P4 Task: syncing files at change: 18", run2);
jenkins.assertLogContains("P4 Task: syncing files at change: 40", run2);
jenkins.assertLogContains("Found last change 17 on client jenkins-master-multiSync-1", run2);
jenkins.assertLogContains("Found last change 13 on client jenkins-master-multiSync-2", run2);
}

private void submitFile(String path, String content) throws Exception {
String filename = path.substring(path.lastIndexOf("/") + 1, path.length());

Expand Down

0 comments on commit ad88e96

Please sign in to comment.