Skip to content

Move all HTTP calls to a dedicated class and use Jetty HTTP client instead of raw HttpUrlConnection#346

Merged
nikita-tkachenko-datadog merged 7 commits into
masterfrom
nikita-tkachenko/network-layer-update
Aug 28, 2023
Merged

Move all HTTP calls to a dedicated class and use Jetty HTTP client instead of raw HttpUrlConnection#346
nikita-tkachenko-datadog merged 7 commits into
masterfrom
nikita-tkachenko/network-layer-update

Conversation

@nikita-tkachenko-datadog

@nikita-tkachenko-datadog nikita-tkachenko-datadog commented Jun 9, 2023

Copy link
Copy Markdown
Collaborator

Requirements for Contributing to this repository

  • Fill out the template below. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion.
  • The pull request must only fix one issue at the time.
  • The pull request must update the test suite to demonstrate the changed functionality.
  • After you create the pull request, all status checks must be pass before a maintainer reviews your contribution. For more details, please see CONTRIBUTING.

What does this PR do?

This PR does a few improvements to the way the plugin does HTTP calls, making them more efficient and limiting the number of concurrent requests.

The reason to do this is that there is a customer with a Jenkins instance that runs multiple large pipelines: when executed with the plugin enabled, the process runs out of available file descriptors because the requests are slow (since each one opens a new TCP socket) and because there is no limit on the number of simultaneous requests and no backpressure if the network layer cannot keep up.

Description of the Change

  • all the HTTP calls are now delegated to org.datadog.jenkins.plugins.datadog.clients.HttpClient which provides a layer of abstraction on top of HTTP-calling logic
  • Jetty HTTP client is now used instead of raw HttpUrlConnection, which allows to pool and reuse TCP sockets, limit the number of simultaneous requests and to make some of the request asynchronously
  • Retry logic is added to re-execute calls that are failing

Alternate Designs

Possible Drawbacks

Verification Process

Verified manually in Agentless/Evp-proxy/Agentful-APM modes.
Separate set of verifications is done with Jenkins using an HTTP proxy (to ensure the plugin respect Jenkins' proxy settings).

Additional Notes

Release Notes

Review checklist (to be filled by reviewers)

  • Feature or bug fix MUST have appropriate tests (unit, integration, etc...)
  • PR title must be written as a CHANGELOG entry (see why)
  • Files changes must correspond to the primary purpose of the PR as described in the title (small unrelated changes should have their own PR)
  • PR must have one changelog/ label attached. If applicable it should have the backward-incompatible label attached.
  • PR should not have do-not-merge/ label attached.
  • If Applicable, issue must have kind/ and severity/ labels attached at least.

@nikita-tkachenko-datadog nikita-tkachenko-datadog added the changelog/Fixed Fixed features results into a bug fix version bump label Jun 9, 2023

@drodriguezhdez drodriguezhdez left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this PR needs to be reviewed by the jenkins-plugin team as well, as AFAIK we wanted to use vanilla Java HTTP classes to avoid affecting Jenkins core with external dependencies.

Comment thread pom.xml
Comment on lines +165 to +169
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>9.4.51.v20230217</version>
</dependency>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we know if Jenkins uses jetty-client? If so, this might affect Jenkins core and we should use something under org.jenkins-ci.* groupId.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Jenkins does not use Jetty client, and neither do any of the plugins that we're depending on, so we're good to go here.

this.retryPolicyFactory = new HttpRetryPolicy.Factory(5, 100, 2.0);
}

public <T> T get(String url, Map<String, String> headers, Function<String, T> responseParser) throws ExecutionException, InterruptedException, TimeoutException {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are we using get and post anywhere? Do we need them?

As far I could check, only asynchronous methods are used.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

we use the synchronous versions whenever we need to do something with the response: get is used to fetch the list of agent endpoints and post is used for connection validation (part of the validation is to check that the response is valid JSON)


public static final class Factory {
private final int maxRetries;
private final long initialDelay;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe just delay? It's confusing to find initial as it seems it's only going to be used once, but this is the actual delay for every backoff delay.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The field in the policy class is mutable and is called delay, but the one in the factory class is really just the initial value


public long backoff() {
long currentDelay = delay;
delay = (long) (delay * delayFactor * ThreadLocalRandom.current().nextDouble(0.85, 1.15));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we encapsulate the jitter into something like nextJitter() or randomJitter() or something similar?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

done, moved to randomJitter()

@nikita-tkachenko-datadog

Copy link
Copy Markdown
Collaborator Author

I think this PR needs to be reviewed by the jenkins-plugin team as well, as AFAIK we wanted to use vanilla Java HTTP classes to avoid affecting Jenkins core with external dependencies.

Jenkins plugins are loaded using isolated classloaders, so including a third-party dependency in a plugin has no effect on Jenkins core.
Plugin classloaders are children of the classloader that is used to load the core, so the worst that can happen in theory is a problem in the plugin in case it tries to load a class that is already available in the core loader (because children CLs delegate to parent CL).
Anyway, this is not the case here, since neither Jenkins core nor the plugins that we depend on use Jetty HTTP.

drodriguezhdez
drodriguezhdez previously approved these changes Jun 15, 2023
Comment thread src/main/java/org/datadog/jenkins/plugins/datadog/clients/HttpClient.java Outdated
Comment thread src/main/java/org/datadog/jenkins/plugins/datadog/clients/HttpClient.java Outdated
Comment thread src/main/java/org/datadog/jenkins/plugins/datadog/clients/HttpClient.java Outdated
try {
Throwable failure = result.getFailure();
if (failure != null) {
if (retryPolicy.shouldRetry(null)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why don't we check if we should retry with the response of the result here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Having a failure usually means there is no response, i.e. we failed before the response could be received.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Gotcha, thanks! Is there any downside in first getting the response, and then checking shouldRetry with it? That way if the response is null, the behavior hasn't changed, but if there is a response, we can use the additional logic in shouldRetry?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agree, that would allow to simplify the code a bit. Changed it accordingly

Comment thread src/main/java/org/datadog/jenkins/plugins/datadog/clients/HttpClient.java Outdated

@sarah-witt sarah-witt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Awesome!

@nikita-tkachenko-datadog
nikita-tkachenko-datadog merged commit 0c0fea3 into master Aug 28, 2023
@nikita-tkachenko-datadog
nikita-tkachenko-datadog deleted the nikita-tkachenko/network-layer-update branch August 28, 2023 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/Fixed Fixed features results into a bug fix version bump

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants