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

Add node labels to Jenkins Pipelines traces #212

Merged
merged 4 commits into from
Jun 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,25 @@ public static String getNodeName(Computer computer){
}
}


@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
public static Set<String> getNodeLabels(Computer computer) {
Set<LabelAtom> labels;
try {
labels = computer.getNode().getAssignedLabels();
} catch (Exception e){
logger.fine("Could not retrieve labels: " + e.getMessage());
return Collections.emptySet();
}

final Set<String> labelsStr = new HashSet<>();
for(final LabelAtom label : labels) {
labelsStr.add(label.getName());
}

return labelsStr;
}

public static String getUserId() {
User user = User.current();
if (user == null) {
Expand Down Expand Up @@ -771,4 +790,31 @@ public static String toISO8601(Date date) {
return sdf.format(date);
}

/**
* Returns a JSON array string based on the set.
* @param set
* @return json array string
*/
public static String toJson(final Set<String> set) {
if(set == null || set.isEmpty()) {
return "";
}

// We want to avoid using Json libraries cause
// may cause incompatibilities on different Jenkins versions.
final StringBuilder sb = new StringBuilder();
Copy link
Collaborator

Choose a reason for hiding this comment

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

can there be quotes in Jenkins labels?

Copy link
Collaborator Author

@drodriguezhdez drodriguezhdez Jun 14, 2021

Choose a reason for hiding this comment

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

If you mean double quotes: no, Jenkins removes the double quotes (") when you configure the labels for the worker. Other characters are allowed (', $, %, etc..)

A valid label array could be:

["arch01","worker","''''asd''''","·$··$·$","asdasdasd"]

sb.append("[");
int index = 1;
for(String val : set) {
sb.append("\"").append(val).append("\"");
if(index < set.size()) {
sb.append(",");
}
index += 1;
}
sb.append("]");

return sb.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void notifyOfNewStep(@Nonnull Step step, @Nonnull StepContext context) {
if(blockStartNodes.hasNext()) {
final FlowNode candidate = blockStartNodes.next();
if("Start of Pipeline".equals(candidate.getDisplayName())) {
run.addAction(new PipelineNodeInfoAction(stepData.getNodeName() != null ? stepData.getNodeName() : "master"));
run.addAction(new PipelineNodeInfoAction(stepData.getNodeName() != null ? stepData.getNodeName() : "master", stepData.getNodeLabels()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ private void completeInformation(final List<BuildPipelineNode> nodes, final Buil
final BuildPipelineNode executableChildNode = searchExecutableChildNode(node);
if(executableChildNode != null) {
node.setPropagatedNodeName(executableChildNode.getNodeName());
node.setPropagatedNodeLabels(executableChildNode.getNodeLabels());
Copy link
Collaborator

Choose a reason for hiding this comment

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

are you propagating it from job spans to stage/pipeline? I guess that's accurate for Jenkins?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, the worker information only is available in an actual executable child node, which is a job.

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

Expand Down Expand Up @@ -68,7 +69,9 @@ public String getBuildLevel() {
private Map<String, String> envVars = new HashMap<>();
private String workspace;
private String nodeName;
private Set<String> nodeLabels;
private String propagatedNodeName;
private Set<String> propagatedNodeLabels;
private String nodeHostname;
private AnnotatedLargeText logText;
private long startTime;
Expand Down Expand Up @@ -133,6 +136,7 @@ public BuildPipelineNode(final BlockEndNode endNode) {
this.workspace = stepData.getWorkspace();
this.nodeName = stepData.getNodeName();
this.nodeHostname = stepData.getNodeHostname();
this.nodeLabels = stepData.getNodeLabels();
}
}

Expand Down Expand Up @@ -169,6 +173,7 @@ public BuildPipelineNode(final StepAtomNode stepNode) {
this.workspace = stepData.getWorkspace();
this.nodeName = stepData.getNodeName();
this.nodeHostname = stepData.getNodeHostname();
this.nodeLabels = stepData.getNodeLabels();
}

final FlowNodeQueueData queueData = getQueueData(stepNode);
Expand Down Expand Up @@ -233,6 +238,10 @@ public String getNodeName() {
return nodeName;
}

public Set<String> getNodeLabels() {
return nodeLabels;
}

public String getPropagatedNodeName() {
return propagatedNodeName;
}
Expand All @@ -241,6 +250,14 @@ public void setPropagatedNodeName(String propagatedNodeName) {
this.propagatedNodeName = propagatedNodeName;
}

public Set<String> getPropagatedNodeLabels() {
return propagatedNodeLabels;
}

public void setPropagatedNodeLabels(final Set<String> propagatedNodeLabels) {
this.propagatedNodeLabels = propagatedNodeLabels;
}

public String getNodeHostname() {
return nodeHostname;
}
Expand Down Expand Up @@ -345,6 +362,7 @@ public void updateData(final BuildPipelineNode buildNode) {
this.workspace = buildNode.workspace;
this.nodeName = buildNode.nodeName;
this.nodeHostname = buildNode.nodeHostname;
this.nodeLabels = buildNode.nodeLabels;
this.logText = buildNode.logText;
this.startTime = buildNode.startTime;
this.startTimeMicros = buildNode.startTimeMicros;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@
import hudson.model.InvisibleAction;

import java.io.Serializable;
import java.util.Set;

public class PipelineNodeInfoAction extends InvisibleAction implements Serializable {

private final String nodeName;
private final Set<String> nodeLabels;

public PipelineNodeInfoAction(final String nodeName) {
public PipelineNodeInfoAction(final String nodeName, final Set<String> nodeLabels) {
this.nodeName = nodeName;
this.nodeLabels = nodeLabels;
}

public String getNodeName() {
return nodeName;
}

public Set<String> getNodeLabels() {
return nodeLabels;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;

Expand All @@ -22,12 +23,14 @@ public class StepData implements Serializable {
private final String nodeName;
private final String nodeHostname;
private final String workspace;
private final Set<String> nodeLabels;

public StepData(final StepContext stepContext){
this.envVars = getEnvVars(stepContext);
this.nodeName = getNodeName(stepContext);
this.nodeHostname = getNodeHostname(stepContext);
this.workspace = getNodeWorkspace(stepContext);
this.nodeLabels = getNodeLabels(stepContext);
}

public Map<String, String> getEnvVars() {
Expand All @@ -46,6 +49,10 @@ public String getWorkspace() {
return workspace;
}

public Set<String> getNodeLabels() {
return nodeLabels;
}


/**
* Returns the workspace filepath of the remote node which is executing a determined {@code Step}
Expand Down Expand Up @@ -104,6 +111,22 @@ private String getNodeName(StepContext stepContext) {
}


/**
* Returns the nodeLabels of the remote node which is executing a determined {@code Step}
* @param stepContext
* @return node labels of the remote node.
*/
private Set<String> getNodeLabels(StepContext stepContext) {
try {
Computer computer = stepContext.get(Computer.class);
return DatadogUtilities.getNodeLabels(computer);
} catch (Exception e) {
logger.fine("Unable to extract the node labels from StepContext.");
return Collections.emptySet();
}
}


/**
* Returns {@code Map<String,String>} with environment variables of a certain {@code StepContext}
* @param stepContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public class CITags {
public static final String STATUS = "ci.status";
public static final String WORKSPACE_PATH = "ci.workspace_path";
public static final String NODE_NAME = "ci.node.name";
public static final String NODE_LABELS = "ci.node.labels";
public static final String QUEUE_TIME = "ci.queue_time";
public static final String _DD_HOSTNAME = "_dd.hostname";
public static final String _DD_CI_INTERNAL = "_dd.ci.internal";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.datadog.jenkins.plugins.datadog.traces;

import static org.datadog.jenkins.plugins.datadog.DatadogUtilities.getNormalizedResultForTraces;
import static org.datadog.jenkins.plugins.datadog.DatadogUtilities.toJson;
import static org.datadog.jenkins.plugins.datadog.traces.GitInfoUtils.normalizeBranch;
import static org.datadog.jenkins.plugins.datadog.traces.GitInfoUtils.normalizeTag;

Expand All @@ -19,13 +20,15 @@
import org.datadog.jenkins.plugins.datadog.model.PipelineQueueInfoAction;
import org.datadog.jenkins.plugins.datadog.model.StageBreakdownAction;
import org.datadog.jenkins.plugins.datadog.model.StageData;
import org.datadog.jenkins.plugins.datadog.steps.DatadogPipelineAction;
import org.datadog.jenkins.plugins.datadog.util.SuppressFBWarnings;
import org.datadog.jenkins.plugins.datadog.util.json.JsonUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

Expand Down Expand Up @@ -131,6 +134,11 @@ public void finishBuildTrace(final BuildData buildData, final Run<?,?> run) {

final String nodeName = getNodeName(run, buildData, updatedBuildData);
buildSpan.setTag(CITags.NODE_NAME, nodeName);

final String nodeLabelsJson = toJson(getNodeLabels(run, nodeName));
if(!nodeLabelsJson.isEmpty()){
buildSpan.setTag(CITags.NODE_LABELS, nodeLabelsJson);
}
// If the NodeName == master, we don't set _dd.hostname. It will be overridden by the Datadog Agent. (Traces are only available using Datadog Agent)
// If the NodeName != master, we set _dd.hostname to 'none' explicitly, cause we cannot calculate the worker hostname.
if(!"master".equalsIgnoreCase(nodeName)) {
Expand Down Expand Up @@ -246,6 +254,40 @@ private String getNodeName(Run<?, ?> run, BuildData buildData, BuildData updated
return buildData.getNodeName("").isEmpty() ? updatedBuildData.getNodeName("") : buildData.getNodeName("");
}

@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
private Set<String> getNodeLabels(Run<?,?> run, final String nodeName) {
try {
if(run == null){
return Collections.emptySet();
}

final PipelineNodeInfoAction pipelineNodeInfoAction = run.getAction(PipelineNodeInfoAction.class);
if(pipelineNodeInfoAction != null) {
return pipelineNodeInfoAction.getNodeLabels();
}

if(run.getExecutor() != null && run.getExecutor().getOwner() != null) {
Set<String> nodeLabels = DatadogUtilities.getNodeLabels(run.getExecutor().getOwner());
if(nodeLabels != null && !nodeLabels.isEmpty()) {
return nodeLabels;
}
}

// If there is no labels and the node name is master,
// we force the label "master".
if("master".equalsIgnoreCase(nodeName)){
final Set<String> masterLabels = new HashSet<>();
masterLabels.add("master");
return masterLabels;
}

return Collections.emptySet();
} catch (Exception ex) {
logger.fine("Unable to find node labels: " + ex.getMessage());
return Collections.emptySet();
}
}

private long getMillisInQueue(BuildData buildData) {
// Reported by the Jenkins Queue API.
// It's not included in the root span duration.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.datadog.jenkins.plugins.datadog.traces;

import static org.datadog.jenkins.plugins.datadog.DatadogUtilities.getNormalizedResultForTraces;
import static org.datadog.jenkins.plugins.datadog.DatadogUtilities.toJson;
import static org.datadog.jenkins.plugins.datadog.model.BuildPipelineNode.NodeType.PIPELINE;
import static org.datadog.jenkins.plugins.datadog.traces.GitInfoUtils.normalizeBranch;
import static org.datadog.jenkins.plugins.datadog.traces.GitInfoUtils.normalizeTag;
Expand All @@ -25,6 +26,7 @@
import org.datadog.jenkins.plugins.datadog.model.PipelineNodeInfoAction;
import org.datadog.jenkins.plugins.datadog.model.StageBreakdownAction;
import org.datadog.jenkins.plugins.datadog.model.StageData;
import org.datadog.jenkins.plugins.datadog.util.SuppressFBWarnings;
import org.datadog.jenkins.plugins.datadog.util.TagsUtil;
import org.datadog.jenkins.plugins.datadog.util.git.GitUtils;
import org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode;
Expand All @@ -35,9 +37,12 @@

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

Expand Down Expand Up @@ -328,6 +333,12 @@ private Map<String, Object> buildTraceTags(final Run run, final BuildPipelineNod
// Node info
final String nodeName = getNodeName(run, current, buildData);
tags.put(CITags.NODE_NAME, nodeName);

final String nodeLabels = toJson(getNodeLabels(run, current, nodeName));
if(!nodeLabels.isEmpty()){
tags.put(CITags.NODE_LABELS, nodeLabels);
}

// If the NodeName == "master", we don't set _dd.hostname. It will be overridden by the Datadog Agent. (Traces are only available using Datadog Agent)
// If the NodeName != "master", we set _dd.hostname to 'none' explicitly, cause we cannot calculate the worker hostname.
if(!"master".equalsIgnoreCase(nodeName)){
Expand Down Expand Up @@ -377,6 +388,36 @@ private Map<String, Object> buildTraceTags(final Run run, final BuildPipelineNod
return tags;
}


@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
private Set<String> getNodeLabels(Run run, BuildPipelineNode current, String nodeName) {
final PipelineNodeInfoAction pipelineNodeInfoAction = run.getAction(PipelineNodeInfoAction.class);
if (current.getPropagatedNodeLabels() != null && !current.getPropagatedNodeLabels().isEmpty()) {
return current.getPropagatedNodeLabels();
} else if (current.getNodeLabels() != null && !current.getNodeLabels().isEmpty()) {
return current.getNodeLabels();
} else if (pipelineNodeInfoAction != null && !pipelineNodeInfoAction.getNodeLabels().isEmpty()) {
return pipelineNodeInfoAction.getNodeLabels();
}

if (run.getExecutor() != null && run.getExecutor().getOwner() != null) {
Set<String> nodeLabels = DatadogUtilities.getNodeLabels(run.getExecutor().getOwner());
if (nodeLabels != null && !nodeLabels.isEmpty()) {
return nodeLabels;
}
}

// If there is no labels and the node name is master,
// we force the label "master".
if ("master".equalsIgnoreCase(nodeName)) {
final Set<String> masterLabels = new HashSet<>();
masterLabels.add("master");
return masterLabels;
}

return Collections.emptySet();
}

private String getResult(BuildPipelineNode current) {
return (current.getPropagatedResult() != null) ? current.getPropagatedResult() : current.getResult();
}
Expand Down
Loading