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

[JEP-222] WebSocket-based agents #357

Merged
merged 39 commits into from
Jan 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a5f4f7a
Add a WebSocket agent client.
jglick Nov 13, 2019
944b358
Reworked protocol to negotiate remote capabilities.
jglick Nov 21, 2019
43c6978
Comments.
jglick Nov 21, 2019
e4fe758
Simplifying handshake to use HTTP headers.
jglick Nov 21, 2019
12afe79
SpotBugs
jglick Nov 22, 2019
89397b1
Merge branch 'publish-incrementals' into websocket
jglick Nov 26, 2019
abd931b
Merge branch 'master' into websocket
jglick Dec 6, 2019
561713b
Add a -webSocket launch option.
jglick Dec 10, 2019
c877936
Better comment.
jglick Dec 10, 2019
ae63bd9
Misleading Javadoc.
jglick Dec 10, 2019
2f77265
Handling some close and error methods.
jglick Dec 10, 2019
aefc7b4
Checking X-Remoting-Minimum-Version if sent.
jglick Dec 11, 2019
4c701f3
SpotBugs
jglick Dec 11, 2019
47ea945
More targeted SpotBugs annotation placement.
jglick Dec 11, 2019
86cea5b
Documenting options incompatible with -webSocket.
jglick Dec 11, 2019
02db50c
Comments.
jglick Dec 13, 2019
6263aae
nginx urgh https://stackoverflow.com/a/49801326/12916
jglick Dec 13, 2019
827bfca
Trying to enable JAR cache.
jglick Dec 13, 2019
e25ee2c
JnlpConnectionState.fireXXX methods need to be public to support othe…
jglick Dec 13, 2019
cf08bcf
JnlpConnectionState.getRemoteEndpointDescription introduced to avoid …
jglick Dec 13, 2019
f2549aa
Do not advertise tyrus-standalone-client-jdk to Maven dependencies, t…
jglick Dec 16, 2019
ee1ecd9
SpotBugs
jglick Dec 17, 2019
50d4df7
Merge branch 'master' of github.com:jenkinsci/remoting into websocket
jglick Dec 17, 2019
21bf706
Shade dependencies needed for agent.jar.
jglick Dec 21, 2019
a54c157
Merge branch 'master' of github.com:jenkinsci/remoting into websocket
jglick Jan 3, 2020
526baab
More intuitive log message, as suggested by @jeffret-b.
jglick Jan 3, 2020
ce68090
FindSecBugs
jglick Jan 3, 2020
f77a3cf
Some updates to documentation to reflect WebSocket mode.
jglick Jan 3, 2020
0682149
Fixed formatting
jglick Jan 3, 2020
87ff8af
Apply suggestions from code review
jglick Jan 4, 2020
84d9d71
Merge branch 'master' of github.com:jenkinsci/remoting into websocket
jglick Jan 6, 2020
d220515
Expect to indicate version in which -webSocket was introduced.
jglick Jan 6, 2020
977bcc1
Introduced constant for X-Remoting-Minimum-Version.
jglick Jan 6, 2020
0cc1903
Merge branch 'websocket' of github.com:jglick/remoting into websocket
jglick Jan 6, 2020
91f8990
Take advantage of args4j declarative depends/forbids. Also reword err…
jglick Jan 6, 2020
739e338
No need to explicitly close connections after EngineListener.error: M…
jglick Jan 6, 2020
7eda193
Implement reconnection in WebSocket mode.
jglick Jan 6, 2020
b779630
Correcting option name.
jglick Jan 6, 2020
c238b7c
FindSecBugs
jglick Jan 6, 2020
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Feel free to contribute.
User documentation:

* [Changelog](CHANGELOG.md) - Remoting release notes
* [Launching inbound TCP agents](docs/tcpAgent.md) - Mechanisms and parameters for launching inbound TCP agents
* [Launching inbound agents](docs/inbound-agent.md) - Mechanisms and parameters for launching inbound agents
* [Remoting Protocols](docs/protocols.md) - Overview of protocols integrated with Jenkins
* [Remoting Configuration](docs/configuration.md) - Configuring remoting agents
* [Logging](docs/logging.md) - Logging
Expand Down
53 changes: 0 additions & 53 deletions assembly-uberjar-14.xml

This file was deleted.

19 changes: 12 additions & 7 deletions docs/tcpAgent.md → docs/inbound-agent.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# Launching inbound TCP agents
# Launching inbound agents
Jenkins provides a number of ways of connecting remote agents.
Two of the most popular are outbound SSH agents and inbound TCP agents.
Two of the most popular are outbound SSH agents and inbound agents.
SSH agents, most commonly used on Unix platforms, are master initiated.
The master creates the connection when it needs.
Inbound TCP agents, most commonly used on Windows platforms, are agent initiated.
Inbound agents, most commonly used on Windows platforms, are agent initiated.
The agent must first connect to the master and then the master sends commands as needed.
These were formerly known as JNLP agents, but that name was erroneous and confusing.
This document describes some of the primary mechanisms for launching inbound TCP agents.
This document describes some of the primary mechanisms for launching inbound agents.

For additional information about Jenkins agents see [Distributed builds](https://wiki.jenkins.io/display/JENKINS/Distributed+builds#Distributedbuilds-HavemasterlaunchagentonWindows).

## Launch mechanisms
Part of the agent status page for TCP agents looks something like this:
Part of the agent status page for inbound agents looks something like this:

![Tcp agent status UI](tcpAgentStatus.jpg)

Expand All @@ -33,7 +33,12 @@ This mechanism is not recommended, is deprecated, and may not be supported or av
### Download JNLP file
Another mechanism, shown in the above status page fragment, runs the agent from a script or command-line to retrieve the JNLP file.
This mechanism does not use JNLP or WebStart but uses the downloaded file to obtain connection information.
Internally, this is a two-step process: 1) connect to an HTTP(S) port to retrieve the connection information, and 2) extract that information and connect to the TCP port.

For an inbound agent using the TCP port, this is a two-step process:
* Connect to an HTTP(S) port to retrieve the connection information.
* Extract that information and connect to the TCP port.

(When using the `-webSocket` option, only a single connection needs to be made.)
jeffret-b marked this conversation as resolved.
Show resolved Hide resolved

Before invoking this command you must download the "agent.jar" file.
The correct version can be obtained from your Jenkins server at something like "https://myjenkins.example.com/jnlpJars/agent.jar".
Expand Down Expand Up @@ -114,7 +119,7 @@ Similar to the usage in a number of other applications, this controls which host
### The '@' argument annotation

If any command-line argument is prepended with the '@' symbol, special behavior is invoked.
For example, more recent versions of Jenkins show in the UI that one form of launching inbound TCP agents is
For example, more recent versions of Jenkins show in the UI that one form of launching inbound agents is
```
echo <secret key> > secret-file
java -jar agent.jar -jnlpUrl <jnlp url> -secret @secret-file -workDir <work directory>
Expand Down
11 changes: 8 additions & 3 deletions docs/protocols.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
Remoting protocols
====

Remoting library provides extension points, which allow implementing custom communication protocols.
For example, Jenkins project defines its own protocols for the CLI client.
The Remoting library provides APIs which allow custom communication protocols to be implemented.

This section describes only the protocols available within the remoting library.

Expand All @@ -27,10 +26,16 @@ If stronger algorithms are needed (for example, AES with 256-bit keys), the [JCE

Protocol uses non-blocking I/O wherever possible which removes the performance bottleneck of the <code>JNLP3-connect</code> protocol.

### WebSocket

jglick marked this conversation as resolved.
Show resolved Hide resolved
* Introduced in: Remoting version TODO, [JEP-222](https://jenkins.io/jep/222)
jeffret-b marked this conversation as resolved.
Show resolved Hide resolved

Uses WebSocket over an HTTP(S) port to handle handshakes, encryption, framing, etc.

## Plugin protocols

### Remoting Kafka Plugin

* [Remoting Kafka Plugin](https://github.com/jenkinsci/remoting-kafka-plugin) uses Kafka as fault-tolerant communication layer to support command invocation between Jenkins master and agent.
* The plugin gets rid of current direct TCP connection between master and agent.
* More info can be found in the technical [documentation](https://github.com/jenkinsci/remoting-kafka-plugin/blob/master/docs/DOCUMENTATION.md) of the plugin.
* More info can be found in the technical [documentation](https://github.com/jenkinsci/remoting-kafka-plugin/blob/master/docs/DOCUMENTATION.md) of the plugin.
118 changes: 59 additions & 59 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ THE SOFTWARE.
<artifactId>animal-sniffer-annotations</artifactId>
<scope>provided</scope>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.glassfish.tyrus.bundles</groupId>
<artifactId>tyrus-standalone-client-jdk</artifactId>
<version>1.12</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down Expand Up @@ -129,7 +134,7 @@ THE SOFTWARE.
<groupId>args4j</groupId>
<artifactId>args4j</artifactId>
<version>2.33</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-io</groupId>
Expand All @@ -148,22 +153,16 @@ THE SOFTWARE.
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<!-- for JRE requirement check annotation -->
<groupId>org.jvnet</groupId>
<artifactId>animal-sniffer-annotation</artifactId>
<version>1.0</version>
<optional>true</optional><!-- no need to have this at runtime -->
</dependency>
<dependency>
<groupId>org.jenkins-ci</groupId>
<artifactId>constant-pool-scanner</artifactId>
<version>1.2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
Expand All @@ -181,13 +180,7 @@ THE SOFTWARE.
<groupId>org.kohsuke</groupId>
<artifactId>access-modifier-annotation</artifactId>
<version>1.12</version>
<type>jar</type>
<optional>true</optional><!-- no need to have this at runtime -->
</dependency>
<dependency>
<groupId>org.jenkins-ci</groupId>
<artifactId>test-annotations</artifactId>
<scope>test</scope>
<scope>provided</scope>
</dependency>
</dependencies>

Expand Down Expand Up @@ -219,25 +212,57 @@ THE SOFTWARE.
<targetJdk>1.${java.level}</targetJdk>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
jeffret-b marked this conversation as resolved.
Show resolved Hide resolved
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>javax.websocket</pattern>
<shadedPattern>io.jenkins.remoting.shaded.javax.websocket</shadedPattern>
</relocation>
<relocation>
<pattern>org.glassfish.tyrus</pattern>
<shadedPattern>io.jenkins.remoting.shaded.org.glassfish.tyrus</shadedPattern>
</relocation>
<relocation>
<pattern>org.kohsuke.args4j</pattern>
<shadedPattern>io.jenkins.remoting.shaded.org.kohsuke.args4j</shadedPattern>
</relocation>
<relocation>
<pattern>org.jenkinsci.constant_pool_scanner</pattern>
<shadedPattern>io.jenkins.remoting.shaded.org.jenkinsci.constant_pool_scanner</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>hudson.remoting.Launcher</mainClass>
<manifestEntries>
<Version>${project.version}</Version>
<!-- attributes related to Java Web Start -->
<!-- see http://docs.oracle.com/javase/8/docs/technotes/guides/jweb/security/manifest.html -->
<Permissions>all-permissions</Permissions>
<Codebase>*</Codebase>
<Application-Name>Jenkins Remoting Agent</Application-Name>
<Trusted-Library>true</Trusted-Library>
</manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<!-- version specified in grandparent pom -->
<configuration>
<archive>
<manifest>
<mainClass>hudson.remoting.Launcher</mainClass>
</manifest>
<manifestEntries>
<Version>${project.version}</Version>
<!-- attributes related to Java Web Start -->
<!-- see http://docs.oracle.com/javase/8/docs/technotes/guides/jweb/security/manifest.html -->
<Permissions>all-permissions</Permissions>
<Codebase>*</Codebase>
<Application-Name>Jenkins Remoting Agent</Application-Name>
<Trusted-Library>true</Trusted-Library>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<id>executable-tests</id>
Expand Down Expand Up @@ -317,31 +342,6 @@ THE SOFTWARE.
</artifactItems>
</configuration>
</execution>
<execution>
<id>bundle-arg4j</id>
<phase>process-classes</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
<includeScope>provided</includeScope>
<includeArtifactIds>args4j</includeArtifactIds>
<includeGroupIds>args4j</includeGroupIds>
</configuration>
</execution>
<execution>
<id>bundle-cps</id>
<phase>process-classes</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
<includeArtifactIds>constant-pool-scanner</includeArtifactIds>
<includeGroupIds>org.jenkins-ci</includeGroupIds>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ public interface ByteArrayReceiver {
*
* As discussed in {@link AbstractByteArrayCommandTransport#writeBlock(Channel, byte[])},
* the block boundary is significant.
* @see CommandReceiver#handle
*/
void handle(byte[] payload);

/**
* See {@link CommandReceiver#handle(Command)} for details.
* @see CommandReceiver#terminate
*/
void terminate(IOException e);
}
Expand Down
38 changes: 36 additions & 2 deletions src/main/java/hudson/remoting/Capability.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.remoting.Channel.Mode;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
Expand All @@ -25,6 +27,12 @@
* @see Channel#remoteCapability
*/
public final class Capability implements Serializable {

/**
* Key usable as a WebSocket HTTP header to negotiate capabilities.
*/
public static final String KEY = "X-Remoting-Capability";

/**
* Bit mask of optional capabilities.
*/
Expand Down Expand Up @@ -121,10 +129,17 @@ public boolean supportsProxyExceptionFallback() {

//TODO: ideally preamble handling needs to be reworked in order to avoid FB suppression
/**
* Writes out the capacity preamble.
* Writes {@link #PREAMBLE} then uses {@link #write}.
*/
void writePreamble(OutputStream os) throws IOException {
os.write(PREAMBLE);
write(os);
}

/**
* Writes this capability to a stream.
*/
private void write(OutputStream os) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(Mode.TEXT.wrap(os)) {
@Override
public void close() throws IOException {
Expand All @@ -144,7 +159,7 @@ protected void annotateClass(Class<?> c) throws IOException {
}

/**
* The opposite operation of {@link #writePreamble(OutputStream)}.
* The opposite operation of {@link #write}.
*/
@SuppressFBWarnings(value = "OBJECT_DESERIALIZATION", justification = "Capability is used for negotiating channel between authorized agent and server. Whitelisting and proper deserialization hygiene are used.")
public static Capability read(InputStream is) throws IOException {
Expand Down Expand Up @@ -173,6 +188,25 @@ public void close() throws IOException {
}
}

/**
* Uses {@link #write} to serialize this object to a Base64-encoded ASCII stream.
*/
public String toASCII() throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
write(baos);
return baos.toString(StandardCharsets.US_ASCII.name());
}
}

/**
* The inverse of {@link #toASCII}.
*/
public static Capability fromASCII(String ascii) throws IOException {
try (ByteArrayInputStream bais = new ByteArrayInputStream(ascii.getBytes(StandardCharsets.US_ASCII))) {
return Capability.read(bais);
}
}

private static final long serialVersionUID = 1L;

/**
Expand Down
Loading