├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.markdown
├── mesos-rxjava-client
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── mesosphere
│ │ └── mesos
│ │ └── rx
│ │ └── java
│ │ ├── AwaitableSubscription.java
│ │ ├── Mesos4xxException.java
│ │ ├── Mesos5xxException.java
│ │ ├── MesosClient.java
│ │ ├── MesosClientBuilder.java
│ │ ├── MesosClientErrorContext.java
│ │ ├── MesosException.java
│ │ ├── ResponseUtils.java
│ │ ├── Rx.java
│ │ ├── SinkOperation.java
│ │ ├── SinkOperations.java
│ │ ├── SinkSubscriber.java
│ │ └── package-info.java
│ └── test
│ ├── java
│ └── com
│ │ └── mesosphere
│ │ └── mesos
│ │ └── rx
│ │ └── java
│ │ ├── MesosClientBackpressureIntegrationTest.java
│ │ ├── MesosClientIntegrationTest.java
│ │ ├── MesosClientTest.java
│ │ ├── ResponseUtilsTest.java
│ │ └── SubscriberDecoratorTest.java
│ └── resources
│ └── logback-test.xml
├── mesos-rxjava-example
├── mesos-rxjava-example-framework
│ ├── README.md
│ ├── pom.xml
│ └── src
│ │ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── mesosphere
│ │ │ │ └── mesos
│ │ │ │ └── rx
│ │ │ │ └── java
│ │ │ │ └── example
│ │ │ │ └── framework
│ │ │ │ └── sleepy
│ │ │ │ ├── Sleepy.java
│ │ │ │ ├── State.java
│ │ │ │ ├── Tuple2.java
│ │ │ │ └── package-info.java
│ │ └── resources
│ │ │ └── logback.xml
│ │ └── test
│ │ ├── java
│ │ └── com
│ │ │ └── mesosphere
│ │ │ └── mesos
│ │ │ └── rx
│ │ │ └── java
│ │ │ └── example
│ │ │ └── framework
│ │ │ └── sleepy
│ │ │ └── SleepySimulationTest.java
│ │ └── resources
│ │ └── logback-test.xml
└── pom.xml
├── mesos-rxjava-protobuf-client
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── mesosphere
│ │ └── mesos
│ │ └── rx
│ │ └── java
│ │ └── protobuf
│ │ ├── ProtoCodec.java
│ │ ├── ProtoUtils.java
│ │ ├── ProtobufMesosClientBuilder.java
│ │ ├── ProtobufMessageCodecs.java
│ │ ├── SchedulerCalls.java
│ │ ├── SchedulerEvents.java
│ │ └── package-info.java
│ └── test
│ └── java
│ └── com
│ └── mesosphere
│ └── mesos
│ └── rx
│ └── java
│ └── protobuf
│ └── ProtobufMessageCodecsTest.java
├── mesos-rxjava-recordio
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── mesosphere
│ │ └── mesos
│ │ └── rx
│ │ └── java
│ │ └── recordio
│ │ ├── RecordIOOperator.java
│ │ └── package-info.java
│ └── test
│ ├── java
│ └── com
│ │ └── mesosphere
│ │ └── mesos
│ │ └── rx
│ │ └── java
│ │ └── recordio
│ │ ├── RecordIOOperatorChunkSizesTest.java
│ │ ├── RecordIOOperatorTest.java
│ │ └── TestingProtos.java
│ └── resources
│ ├── events.bin
│ └── logback-test.xml
├── mesos-rxjava-test
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── mesosphere
│ │ └── mesos
│ │ └── rx
│ │ └── java
│ │ └── test
│ │ ├── Async.java
│ │ ├── RecordIOUtils.java
│ │ ├── StringMessageCodec.java
│ │ ├── TcpSocketProxy.java
│ │ ├── package-info.java
│ │ └── simulation
│ │ ├── AwaitableEventSubscriberDecorator.java
│ │ ├── MesosServerSimulation.java
│ │ └── package-info.java
│ └── test
│ ├── java
│ └── com
│ │ └── mesosphere
│ │ └── mesos
│ │ └── rx
│ │ └── java
│ │ └── test
│ │ ├── AsyncTest.java
│ │ ├── TcpSocketProxyTest.java
│ │ └── simulation
│ │ ├── AwaitableEventSubscriberDecoratorTest.java
│ │ ├── MesosServerSimulationScenariosTest.java
│ │ └── MesosServerSimulationTest.java
│ └── resources
│ └── logback-test.xml
├── mesos-rxjava-util
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── mesosphere
│ │ └── mesos
│ │ └── rx
│ │ └── java
│ │ └── util
│ │ ├── CollectionUtils.java
│ │ ├── MessageCodec.java
│ │ ├── UserAgent.java
│ │ ├── UserAgentEntries.java
│ │ ├── UserAgentEntry.java
│ │ ├── Validations.java
│ │ └── package-info.java
│ └── test
│ └── java
│ └── com
│ └── mesosphere
│ └── mesos
│ └── rx
│ └── java
│ ├── UserAgentTest.java
│ └── util
│ └── CollectionUtilsTest.java
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Maven template
3 | target/
4 | pom.xml.tag
5 | pom.xml.releaseBackup
6 | pom.xml.versionsBackup
7 | pom.xml.next
8 | release.properties
9 | dependency-reduced-pom.xml
10 | buildNumber.properties
11 | .mvn/timing.properties
12 | ### JetBrains template
13 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
14 |
15 | *.iml
16 |
17 | ## Directory-based project format:
18 | .idea/
19 | # if you remove the above rule, at least ignore the following:
20 |
21 | # User-specific stuff:
22 | # .idea/workspace.xml
23 | # .idea/tasks.xml
24 | # .idea/dictionaries
25 |
26 | # Sensitive or high-churn files:
27 | # .idea/dataSources.ids
28 | # .idea/dataSources.xml
29 | # .idea/sqlDataSources.xml
30 | # .idea/dynamic.xml
31 | # .idea/uiDesigner.xml
32 |
33 | # Gradle:
34 | # .idea/gradle.xml
35 | # .idea/libraries
36 |
37 | # Mongo Explorer plugin:
38 | # .idea/mongoSettings.xml
39 |
40 | ## File-based project format:
41 | *.ipr
42 | *.iws
43 |
44 | ## Plugin-specific files:
45 |
46 | # IntelliJ
47 | /out/
48 |
49 | # mpeltonen/sbt-idea plugin
50 | .idea_modules/
51 |
52 | # JIRA plugin
53 | atlassian-ide-plugin.xml
54 |
55 | # Crashlytics plugin (for Android Studio and IntelliJ)
56 | com_crashlytics_export_strings.xml
57 | crashlytics.properties
58 | crashlytics-build.properties
59 | ### Java template
60 | *.class
61 |
62 | # Mobile Tools for Java (J2ME)
63 | .mtj.tmp/
64 |
65 | # Package Files #
66 | *.jar
67 | *.war
68 | *.ear
69 |
70 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
71 | hs_err_pid*
72 |
73 | tmp/
74 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Please follow these steps:
4 |
5 | 1. Fork this repository on GitHub
6 | 1. Clone your fork
7 | 1. Ensure all tests pass before making changes (`mvn test`)
8 | 1. Make your changes on a topic branch off of master
9 | - Write tests for your changes, and make sure *all* tests pass
10 | - Commits should be atomic
11 | - Commit messages should be in the following format:
12 |
13 | Short summary of the change in present tense (e.g. "Add feature ...", "Fix bug ...").
14 |
15 | Longer, more detailed message if additional explanation is needed. This may
16 | span multiple paragraphs.
17 | 1. Push your topic branch to your fork on GitHub
18 | 1. Open a pull request for your branch in this repository
19 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | DEPRECATED
2 |
3 | This project has been moved to the unofficial Mesos GitHub organization. Find the [new repository here](https://github.com/mesos/mesos-rxjava).
4 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
20 | 4.0.0
21 |
22 |
23 | com.mesosphere.mesos.rx.java
24 | mesos-rxjava
25 | 0.2.1-SNAPSHOT
26 |
27 |
28 | mesos-rxjava-client
29 |
30 | Mesos RxJava :: Client
31 |
32 |
33 | false
34 |
35 |
36 |
37 |
38 | com.mesosphere.mesos.rx.java
39 | mesos-rxjava-recordio
40 |
41 |
42 | com.mesosphere.mesos.rx.java
43 | mesos-rxjava-util
44 |
45 |
46 | com.mesosphere.mesos.rx.java
47 | mesos-rxjava-test
48 |
49 |
50 | io.reactivex
51 | rxjava
52 |
53 |
54 | io.reactivex
55 | rxnetty
56 |
57 |
58 | io.netty
59 | netty-codec-http
60 |
61 |
62 | io.netty
63 | netty-handler
64 |
65 |
66 | io.netty
67 | netty-transport-native-epoll
68 |
69 |
70 |
71 | org.slf4j
72 | slf4j-api
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/main/java/com/mesosphere/mesos/rx/java/AwaitableSubscription.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import rx.Subscriber;
20 | import rx.Subscription;
21 |
22 | /**
23 | * A sub-interface of {@link Subscription} that provides the definition of a subscription that can be
24 | * awaited.
25 | *
26 | * This is useful for an application that wants to start up an event stream and block its main thread
27 | * until the event stream has completed.
28 | */
29 | public interface AwaitableSubscription extends Subscription {
30 |
31 | /**
32 | * Blocks the current thread until the underlying {@link rx.Observable} ends.
33 | * If the {@code rx.Observable} ends due to {@link Subscriber#onError(Throwable) onError} the {@code Throwable}
34 | * delivered to {@code onError} will be rethrown. If the {@code rx.Observable} ends by
35 | * {@link Subscriber#onCompleted() onCompleted} the method will complete cleanly.
36 | * @throws Throwable If the stream terminates due to any Throwable, it will be re-thrown from this method
37 | */
38 | void await() throws Throwable;
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/main/java/com/mesosphere/mesos/rx/java/Mesos4xxException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | /**
20 | * This class represents a client error (HTTP 400 series) occurred while sending a request to Mesos.
21 | */
22 | public final class Mesos4xxException extends MesosException {
23 | public Mesos4xxException(final Object originalCall, final MesosClientErrorContext context) {
24 | super(originalCall, context);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/main/java/com/mesosphere/mesos/rx/java/Mesos5xxException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | /**
20 | * This class represents a server error (HTTP 500 series) occurred while sending a request to Mesos.
21 | */
22 | public final class Mesos5xxException extends MesosException {
23 | public Mesos5xxException(final Object originalCall, final MesosClientErrorContext context) {
24 | super(originalCall, context);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/main/java/com/mesosphere/mesos/rx/java/MesosClientErrorContext.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 |
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.Objects;
24 |
25 | /**
26 | * This class represents the context of a request that is made to Mesos that resulted in an error.
27 | * Including the status code of the HTTP Request, response headers and content body message if
28 | * present in the response.
29 | */
30 | public final class MesosClientErrorContext {
31 |
32 | private final int statusCode;
33 | private final String message;
34 | private final List> headers;
35 |
36 | public MesosClientErrorContext(final int statusCode, final List> headers) {
37 | this(statusCode, "", headers);
38 | }
39 |
40 | public MesosClientErrorContext(final int statusCode, final String message, final List> headers) {
41 | this.statusCode = statusCode;
42 | this.message = message;
43 | this.headers = headers;
44 | }
45 |
46 | /**
47 | * @return The statusCode from the HTTP response
48 | */
49 | public int getStatusCode() {
50 | return statusCode;
51 | }
52 |
53 | /**
54 | * @return The content body message if present in the response
55 | */
56 | public String getMessage() {
57 | return message;
58 | }
59 |
60 | /**
61 | * @return The headers returned in the HTTP response
62 | */
63 | public List> getHeaders() {
64 | return headers;
65 | }
66 |
67 | @Override
68 | public boolean equals(final Object o) {
69 | if (this == o) {
70 | return true;
71 | }
72 | if (o == null || getClass() != o.getClass()) {
73 | return false;
74 | }
75 | final MesosClientErrorContext that = (MesosClientErrorContext) o;
76 | return statusCode == that.statusCode &&
77 | Objects.equals(message, that.message) &&
78 | Objects.equals(headers, that.headers);
79 | }
80 |
81 | @Override
82 | public int hashCode() {
83 | return Objects.hash(statusCode, message, headers);
84 | }
85 |
86 | @Override
87 | public String toString() {
88 | return "MesosClientErrorContext{" +
89 | "statusCode=" + statusCode +
90 | ", message='" + message + '\'' +
91 | '}';
92 | }
93 |
94 | /**
95 | * Close {@code this} context replacing the existing message with {@code message}
96 | * @param message The message to include in the new {@code MesosClientErrorContext}
97 | * @return A new {@code MesosClientErrorContext} with its message set to {@code message}
98 | */
99 | @NotNull
100 | public MesosClientErrorContext withMessage(@NotNull final String message) {
101 | return new MesosClientErrorContext(this.statusCode, message, this.headers);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/main/java/com/mesosphere/mesos/rx/java/MesosException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | /**
20 | * This class represents an error occurred while sending a request to Mesos.
21 | */
22 | public class MesosException extends RuntimeException {
23 | private final Object originalCall;
24 | private final MesosClientErrorContext context;
25 |
26 | /**
27 | * Constructor used to create a new instance
28 | * @param originalCall The original object that was sent to Mesos.
29 | * @param context The response context built from the Mesos response.
30 | */
31 | public MesosException(final Object originalCall, final MesosClientErrorContext context) {
32 | super(
33 | "Error while trying to send request."
34 | + " Status: " + context.getStatusCode()
35 | + " Message: '" + context.getMessage() + "'"
36 | );
37 | this.originalCall = originalCall;
38 | this.context = context;
39 | }
40 |
41 | /**
42 | * The original object that was sent to Mesos.
43 | * @return The original object that was sent to Mesos.
44 | */
45 | public Object getOriginalCall() {
46 | return originalCall;
47 | }
48 |
49 | /**
50 | * The response context built from the Mesos response.
51 | * @return The response context built from the Mesos response.
52 | */
53 | public MesosClientErrorContext getContext() {
54 | return context;
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/main/java/com/mesosphere/mesos/rx/java/ResponseUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import io.netty.buffer.ByteBuf;
20 | import io.netty.handler.codec.http.HttpHeaderNames;
21 | import io.reactivex.netty.protocol.http.client.HttpClientResponse;
22 | import io.reactivex.netty.protocol.http.client.HttpResponseHeaders;
23 | import org.jetbrains.annotations.NotNull;
24 | import rx.Observable;
25 |
26 | import java.nio.charset.StandardCharsets;
27 |
28 | final class ResponseUtils {
29 |
30 | private ResponseUtils() {}
31 |
32 | /**
33 | * Attempts to read the content of an error response as {@code text/plain;charset=utf-8}, otherwise the content
34 | * will be ignored and a string detailing the Content-Type that was not processed.
35 | *
36 | * NOTE:
37 | *
38 | * This method MUST be called from the netty-io thread otherwise the content of the response will not be
39 | * available because if will be released automatically as soon as the netty-io thread is left.
40 | *
41 | * @param resp The response to attempt to read from
42 | * @return An {@link Observable} representing the {@code text/plain;charset=utf-8} response content if it existed
43 | * or an error message indicating the content-type that was not attempted to read.
44 | */
45 | @NotNull
46 | static Observable attemptToReadErrorResponse(@NotNull final HttpClientResponse resp) {
47 | final HttpResponseHeaders headers = resp.getHeaders();
48 | final String contentType = resp.getHeaders().get(HttpHeaderNames.CONTENT_TYPE);
49 | if (headers.isContentLengthSet() && headers.getContentLength() > 0 ) {
50 | if (contentType != null && contentType.startsWith("text/plain")) {
51 | return resp.getContent()
52 | .map(r -> r.toString(StandardCharsets.UTF_8));
53 | } else {
54 | resp.ignoreContent();
55 | final String errMsg = getErrMsg(contentType);
56 | return Observable.just(errMsg);
57 | }
58 | } else {
59 | return Observable.just("");
60 | }
61 | }
62 |
63 | private static String getErrMsg(final String contentType) {
64 | if (contentType == null) {
65 | return "Not attempting to decode error response with unspecified Content-Type";
66 | }
67 | return String.format("Not attempting to decode error response of type '%s' as string", contentType);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/main/java/com/mesosphere/mesos/rx/java/Rx.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 | import rx.Scheduler;
21 | import rx.schedulers.Schedulers;
22 |
23 | /**
24 | * A set of utilities related to Rx.
25 | */
26 | public final class Rx {
27 |
28 | private Rx() {}
29 |
30 | /**
31 | * This method will return the {@link Scheduler} that is used for all "computation" workloads.
32 | *
33 | * The idea of a computation workload is one that is CPU bound.
34 | * @return The {@link Scheduler} that is to be used for all CPU bound workloads.
35 | */
36 | @NotNull
37 | public static Scheduler compute() {
38 | return Schedulers.computation();
39 | }
40 |
41 | /**
42 | * This method will return the {@link Scheduler} that is used for all "io" workloads.
43 | *
44 | * The idea of a io workload is one that is IO bound (disk, network, etc.).
45 | * @return The {@link Scheduler} that is to be used for all IO bound workloads.
46 | */
47 | @NotNull
48 | public static Scheduler io() {
49 | return Schedulers.io();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/main/java/com/mesosphere/mesos/rx/java/SinkOperation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 | import rx.functions.Action0;
21 | import rx.functions.Action1;
22 |
23 | import static com.mesosphere.mesos.rx.java.util.Validations.checkNotNull;
24 |
25 | /**
26 | * A simple object that represents a {@link T} that is to be sent to Mesos.
27 | *
28 | * For example, when communicating with the Mesos HTTP Scheduler API, any
29 | * Call
30 | * that isn't a
31 | * SUBSCRIBE
32 | * will result in a semi-blocking request.
33 | *
34 | * This means things like request validation (including body deserialization and field validation) are
35 | * performed synchronously during the request. Due to this behavior, this class exists to inform the
36 | * user of the success or failure of requests sent to the master.
37 | *
38 | * It should be noted that this object doesn't represent the means of detecting and handling connection errors
39 | * with Mesos. The intent is that it will be communicated to the whole event stream rather than an
40 | * individual {@code SinkOperation}.
41 | *
42 | * NOTE
43 | * The semantics of which thread a callback ({@code onComplete} or {@code onError}) will be invoked on are undefined
44 | * and should not be relied upon. This means that all standard thread safety/guards should be in place for the actions
45 | * performed inside the callback.
46 | *
47 | * @see SinkOperations#create
48 | */
49 | public final class SinkOperation {
50 | @NotNull
51 | private final T thingToSink;
52 | @NotNull
53 | private final Action1 onError;
54 | @NotNull
55 | private final Action0 onCompleted;
56 |
57 | /**
58 | * This constructor is considered an internal API and should not be used directly, instead use one of the
59 | * factory methods defined in {@link SinkOperations}.
60 | * @param thingToSink The {@link T} to send to Mesos
61 | * @param onCompleted The callback invoked when HTTP 202 is returned by Mesos
62 | * @param onError The callback invoked for an HTTP 400 or 500 status code returned by Mesos
63 | */
64 | SinkOperation(
65 | @NotNull final T thingToSink,
66 | @NotNull final Action0 onCompleted,
67 | @NotNull final Action1 onError
68 | ) {
69 | this.thingToSink = checkNotNull(thingToSink, "argument thingToSink can not be null");
70 | this.onCompleted = checkNotNull(onCompleted, "argument onCompleted can not be null");
71 | this.onError = checkNotNull(onError, "argument onError can not be null");
72 | }
73 |
74 | public void onCompleted() {
75 | onCompleted.call();
76 | }
77 |
78 | public void onError(final Throwable e) {
79 | onError.call(e);
80 | }
81 |
82 | @NotNull
83 | public T getThingToSink() {
84 | return thingToSink;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/main/java/com/mesosphere/mesos/rx/java/SinkOperations.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 | import rx.functions.Action0;
21 | import rx.functions.Action1;
22 |
23 | /**
24 | * This class provides the set of methods that can be used to create {@link SinkOperation}s
25 | */
26 | public final class SinkOperations {
27 |
28 | @NotNull
29 | private static final Action1 ERROR_NO_OP = (t) -> {};
30 | @NotNull
31 | private static final Action0 COMPLETED_NO_OP = () -> {};
32 |
33 | private SinkOperations() {}
34 |
35 | /**
36 | * Creates a new {@link SinkOperation}.
37 | * @param thing The message to be sent to Mesos.
38 | * @param onCompleted The callback to be invoked upon a 202 response from Mesos.
39 | * @param onError The callback to be invoked upon a 4xx or 5xx response from Mesos.
40 | * @param The type of the message to be sent to Mesos.
41 | * @return A new {@link SinkOperation} that can be sent to Mesos.
42 | */
43 | @NotNull
44 | public static SinkOperation create(
45 | @NotNull final T thing,
46 | @NotNull final Action0 onCompleted,
47 | @NotNull final Action1 onError
48 | ) {
49 | return new SinkOperation<>(thing, onCompleted, onError);
50 | }
51 | /**
52 | * Creates a new {@link SinkOperation}.
53 | * @param thing The message to be sent to Mesos.
54 | * @param onCompleted The callback to be invoked upon a 202 response from Mesos.
55 | * @param The type of the message to be sent to Mesos.
56 | * @return A new {@link SinkOperation} that can be sent to Mesos.
57 | */
58 | @NotNull
59 | public static SinkOperation create(
60 | @NotNull final T thing,
61 | @NotNull final Action0 onCompleted
62 | ) {
63 | return create(thing, onCompleted, ERROR_NO_OP);
64 | }
65 |
66 | /**
67 | * Creates a new {@link SinkOperation}.
68 | * @param thing The message to be sent to Mesos.
69 | * @param onError The callback to be invoked upon a 4xx or 5xx response from Mesos.
70 | * @param The type of the message to be sent to Mesos.
71 | * @return A new {@link SinkOperation} that can be sent to Mesos.
72 | */
73 | @NotNull
74 | public static SinkOperation create(
75 | @NotNull final T thing,
76 | @NotNull final Action1 onError
77 | ) {
78 | return create(thing, COMPLETED_NO_OP, onError);
79 | }
80 |
81 | /**
82 | * Creates a new {@link SinkOperation}.
83 | * @param thing The message to be sent to Mesos.
84 | * @param The type of the message to be sent to Mesos.
85 | * @return A new {@link SinkOperation} that can be sent to Mesos.
86 | */
87 | @NotNull
88 | public static SinkOperation create(@NotNull final T thing) {
89 | return create(thing, COMPLETED_NO_OP, ERROR_NO_OP);
90 | }
91 |
92 | /**
93 | * Creates a new {@link SinkOperation}.
94 | * @param thing The message to be sent to Mesos.
95 | * @param onCompleted The callback to be invoked upon a 202 response from Mesos.
96 | * @param onError The callback to be invoked upon a 4xx or 5xx response from Mesos.
97 | * @param The type of the message to be sent to Mesos.
98 | * @return A new {@link SinkOperation} that can be sent to Mesos.
99 | * @see #create(Object, Action0, Action1)
100 | */
101 | @NotNull
102 | public static SinkOperation sink(
103 | @NotNull final T thing,
104 | @NotNull final Action0 onCompleted,
105 | @NotNull final Action1 onError
106 | ) {
107 | return create(thing, onCompleted, onError);
108 | }
109 |
110 | /**
111 | * Creates a new {@link SinkOperation}.
112 | * @param thing The message to be sent to Mesos.
113 | * @param onCompleted The callback to be invoked upon a 202 response from Mesos.
114 | * @param The type of the message to be sent to Mesos.
115 | * @return A new {@link SinkOperation} that can be sent to Mesos.
116 | * @see #create(Object, Action0)
117 | */
118 | @NotNull
119 | public static SinkOperation sink(
120 | @NotNull final T thing,
121 | @NotNull final Action0 onCompleted
122 | ) {
123 | return create(thing, onCompleted);
124 | }
125 |
126 | /**
127 | * Creates a new {@link SinkOperation}.
128 | * @param thing The message to be sent to Mesos.
129 | * @param onError The callback to be invoked upon a 4xx or 5xx response from Mesos.
130 | * @param The type of the message to be sent to Mesos.
131 | * @return A new {@link SinkOperation} that can be sent to Mesos.
132 | * @see #create(Object, Action1)
133 | */
134 | @NotNull
135 | public static SinkOperation sink(
136 | @NotNull final T thing,
137 | @NotNull final Action1 onError
138 | ) {
139 | return create(thing, onError);
140 | }
141 |
142 | /**
143 | * Creates a new {@link SinkOperation}.
144 | * @param thing The message to be sent to Mesos.
145 | * @param The type of the message to be sent to Mesos.
146 | * @return A new {@link SinkOperation} that can be sent to Mesos.
147 | * @see #create(Object)
148 | */
149 | @NotNull
150 | public static SinkOperation sink(
151 | @NotNull final T thing
152 | ) {
153 | return create(thing);
154 | }
155 |
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/main/java/com/mesosphere/mesos/rx/java/SinkSubscriber.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import io.netty.buffer.ByteBuf;
20 | import io.netty.handler.codec.http.HttpResponseStatus;
21 | import io.reactivex.netty.protocol.http.client.HttpClient;
22 | import io.reactivex.netty.protocol.http.client.HttpClientRequest;
23 | import io.reactivex.netty.protocol.http.client.HttpResponseHeaders;
24 | import org.jetbrains.annotations.NotNull;
25 | import rx.Observable;
26 | import rx.Subscriber;
27 | import rx.exceptions.Exceptions;
28 | import rx.functions.Func1;
29 |
30 | import java.util.List;
31 | import java.util.Map;
32 | import java.util.Optional;
33 |
34 | final class SinkSubscriber extends Subscriber> {
35 |
36 | @NotNull
37 | private final HttpClient httpClient;
38 | @NotNull
39 | private final Func1>> createPost;
40 |
41 | SinkSubscriber(
42 | @NotNull final HttpClient httpClient,
43 | @NotNull final Func1>> createPost
44 | ) {
45 | this.httpClient = httpClient;
46 | this.createPost = createPost;
47 | }
48 |
49 | @Override
50 | public void onNext(final SinkOperation op) {
51 | try {
52 | final Send toSink = op.getThingToSink();
53 | createPost.call(toSink)
54 | .flatMap(httpClient::submit)
55 | .flatMap(resp -> {
56 | final HttpResponseStatus status = resp.getStatus();
57 | final int code = status.code();
58 |
59 | if (code == 202) {
60 | /* This is success */
61 | return Observable.just(Optional.empty());
62 | } else {
63 | final HttpResponseHeaders headers = resp.getHeaders();
64 | return ResponseUtils.attemptToReadErrorResponse(resp)
65 | .map(msg -> {
66 | final List> entries = headers.entries();
67 | final MesosClientErrorContext context = new MesosClientErrorContext(code, msg, entries);
68 | MesosException error;
69 | if (400 <= code && code < 500) {
70 | // client error
71 | error = new Mesos4xxException(toSink, context);
72 | } else if (500 <= code && code < 600) {
73 | // client error
74 | error = new Mesos5xxException(toSink, context);
75 | } else {
76 | // something else that isn't success but not an error as far as http is concerned
77 | error = new MesosException(toSink, context);
78 | }
79 | return Optional.of(error);
80 | });
81 | }
82 | })
83 | .observeOn(Rx.compute())
84 | .subscribe(exception -> {
85 | if (!exception.isPresent()) {
86 | op.onCompleted();
87 | } else {
88 | op.onError(exception.get());
89 | }
90 | });
91 | } catch (Throwable e) {
92 | Exceptions.throwIfFatal(e);
93 | op.onError(e);
94 | }
95 | }
96 |
97 | @Override
98 | public void onError(final Throwable e) {
99 | Exceptions.throwIfFatal(e);
100 | }
101 |
102 | @Override
103 | public void onCompleted() {
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/main/java/com/mesosphere/mesos/rx/java/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * This package contains defines a client used to interact with a Mesos HTTP API such as the
18 | * Scheduler HTTP API.
19 | *
20 | * Design
21 | * This library is designed to allow a user to interact with Mesos by defining a function that receives a stream
22 | * ({@link rx.Observable Observable}) of
23 | * Events
24 | * from Mesos potentially emitting events of it's own that will be sent to Mesos.
25 | *
26 | * However this function is implemented doesn't matter to the library, as long as the function contract is upheld.
27 | *
28 | * This module is ignorant of the actual messages being sent and received from Mesos and the serialization mechanism
29 | * used. Instead, the user provides a {@link com.mesosphere.mesos.rx.java.util.MessageCodec MessageCodec} for each
30 | * of the corresponding messages. The advantage of this is that the client can stay buffered from message changes
31 | * made my Mesos as well as serialization used (the package
32 | * com.mesosphere.mesos.rx.java.protobuf provides the codecs necessary to use
33 | * protobuf when connecting to mesos).
34 | */
35 | package com.mesosphere.mesos.rx.java;
36 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/test/java/com/mesosphere/mesos/rx/java/MesosClientBackpressureIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import com.mesosphere.mesos.rx.java.test.StringMessageCodec;
20 | import com.mesosphere.mesos.rx.java.util.UserAgentEntries;
21 | import io.netty.buffer.ByteBuf;
22 | import io.netty.handler.codec.http.HttpResponseStatus;
23 | import io.reactivex.netty.RxNetty;
24 | import io.reactivex.netty.protocol.http.server.HttpServer;
25 | import io.reactivex.netty.protocol.http.server.HttpServerResponse;
26 | import io.reactivex.netty.protocol.http.server.RequestHandler;
27 | import org.junit.Ignore;
28 | import org.junit.Rule;
29 | import org.junit.Test;
30 | import org.junit.rules.Timeout;
31 | import org.slf4j.Logger;
32 | import org.slf4j.LoggerFactory;
33 | import rx.exceptions.MissingBackpressureException;
34 |
35 | import java.net.URI;
36 | import java.nio.charset.StandardCharsets;
37 | import java.util.Optional;
38 | import java.util.concurrent.TimeUnit;
39 |
40 | import static org.assertj.core.api.Assertions.assertThat;
41 | import static org.junit.Assert.assertEquals;
42 | import static org.junit.Assert.fail;
43 |
44 | public final class MesosClientBackpressureIntegrationTest {
45 |
46 | private static final Logger LOGGER = LoggerFactory.getLogger(MesosClientBackpressureIntegrationTest.class);
47 |
48 | static int msgNo = 0;
49 |
50 | @Rule
51 | public Timeout timeoutRule = new Timeout(10_000, TimeUnit.MILLISECONDS);
52 |
53 | @Test
54 | @Ignore
55 | public void testBurstyObservable_missingBackpressureException() throws Throwable {
56 | final String subscribedMessage = "{\"type\": \"SUBSCRIBED\",\"subscribed\": {\"framework_id\": {\"value\":\"12220-3440-12532-2345\"},\"heartbeat_interval_seconds\":15.0}";
57 | final String heartbeatMessage = "{\"type\":\"HEARTBEAT\"}";
58 | final byte[] hmsg = heartbeatMessage.getBytes(StandardCharsets.UTF_8);
59 | final byte[] hbytes = String.format("%d\n", heartbeatMessage.getBytes().length).getBytes(StandardCharsets.UTF_8);
60 |
61 | final RequestHandler handler = (request, response) -> {
62 | response.setStatus(HttpResponseStatus.OK);
63 | response.getHeaders().setHeader("Content-Type", "text/plain;charset=utf-8");
64 | writeRecordIOMessage(response, subscribedMessage);
65 | for (int i = 0; i < 20000; i++) {
66 | response.writeBytes(hbytes);
67 | response.writeBytes(hmsg);
68 | }
69 | return response.flush();
70 | };
71 | final HttpServer server = RxNetty.createHttpServer(0, handler);
72 | server.start();
73 | final URI uri = URI.create(String.format("http://localhost:%d/api/v1/scheduler", server.getServerPort()));
74 | final MesosClient client = createClientForStreaming(uri).build();
75 |
76 | try {
77 | client.openStream().await();
78 | fail("Expect an exception to be propagated up due to backpressure");
79 | } catch (MissingBackpressureException e) {
80 | // expected
81 | e.printStackTrace();
82 | assertThat(e.getMessage()).isNullOrEmpty();
83 | } finally {
84 | server.shutdown();
85 | }
86 | }
87 |
88 | @Test
89 | public void testBurstyObservable_unboundedBufferSucceeds() throws Throwable {
90 | msgNo = 0;
91 | final int numMessages = 20000;
92 | final String subscribedMessage = "{\"type\": \"SUBSCRIBED\",\"subscribed\": {\"framework_id\": {\"value\":\"12220-3440-12532-2345\"},\"heartbeat_interval_seconds\":15.0}";
93 | final String heartbeatMessage = "{\"type\":\"HEARTBEAT\"}";
94 | final RequestHandler handler = (request, response) -> {
95 | response.setStatus(HttpResponseStatus.OK);
96 | response.getHeaders().setHeader("Content-Type", "text/plain;charset=utf-8");
97 | writeRecordIOMessage(response, subscribedMessage);
98 | for (int i = 0; i < numMessages; i++) {
99 | writeRecordIOMessage(response, heartbeatMessage);
100 | }
101 | return response.close();
102 | };
103 | final HttpServer server = RxNetty.createHttpServer(0, handler);
104 | server.start();
105 | final URI uri = URI.create(String.format("http://localhost:%d/api/v1/scheduler", server.getServerPort()));
106 | final MesosClient client = createClientForStreaming(uri)
107 | .onBackpressureBuffer()
108 | .build();
109 |
110 | try {
111 | client.openStream().await();
112 | } finally {
113 | // 20000 heartbeats PLUS 1 subscribe
114 | assertEquals("All heartbeats received (plus the subscribed)", 1 + numMessages, msgNo);
115 | server.shutdown();
116 | }
117 | }
118 |
119 | private void writeRecordIOMessage(HttpServerResponse response, String msg) {
120 | response.writeBytesAndFlush(String.format("%d\n", msg.getBytes().length).getBytes(StandardCharsets.UTF_8));
121 | response.writeBytesAndFlush(msg.getBytes(StandardCharsets.UTF_8));
122 | }
123 |
124 | private static MesosClientBuilder createClientForStreaming(final URI uri) {
125 | return MesosClientBuilder.newBuilder()
126 | .sendCodec(StringMessageCodec.UTF8_STRING)
127 | .receiveCodec(StringMessageCodec.UTF8_STRING)
128 | .mesosUri(uri)
129 | .applicationUserAgentEntry(UserAgentEntries.literal("test", "test"))
130 | .processStream(events ->
131 | events
132 | .doOnNext(e -> LOGGER.debug(++msgNo + " : " + e))
133 | .map(e -> Optional.empty()))
134 | .subscribe("subscribe");
135 | }
136 |
137 | }
138 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/test/java/com/mesosphere/mesos/rx/java/MesosClientIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import com.mesosphere.mesos.rx.java.test.StringMessageCodec;
20 | import com.mesosphere.mesos.rx.java.util.UserAgentEntries;
21 | import io.netty.buffer.ByteBuf;
22 | import io.netty.handler.codec.http.HttpResponseStatus;
23 | import io.reactivex.netty.RxNetty;
24 | import io.reactivex.netty.protocol.http.server.HttpServer;
25 | import io.reactivex.netty.protocol.http.server.RequestHandler;
26 | import org.jetbrains.annotations.NotNull;
27 | import org.junit.Rule;
28 | import org.junit.Test;
29 | import org.junit.rules.Timeout;
30 |
31 | import java.net.URI;
32 | import java.nio.charset.StandardCharsets;
33 | import java.util.Optional;
34 | import java.util.concurrent.TimeUnit;
35 |
36 | import static org.assertj.core.api.Assertions.assertThat;
37 | import static org.junit.Assert.fail;
38 |
39 | public final class MesosClientIntegrationTest {
40 |
41 | @Rule
42 | public Timeout timeoutRule = new Timeout(5_000, TimeUnit.MILLISECONDS);
43 |
44 | @Test
45 | public void testStreamDoesNotRunWhenSubscribeFails_mesos4xxResponse() throws Throwable {
46 | final String errorMessage = "Error message that should come from the server";
47 | final RequestHandler handler = (request, response) -> {
48 | response.setStatus(HttpResponseStatus.BAD_REQUEST);
49 | final byte[] msgBytes = errorMessage.getBytes(StandardCharsets.UTF_8);
50 | response.getHeaders().setHeader("Content-Length", msgBytes.length);
51 | response.getHeaders().setHeader("Content-Type", "text/plain;charset=utf-8");
52 | response.writeBytes(msgBytes);
53 | return response.close();
54 | };
55 | final HttpServer server = RxNetty.createHttpServer(0, handler);
56 | server.start();
57 | final URI uri = URI.create(String.format("http://localhost:%d/api/v1/scheduler", server.getServerPort()));
58 | final MesosClient client = createClient(uri);
59 |
60 | try {
61 | client.openStream().await();
62 | fail("Expect an exception to be propagated up because subscribe will 400");
63 | } catch (Mesos4xxException e) {
64 | // expected
65 | final MesosClientErrorContext ctx = e.getContext();
66 | assertThat(ctx.getStatusCode()).isEqualTo(400);
67 | assertThat(ctx.getMessage()).isEqualTo(errorMessage);
68 | } finally {
69 | server.shutdown();
70 | }
71 | }
72 |
73 | @Test
74 | public void testStreamDoesNotRunWhenSubscribeFails_nonTextResponseBodyNotRead() throws Throwable {
75 | final RequestHandler handler = (request, response) -> {
76 | response.setStatus(HttpResponseStatus.BAD_REQUEST);
77 | response.getHeaders().setHeader("Content-Length", 1);
78 | response.getHeaders().setHeader("Content-Type", "application/octet-stream");
79 | response.writeBytes(new byte[]{0b1});
80 | return response.close();
81 | };
82 | final HttpServer server = RxNetty.createHttpServer(0, handler);
83 | server.start();
84 | final URI uri = URI.create(String.format("http://localhost:%d/api/v1/scheduler", server.getServerPort()));
85 | final MesosClient client = createClient(uri);
86 |
87 | try {
88 | client.openStream().await();
89 | fail("Expect an exception to be propagated up because subscribe will 400");
90 | } catch (Mesos4xxException e) {
91 | // expected
92 | final MesosClientErrorContext ctx = e.getContext();
93 | assertThat(ctx.getStatusCode()).isEqualTo(400);
94 | assertThat(ctx.getMessage()).isEqualTo("Not attempting to decode error response of type 'application/octet-stream' as string");
95 | } finally {
96 | server.shutdown();
97 | }
98 | }
99 |
100 | @Test
101 | public void testStreamDoesNotRunWhenSubscribeFails_mesos5xxResponse() throws Throwable {
102 | final RequestHandler handler = (request, response) -> {
103 | response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
104 | return response.close();
105 | };
106 | final HttpServer server = RxNetty.createHttpServer(0, handler);
107 | server.start();
108 | final URI uri = URI.create(String.format("http://localhost:%d/api/v1/scheduler", server.getServerPort()));
109 | final MesosClient client = createClient(uri);
110 |
111 | try {
112 | client.openStream().await();
113 | fail("Expect an exception to be propagated up because subscribe will 500");
114 | } catch (Mesos5xxException e) {
115 | // expected
116 | final MesosClientErrorContext ctx = e.getContext();
117 | assertThat(ctx.getStatusCode()).isEqualTo(500);
118 | } finally {
119 | server.shutdown();
120 | }
121 | }
122 |
123 | @Test
124 | public void testStreamDoesNotRunWhenSubscribeFails_mismatchContentType() throws Throwable {
125 | final RequestHandler handler = (request, response) -> {
126 | response.setStatus(HttpResponseStatus.OK);
127 | response.getHeaders().setHeader("Content-Type", "application/json");
128 | return response.close();
129 | };
130 | final HttpServer server = RxNetty.createHttpServer(0, handler);
131 | server.start();
132 | final URI uri = URI.create(String.format("http://localhost:%d/api/v1/scheduler", server.getServerPort()));
133 | final MesosClient client = createClient(uri);
134 |
135 | try {
136 | client.openStream().await();
137 | fail("Expect an exception to be propagated up because of content type mismatch");
138 | } catch (MesosException e) {
139 | // expected
140 | final MesosClientErrorContext ctx = e.getContext();
141 | assertThat(ctx.getStatusCode()).isEqualTo(200);
142 | assertThat(ctx.getMessage()).isEqualTo("Response had Content-Type \"application/json\" expected \"text/plain;charset=utf-8\"");
143 | } finally {
144 | server.shutdown();
145 | }
146 | }
147 |
148 | @NotNull
149 | private static MesosClient createClient(final URI uri) {
150 | return MesosClientBuilder.newBuilder()
151 | .sendCodec(StringMessageCodec.UTF8_STRING)
152 | .receiveCodec(StringMessageCodec.UTF8_STRING)
153 | .mesosUri(uri)
154 | .applicationUserAgentEntry(UserAgentEntries.literal("test", "test"))
155 | .processStream(events ->
156 | events
157 | .doOnNext(e -> fail("event stream should never start"))
158 | .map(e -> Optional.empty()))
159 | .subscribe("subscribe")
160 | .build();
161 | }
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/test/java/com/mesosphere/mesos/rx/java/ResponseUtilsTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import io.netty.buffer.ByteBuf;
20 | import io.netty.buffer.Unpooled;
21 | import io.netty.handler.codec.http.DefaultHttpResponse;
22 | import io.netty.handler.codec.http.HttpHeaders;
23 | import io.netty.handler.codec.http.HttpResponseStatus;
24 | import io.netty.handler.codec.http.HttpVersion;
25 | import io.reactivex.netty.protocol.http.UnicastContentSubject;
26 | import io.reactivex.netty.protocol.http.client.HttpClientResponse;
27 | import org.jetbrains.annotations.NotNull;
28 | import org.junit.Test;
29 | import rx.functions.Action1;
30 |
31 | import java.nio.charset.StandardCharsets;
32 | import java.util.concurrent.TimeUnit;
33 |
34 | import static org.assertj.core.api.Assertions.assertThat;
35 |
36 | public class ResponseUtilsTest {
37 |
38 | @Test
39 | public void attemptToReadErrorResponse_plainString() throws Exception {
40 | final String errMsg = "some response";
41 | final byte[] bytes = errMsg.getBytes(StandardCharsets.UTF_8);
42 | final HttpClientResponse resp = response(Unpooled.copiedBuffer(bytes), (headers) -> {
43 | headers.add("Content-Type", "text/plain;charset=utf-8");
44 | headers.add("Content-Length", bytes.length);
45 | });
46 |
47 | final String err = ResponseUtils.attemptToReadErrorResponse(resp).toBlocking().first();
48 | assertThat(err).isEqualTo(errMsg);
49 | }
50 |
51 | @Test
52 | public void attemptToReadErrorResponse_noContentLength() throws Exception {
53 | final String errMsg = "some response";
54 | final byte[] bytes = errMsg.getBytes(StandardCharsets.UTF_8);
55 | final HttpClientResponse resp = response(Unpooled.copiedBuffer(bytes), (headers) ->
56 | headers.add("Content-Type", "text/plain;charset=utf-8")
57 | );
58 |
59 | final String err = ResponseUtils.attemptToReadErrorResponse(resp).toBlocking().first();
60 | assertThat(err).isEqualTo("");
61 | }
62 |
63 | @Test
64 | public void attemptToReadErrorResponse_noContentType() throws Exception {
65 | final String errMsg = "some response";
66 | final byte[] bytes = errMsg.getBytes(StandardCharsets.UTF_8);
67 | final HttpClientResponse resp = response(Unpooled.copiedBuffer(bytes), (headers) ->
68 | headers.add("Content-Length", bytes.length)
69 | );
70 |
71 | final String err = ResponseUtils.attemptToReadErrorResponse(resp).toBlocking().first();
72 | assertThat(err).isEqualTo("Not attempting to decode error response with unspecified Content-Type");
73 | }
74 |
75 | @Test
76 | public void attemptToReadErrorResponse_contentLengthIsZero() throws Exception {
77 | final HttpClientResponse resp = response(Unpooled.copiedBuffer(new byte[]{}), (headers) ->
78 | headers.add("Content-Length", "0")
79 | );
80 |
81 | final String err = ResponseUtils.attemptToReadErrorResponse(resp).toBlocking().first();
82 | assertThat(err).isEqualTo("");
83 | }
84 |
85 | @Test
86 | public void attemptToReadErrorResponse_nonTextPlain() throws Exception {
87 | final String errMsg = "{\"some\":\"json\"}";
88 | final byte[] bytes = errMsg.getBytes(StandardCharsets.UTF_8);
89 | final HttpClientResponse resp = response(Unpooled.copiedBuffer(bytes), (headers) -> {
90 | headers.add("Content-Type", "application/json;charset=utf-8");
91 | headers.add("Content-Length", bytes.length);
92 | });
93 |
94 | final String err = ResponseUtils.attemptToReadErrorResponse(resp).toBlocking().first();
95 | assertThat(err).isEqualTo("Not attempting to decode error response of type 'application/json;charset=utf-8' as string");
96 | }
97 |
98 | @Test
99 | public void attemptToReadErrorResponse_responseContentIgnoredByDefaultWhenNotString() throws Exception {
100 | final String errMsg = "lies";
101 | final byte[] bytes = errMsg.getBytes(StandardCharsets.UTF_8);
102 | final HttpClientResponse resp = response(Unpooled.copiedBuffer(bytes), (headers) -> {
103 | headers.add("Content-Type", "application/json;charset=utf-8");
104 | headers.add("Content-Length", bytes.length);
105 | });
106 |
107 | final String err = ResponseUtils.attemptToReadErrorResponse(resp).toBlocking().first();
108 | assertThat(err).isNotEqualTo("lies");
109 |
110 | try {
111 | resp.getContent().toBlocking().first();
112 | } catch (IllegalStateException e) {
113 | assertThat(e.getMessage()).isEqualTo("Content stream is already disposed.");
114 | }
115 | }
116 |
117 | private static HttpClientResponse response(
118 | @NotNull final ByteBuf content,
119 | @NotNull final Action1 headerTransformer
120 | ) {
121 | final DefaultHttpResponse nettyResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
122 | headerTransformer.call(nettyResponse.headers());
123 | final UnicastContentSubject subject = UnicastContentSubject.create(1000, TimeUnit.MILLISECONDS);
124 | subject.onNext(content);
125 | return new HttpClientResponse<>(
126 | nettyResponse,
127 | subject
128 | );
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/test/java/com/mesosphere/mesos/rx/java/SubscriberDecoratorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import org.junit.Test;
20 | import rx.Observable;
21 | import rx.observers.Subscribers;
22 | import rx.observers.TestSubscriber;
23 |
24 | import java.util.Optional;
25 | import java.util.concurrent.ExecutionException;
26 | import java.util.concurrent.ExecutorService;
27 | import java.util.concurrent.Executors;
28 |
29 | import static org.assertj.core.api.Assertions.assertThat;
30 |
31 | public final class SubscriberDecoratorTest {
32 |
33 |
34 | @Test
35 | public void worksForCompleted() throws Exception {
36 | final TestSubscriber testSubscriber = new TestSubscriber<>();
37 | final MesosClient.SubscriberDecorator decorator = new MesosClient.SubscriberDecorator<>(testSubscriber);
38 | Observable.just("something")
39 | .subscribe(decorator);
40 |
41 | testSubscriber.assertCompleted();
42 | testSubscriber.assertNoErrors();
43 | testSubscriber.assertValueCount(1);
44 | testSubscriber.assertValue("something");
45 |
46 | assertThat(decorator.call()).isEqualTo(Optional.empty());
47 | }
48 |
49 | @Test
50 | public void worksForError() throws Exception {
51 | final ExecutorService service = Executors.newSingleThreadExecutor();
52 | final TestSubscriber testSubscriber = new TestSubscriber<>();
53 | final MesosClient.SubscriberDecorator decorator = new MesosClient.SubscriberDecorator<>(testSubscriber);
54 | Observable.from(service.submit(() -> {throw new RuntimeException("kaboom");}))
55 | .subscribe(decorator);
56 |
57 | testSubscriber.assertNoValues();
58 | testSubscriber.assertError(ExecutionException.class);
59 |
60 | final Optional e = decorator.call();
61 | assertThat(e.isPresent()).isTrue();
62 | final Throwable t = e.get();
63 | assertThat(t).isInstanceOf(ExecutionException.class);
64 | assertThat(t.getCause()).isInstanceOf(RuntimeException.class);
65 | assertThat(t.getCause().getMessage()).isEqualTo("kaboom");
66 | }
67 |
68 | @Test
69 | public void exceptionThrowByOnErrorIsReturned() throws Exception {
70 | final MesosClient.SubscriberDecorator decorator = new MesosClient.SubscriberDecorator<>(
71 | Subscribers.create(
72 | s -> {throw new RuntimeException("Supposed to break");},
73 | (t) -> {throw new RuntimeException("wrapped", t);}
74 | )
75 | );
76 | Observable.just("doesn't matter")
77 | .subscribe(decorator);
78 |
79 | final Optional e = decorator.call();
80 | assertThat(e.isPresent()).isTrue();
81 | final Throwable t = e.get();
82 | assertThat(t).isInstanceOf(RuntimeException.class);
83 | assertThat(t.getMessage()).isEqualTo("wrapped");
84 | assertThat(t.getCause().getMessage()).isEqualTo("Supposed to break");
85 | }
86 |
87 | @Test
88 | public void exceptionThrowByOnCompletedIsReturned() throws Exception {
89 | final MesosClient.SubscriberDecorator decorator = new MesosClient.SubscriberDecorator<>(
90 | Subscribers.create(
91 | s -> {},
92 | (t) -> {throw new RuntimeException("wrapped", t);},
93 | () -> {throw new RuntimeException("wrapped2");}
94 | )
95 | );
96 | Observable.just("doesn't matter")
97 | .subscribe(decorator);
98 |
99 | final Optional e = decorator.call();
100 | assertThat(e.isPresent()).isTrue();
101 | final Throwable t = e.get();
102 | assertThat(t).isInstanceOf(RuntimeException.class);
103 | assertThat(t.getMessage()).isEqualTo("wrapped2");
104 | }
105 |
106 | @Test(expected = StackOverflowError.class)
107 | public void fatalExceptionIsThrown() throws Exception {
108 | final TestSubscriber testSubscriber = new TestSubscriber<>();
109 | final MesosClient.SubscriberDecorator decorator = new MesosClient.SubscriberDecorator<>(testSubscriber);
110 | Observable.just("doesn't matter")
111 | .map((s) -> {
112 | //noinspection ConstantIfStatement,ConstantConditions -- This is here to trick the compiler to think this function returns a string
113 | if (true) {
114 | throw new StackOverflowError("stack overflow");
115 | }
116 | return s;
117 | })
118 | .subscribe(decorator);
119 |
120 | testSubscriber.assertNoValues();
121 | }
122 |
123 | @Test
124 | public void onlyOneValueWillBeHeld_completed() throws Exception {
125 | final TestSubscriber testSubscriber = new TestSubscriber<>();
126 | final MesosClient.SubscriberDecorator decorator = new MesosClient.SubscriberDecorator<>(testSubscriber);
127 |
128 | decorator.onCompleted();
129 | decorator.onCompleted();
130 |
131 | final Optional call = decorator.call();
132 | assertThat(call.isPresent()).isFalse();
133 | }
134 |
135 | @Test
136 | public void receiveFirstValue_error() throws Exception {
137 | final TestSubscriber testSubscriber = new TestSubscriber<>();
138 | final MesosClient.SubscriberDecorator decorator = new MesosClient.SubscriberDecorator<>(testSubscriber);
139 |
140 | decorator.onError(new RuntimeException("this one"));
141 | decorator.onError(new RuntimeException("not this one"));
142 |
143 | final Optional e = decorator.call();
144 | assertThat(e.isPresent()).isTrue();
145 | final Throwable t = e.get();
146 | assertThat(t).isInstanceOf(RuntimeException.class);
147 | assertThat(t.getMessage()).isEqualTo("this one");
148 | }
149 |
150 | @Test
151 | public void onlyOneValueWillBeHeld_completedThenError() throws Exception {
152 | final TestSubscriber testSubscriber = new TestSubscriber<>();
153 | final MesosClient.SubscriberDecorator decorator = new MesosClient.SubscriberDecorator<>(testSubscriber);
154 |
155 | decorator.onCompleted();
156 | decorator.onError(new RuntimeException("really doesn't matter"));
157 |
158 | final Optional call = decorator.call();
159 | assertThat(call.isPresent()).isFalse();
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/mesos-rxjava-client/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | true
20 |
21 |
22 |
23 |
24 | %date %-5.5level [%-40.40thread] %-36.36logger{36} - %message%n
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/mesos-rxjava-example/mesos-rxjava-example-framework/README.md:
--------------------------------------------------------------------------------
1 | # Example framework
2 |
3 | A simple Mesos framework that demonstrates API client usage.
4 |
5 | ## Running
6 |
7 | You'll need a Mesos master accessible at `$mesos_uri`.
8 |
9 | From the project root directory run the following commands:
10 |
11 | ```bash
12 | mvn clean package
13 | package_path="mesos-rxjava-example/mesos-rxjava-example-framework/target"
14 | package_file="mesos-rxjava-example-framework-0.1.0-SNAPSHOT-jar-with-dependencies.jar"
15 | main_class="com.mesosphere.mesos.rx.java.example.framework.sleepy.Sleepy"
16 | mesos_uri="http://localhost:5050/api/v1/scheduler"
17 | cpus_per_task="0.04"
18 | mesos_resource_role="*"
19 | java -cp "$package_path/$package_file" "$main_class" "$mesos_uri" "$cpus_per_task" "$mesos_resource_role"
20 | ```
21 |
--------------------------------------------------------------------------------
/mesos-rxjava-example/mesos-rxjava-example-framework/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
20 |
21 | com.mesosphere.mesos.rx.java.example
22 | mesos-rxjava-example
23 | 0.2.1-SNAPSHOT
24 |
25 | 4.0.0
26 |
27 | mesos-rxjava-example-framework
28 |
29 | Mesos RxJava :: Example :: Sleepy Framework
30 |
31 |
32 | A simple framework that launches sleep tasks to exercise the client and the mesos http api.
33 |
34 |
35 |
36 | false
37 |
38 |
39 |
40 |
41 | com.mesosphere.mesos.rx.java
42 | mesos-rxjava-protobuf-client
43 |
44 |
45 | com.mesosphere.mesos.rx.java
46 | mesos-rxjava-test
47 |
48 |
49 |
50 | com.google.guava
51 | guava
52 | compile
53 |
54 |
55 |
56 |
57 | ch.qos.logback
58 | logback-classic
59 | compile
60 |
61 |
62 |
63 |
64 |
65 |
66 | maven-assembly-plugin
67 |
68 |
69 | jar-with-dependencies
70 |
71 |
72 |
73 | com.mesosphere.mesos.rx.java.example.framework.sleepy.Sleepy
74 |
75 |
76 |
77 |
78 |
79 | make-assembly
80 | package
81 |
82 | single
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/mesos-rxjava-example/mesos-rxjava-example-framework/src/main/java/com/mesosphere/mesos/rx/java/example/framework/sleepy/State.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.example.framework.sleepy;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 |
23 | import java.util.Map;
24 | import java.util.concurrent.ConcurrentHashMap;
25 | import java.util.concurrent.atomic.AtomicInteger;
26 |
27 | import static com.mesosphere.mesos.rx.java.protobuf.ProtoUtils.protoToString;
28 |
29 | final class State {
30 | private static final Logger LOGGER = LoggerFactory.getLogger(State.class);
31 |
32 | private final double cpusPerTask;
33 | private final double memMbPerTask;
34 | @NotNull
35 | private final String resourceRole;
36 |
37 | @NotNull
38 | private final Map taskStates;
39 | @NotNull
40 | private final AtomicInteger offerCounter = new AtomicInteger();
41 | @NotNull
42 | private final AtomicInteger totalTaskCounter = new AtomicInteger();
43 |
44 | @NotNull
45 | private final FwId fwId;
46 |
47 | public State(
48 | @NotNull final FwId fwId,
49 | @NotNull final String resourceRole,
50 | final double cpusPerTask,
51 | final double memMbPerTask
52 | ) {
53 | this.fwId = fwId;
54 | this.resourceRole = resourceRole;
55 | this.cpusPerTask = cpusPerTask;
56 | this.memMbPerTask = memMbPerTask;
57 | this.taskStates = new ConcurrentHashMap<>();
58 | }
59 |
60 | @NotNull
61 | public FwId getFwId() {
62 | return fwId;
63 | }
64 |
65 | public double getCpusPerTask() {
66 | return cpusPerTask;
67 | }
68 |
69 | public double getMemMbPerTask() {
70 | return memMbPerTask;
71 | }
72 |
73 | @NotNull
74 | public String getResourceRole() {
75 | return resourceRole;
76 | }
77 |
78 | @NotNull
79 | public AtomicInteger getOfferCounter() {
80 | return offerCounter;
81 | }
82 |
83 | @NotNull
84 | public AtomicInteger getTotalTaskCounter() {
85 | return totalTaskCounter;
86 | }
87 |
88 | public void put(final TaskId key, final TaskState value) {
89 | LOGGER.debug("put(key : {}, value : {})", protoToString(key), value);
90 | taskStates.put(key, value);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/mesos-rxjava-example/mesos-rxjava-example-framework/src/main/java/com/mesosphere/mesos/rx/java/example/framework/sleepy/Tuple2.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.example.framework.sleepy;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 |
21 | final class Tuple2 {
22 | @NotNull
23 | public final T1 _1;
24 | @NotNull
25 | public final T2 _2;
26 |
27 | public Tuple2(@NotNull final T1 v1, @NotNull final T2 v2) {
28 | _1 = v1;
29 | _2 = v2;
30 | }
31 |
32 | public static Tuple2 create(@NotNull final T1 v1, @NotNull final T2 v2) {
33 | return new Tuple2<>(v1, v2);
34 | }
35 |
36 | public static Tuple2 t(@NotNull final T1 v1, @NotNull final T2 v2) {
37 | return create(v1, v2);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/mesos-rxjava-example/mesos-rxjava-example-framework/src/main/java/com/mesosphere/mesos/rx/java/example/framework/sleepy/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * This package contains an example Mesos Framework built on the Mesos RxJava Client.
19 | *
20 | * The framework defined in this package is pretty rudimentary but does illustrate how to use Mesos RxJava, and some
21 | * of the best practices that should be employed when creating a framework.
22 | */
23 | package com.mesosphere.mesos.rx.java.example.framework.sleepy;
24 |
--------------------------------------------------------------------------------
/mesos-rxjava-example/mesos-rxjava-example-framework/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | true
20 |
21 |
22 |
23 |
24 | %date %-5.5level [%-40.40thread] %-36.36logger{36} - %message%n
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/mesos-rxjava-example/mesos-rxjava-example-framework/src/test/java/com/mesosphere/mesos/rx/java/example/framework/sleepy/SleepySimulationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.example.framework.sleepy;
18 |
19 | import com.google.protobuf.ByteString;
20 | import com.mesosphere.mesos.rx.java.protobuf.ProtobufMessageCodecs;
21 | import com.mesosphere.mesos.rx.java.protobuf.SchedulerCalls;
22 | import com.mesosphere.mesos.rx.java.test.Async;
23 | import com.mesosphere.mesos.rx.java.test.simulation.MesosServerSimulation;
24 | import org.apache.mesos.v1.scheduler.Protos;
25 | import org.jetbrains.annotations.NotNull;
26 | import org.junit.After;
27 | import org.junit.Before;
28 | import org.junit.Rule;
29 | import org.junit.Test;
30 | import org.junit.rules.Timeout;
31 | import rx.subjects.BehaviorSubject;
32 |
33 | import java.net.URI;
34 | import java.util.List;
35 | import java.util.Set;
36 | import java.util.UUID;
37 | import java.util.stream.Collectors;
38 |
39 | import static com.google.common.collect.Lists.newArrayList;
40 | import static com.google.common.collect.Sets.newHashSet;
41 | import static com.mesosphere.mesos.rx.java.protobuf.SchedulerEvents.*;
42 | import static java.util.concurrent.TimeUnit.MILLISECONDS;
43 | import static org.apache.mesos.v1.Protos.TaskState.TASK_RUNNING;
44 | import static org.assertj.core.api.Assertions.assertThat;
45 |
46 | public final class SleepySimulationTest {
47 |
48 | @NotNull
49 | private static final Protos.Event HEARTBEAT = Protos.Event.newBuilder()
50 | .setType(Protos.Event.Type.HEARTBEAT)
51 | .build();
52 |
53 | @Rule
54 | public Timeout timeoutRule = new Timeout(15_000, MILLISECONDS);
55 |
56 | @Rule
57 | public Async async = new Async();
58 |
59 | private BehaviorSubject subject;
60 | private MesosServerSimulation sim;
61 |
62 | private URI uri;
63 |
64 | @Before
65 | public void setUp() throws Exception {
66 | subject = BehaviorSubject.create();
67 | sim = new MesosServerSimulation<>(
68 | subject,
69 | ProtobufMessageCodecs.SCHEDULER_EVENT,
70 | ProtobufMessageCodecs.SCHEDULER_CALL,
71 | (e) -> e.getType() == Protos.Call.Type.SUBSCRIBE
72 | );
73 | final int serverPort = sim.start();
74 | uri = URI.create(String.format("http://localhost:%d/api/v1/scheduler", serverPort));
75 | }
76 |
77 | @After
78 | public void tearDown() throws Exception {
79 | sim.shutdown();
80 | }
81 |
82 | @Test
83 | public void offerSimulation() throws Throwable {
84 | final String fwId = "sleepy-" + UUID.randomUUID();
85 | final org.apache.mesos.v1.Protos.FrameworkID frameworkID = org.apache.mesos.v1.Protos.FrameworkID.newBuilder()
86 | .setValue(fwId)
87 | .build();
88 | final Protos.Call subscribe = SchedulerCalls.subscribe(
89 | frameworkID,
90 | org.apache.mesos.v1.Protos.FrameworkInfo.newBuilder()
91 | .setId(frameworkID)
92 | .setUser("root")
93 | .setName("sleepy")
94 | .setFailoverTimeout(0)
95 | .setRole("*")
96 | .build()
97 | );
98 |
99 | async.run("sleepy-framework", () -> Sleepy._main(fwId, uri.toString(), "1", "*"));
100 |
101 | subject.onNext(subscribed(fwId, 15));
102 | sim.awaitSubscribeCall();
103 | final List callsReceived1 = sim.getCallsReceived();
104 | assertThat(callsReceived1).hasSize(1);
105 | assertThat(callsReceived1.get(0)).isEqualTo(subscribe);
106 |
107 | // send a heartbeat
108 | subject.onNext(HEARTBEAT);
109 | // send an offer
110 | subject.onNext(resourceOffer("host-1", "offer-1", "agent-1", fwId, 4, 16 * 1024, 100 * 1024));
111 |
112 | sim.awaitCall(); // wait for accept to reach the server
113 | final List callsReceived2 = sim.getCallsReceived();
114 | assertThat(callsReceived2).hasSize(2);
115 | final Protos.Call.Accept accept = callsReceived2.get(1).getAccept();
116 | assertThat(accept).isNotNull();
117 | assertThat(
118 | accept.getOfferIdsList().stream()
119 | .map(org.apache.mesos.v1.Protos.OfferID::getValue)
120 | .collect(Collectors.toList())
121 | ).isEqualTo(newArrayList("offer-1"));
122 | final List taskInfos = accept.getOperationsList().stream()
123 | .map(org.apache.mesos.v1.Protos.Offer.Operation::getLaunch)
124 | .flatMap(l -> l.getTaskInfosList().stream())
125 | .collect(Collectors.toList());
126 | assertThat(taskInfos).hasSize(4);
127 | assertThat(
128 | taskInfos.stream()
129 | .map(t -> t.getTaskId().getValue())
130 | .collect(Collectors.toSet())
131 | ).isEqualTo(newHashSet("task-1-1", "task-1-2", "task-1-3", "task-1-4"));
132 |
133 | // send task status updates
134 | final ByteString uuid1 = ByteString.copyFromUtf8(UUID.randomUUID().toString());
135 | final ByteString uuid2 = ByteString.copyFromUtf8(UUID.randomUUID().toString());
136 | final ByteString uuid3 = ByteString.copyFromUtf8(UUID.randomUUID().toString());
137 | final ByteString uuid4 = ByteString.copyFromUtf8(UUID.randomUUID().toString());
138 | subject.onNext(update("agent-1", "task-1-1", "task-1-1", TASK_RUNNING, uuid1));
139 | subject.onNext(update("agent-1", "task-1-2", "task-1-2", TASK_RUNNING, uuid2));
140 | subject.onNext(update("agent-1", "task-1-3", "task-1-3", TASK_RUNNING, uuid3));
141 | subject.onNext(update("agent-1", "task-1-4", "task-1-4", TASK_RUNNING, uuid4));
142 |
143 | // wait for the task status updates to be ack'd
144 | sim.awaitCall(4);
145 | final List callsReceived3 = sim.getCallsReceived();
146 | assertThat(callsReceived3).hasSize(6);
147 | final List ackCalls = callsReceived3.subList(2, 6);
148 | final Set ackdUuids = ackCalls.stream()
149 | .map(c -> c.getAcknowledge().getUuid())
150 | .collect(Collectors.toSet());
151 |
152 | assertThat(ackdUuids).isEqualTo(newHashSet(uuid1, uuid2, uuid3, uuid4));
153 |
154 | // send another offer with too little cpu for a task to run
155 | subject.onNext(resourceOffer("host-1", "offer-2", "agent-1", fwId, 0.9, 15 * 1024, 100 * 1024));
156 |
157 | // wait for the decline of the offer
158 | sim.awaitCall();
159 | final List callsReceived4 = sim.getCallsReceived();
160 | assertThat(callsReceived4).hasSize(7);
161 | final Protos.Call.Decline decline = callsReceived4.get(6).getDecline();
162 | assertThat(
163 | decline.getOfferIdsList().stream()
164 | .map(org.apache.mesos.v1.Protos.OfferID::getValue)
165 | .collect(Collectors.toList())
166 | ).isEqualTo(newArrayList("offer-2"));
167 |
168 | subject.onCompleted();
169 | sim.awaitSendingEvents();
170 | }
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/mesos-rxjava-example/mesos-rxjava-example-framework/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | true
20 |
21 |
22 |
23 |
24 | %date %-5.5level [%-40.40thread] %-36.36logger{36} - %message%n
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/mesos-rxjava-example/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
20 |
21 | com.mesosphere.mesos.rx.java
22 | mesos-rxjava
23 | 0.2.1-SNAPSHOT
24 |
25 | 4.0.0
26 | pom
27 |
28 | com.mesosphere.mesos.rx.java.example
29 | mesos-rxjava-example
30 |
31 | Mesos RxJava :: Example
32 |
33 |
34 | A collection of examples (frameworks, deployable tests etc)
35 |
36 |
37 |
38 | mesos-rxjava-example-framework
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/mesos-rxjava-protobuf-client/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
20 | 4.0.0
21 |
22 |
23 | com.mesosphere.mesos.rx.java
24 | mesos-rxjava
25 | 0.2.1-SNAPSHOT
26 |
27 |
28 | mesos-rxjava-protobuf-client
29 |
30 | Mesos RxJava :: Protobuf Client
31 |
32 |
33 | false
34 |
35 |
36 |
37 |
38 | com.mesosphere.mesos.rx.java
39 | mesos-rxjava-client
40 |
41 |
42 | org.apache.mesos
43 | mesos
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/mesos-rxjava-protobuf-client/src/main/java/com/mesosphere/mesos/rx/java/protobuf/ProtoCodec.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.protobuf;
18 |
19 | import com.google.protobuf.InvalidProtocolBufferException;
20 | import com.google.protobuf.Message;
21 | import com.mesosphere.mesos.rx.java.util.MessageCodec;
22 | import org.jetbrains.annotations.NotNull;
23 |
24 | import java.io.IOException;
25 | import java.io.InputStream;
26 |
27 | /**
28 | * Implements {@link MessageCodec} for Protocol Buffers.
29 | *
30 | * @param the protobuf message type
31 | */
32 | public final class ProtoCodec implements MessageCodec {
33 |
34 | @NotNull
35 | private final ByteArrayParser byteArrayParser;
36 | @NotNull
37 | private final InputStreamParser inputStreamParser;
38 |
39 | /**
40 | * Instantiates a ProtoCodec instance that deserializes messages with the given
41 | * {@link ByteArrayParser}.
42 | *
43 | * The specific parser that is provided defines which protobuf message class this codec is for. For example,
44 | * {@code new ProtoCodec<>(Protos.Event::parseFrom)} instantiates a codec for
45 | * {@link org.apache.mesos.v1.scheduler.Protos.Event Event} messages.
46 | *
47 | * @param byteArrayParser The function to use to parse {@code byte[]}
48 | * @param inputStreamParser The function to use to parse {@link InputStream}
49 | */
50 | public ProtoCodec(
51 | @NotNull final ByteArrayParser byteArrayParser,
52 | @NotNull final InputStreamParser inputStreamParser
53 | ) {
54 | this.byteArrayParser = byteArrayParser;
55 | this.inputStreamParser = inputStreamParser;
56 | }
57 |
58 | @NotNull
59 | @Override
60 | public byte[] encode(@NotNull final T message) {
61 | return message.toByteArray();
62 | }
63 |
64 | @NotNull
65 | @Override
66 | public T decode(@NotNull byte[] bytes) {
67 | try {
68 | return byteArrayParser.parseFrom(bytes);
69 | } catch (InvalidProtocolBufferException e) {
70 | throw new RuntimeException(e);
71 | }
72 | }
73 |
74 | @NotNull
75 | @Override
76 | public T decode(@NotNull final InputStream in) {
77 | try {
78 | return inputStreamParser.parseFrom(in);
79 | } catch (IOException e) {
80 | throw new RuntimeException(e);
81 | }
82 | }
83 |
84 | @NotNull
85 | @Override
86 | public String mediaType() {
87 | return "application/x-protobuf";
88 | }
89 |
90 | @NotNull
91 | @Override
92 | public String show(@NotNull final T message) {
93 | return ProtoUtils.protoToString(message);
94 | }
95 |
96 | /**
97 | * Mandatory functional interface definition due to protobuf parsing methods throwing
98 | * {@link InvalidProtocolBufferException}, a checked exception.
99 | *
100 | * This interface should be satisfied by {@code M::parseFrom} for any {@link Message} subclass M.
101 | */
102 | @FunctionalInterface
103 | public interface ByteArrayParser {
104 | @NotNull R parseFrom(@NotNull final byte[] data) throws InvalidProtocolBufferException;
105 | }
106 |
107 | /**
108 | * Mandatory functional interface definition due to protobuf parsing methods throwing
109 | * a checked exception.
110 | *
111 | * This interface should be satisfied by {@code M::parseFrom} for any {@link Message} subclass M.
112 | */
113 | @FunctionalInterface
114 | public interface InputStreamParser {
115 | @NotNull R parseFrom(@NotNull final InputStream data) throws IOException;
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/mesos-rxjava-protobuf-client/src/main/java/com/mesosphere/mesos/rx/java/protobuf/ProtoUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.protobuf;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 |
21 | import java.util.regex.Pattern;
22 |
23 | /**
24 | * A set of utility methods that make working with Mesos Protos easier.
25 | */
26 | public final class ProtoUtils {
27 | private static final Pattern PROTO_TO_STRING = Pattern.compile(" *\\n *");
28 |
29 | private ProtoUtils() {}
30 |
31 | /**
32 | * Utility method to make logging of protos a little better than what is done by default.
33 | * @param message The proto message to be "stringified"
34 | * @return A string representation of the provided {@code message} surrounded with curly braces ('{}') and all
35 | * new lines replaced with a space.
36 | */
37 | @NotNull
38 | public static String protoToString(@NotNull final Object message) {
39 | return "{ " + PROTO_TO_STRING.matcher(message.toString()).replaceAll(" ").trim() + " }";
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/mesos-rxjava-protobuf-client/src/main/java/com/mesosphere/mesos/rx/java/protobuf/ProtobufMesosClientBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.protobuf;
18 |
19 | import com.mesosphere.mesos.rx.java.MesosClientBuilder;
20 | import org.apache.mesos.v1.scheduler.Protos;
21 | import org.jetbrains.annotations.NotNull;
22 |
23 | /**
24 | * A collection of methods that have some pre-defined {@link MesosClientBuilder} configurations.
25 | */
26 | public final class ProtobufMesosClientBuilder {
27 | private ProtobufMesosClientBuilder() {}
28 |
29 | /**
30 | * @return An initial {@link MesosClientBuilder} that will use protobuf
31 | * for the {@link org.apache.mesos.v1.scheduler.Protos.Call Call} and
32 | * {@link org.apache.mesos.v1.scheduler.Protos.Event Event} messages.
33 | */
34 | @NotNull
35 | public static MesosClientBuilder schedulerUsingProtos() {
36 | return MesosClientBuilder.newBuilder()
37 | .sendCodec(ProtobufMessageCodecs.SCHEDULER_CALL)
38 | .receiveCodec(ProtobufMessageCodecs.SCHEDULER_EVENT)
39 | ;
40 | }
41 |
42 | /**
43 | * @return An initial {@link MesosClientBuilder} that will use protobuf
44 | * for the {@link org.apache.mesos.v1.executor.Protos.Call Call} and
45 | * {@link org.apache.mesos.v1.executor.Protos.Event Event} messages.
46 | */
47 | @NotNull
48 | public static MesosClientBuilder<
49 | org.apache.mesos.v1.executor.Protos.Call,
50 | org.apache.mesos.v1.executor.Protos.Event
51 | > executorUsingProtos() {
52 | return MesosClientBuilder.newBuilder()
53 | .sendCodec(ProtobufMessageCodecs.EXECUTOR_CALL)
54 | .receiveCodec(ProtobufMessageCodecs.EXECUTOR_EVENT)
55 | ;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/mesos-rxjava-protobuf-client/src/main/java/com/mesosphere/mesos/rx/java/protobuf/ProtobufMessageCodecs.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.protobuf;
18 |
19 | import com.mesosphere.mesos.rx.java.util.MessageCodec;
20 | import org.apache.mesos.v1.scheduler.Protos;
21 |
22 | /**
23 | * A pair of {@link MessageCodec}s for {@link org.apache.mesos.v1.scheduler.Protos.Call} and
24 | * {@link org.apache.mesos.v1.scheduler.Protos.Event}
25 | */
26 | public final class ProtobufMessageCodecs {
27 |
28 | private ProtobufMessageCodecs() {}
29 |
30 | /** A {@link MessageCodec} for {@link org.apache.mesos.v1.scheduler.Protos.Call Call}. */
31 | public static final MessageCodec SCHEDULER_CALL = new ProtoCodec<>(
32 | Protos.Call::parseFrom, Protos.Call::parseFrom
33 | );
34 |
35 | /** A {@link MessageCodec} for {@link org.apache.mesos.v1.scheduler.Protos.Event Event}. */
36 | public static final MessageCodec SCHEDULER_EVENT = new ProtoCodec<>(
37 | Protos.Event::parseFrom, Protos.Event::parseFrom
38 | );
39 |
40 | /** A {@link MessageCodec} for {@link org.apache.mesos.v1.executor.Protos.Call Call}. */
41 | public static final MessageCodec EXECUTOR_CALL = new ProtoCodec<>(
42 | org.apache.mesos.v1.executor.Protos.Call::parseFrom, org.apache.mesos.v1.executor.Protos.Call::parseFrom
43 | );
44 |
45 | /** A {@link MessageCodec} for {@link org.apache.mesos.v1.executor.Protos.Event Event}. */
46 | public static final MessageCodec EXECUTOR_EVENT = new ProtoCodec<>(
47 | org.apache.mesos.v1.executor.Protos.Event::parseFrom, org.apache.mesos.v1.executor.Protos.Event::parseFrom
48 | );
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/mesos-rxjava-protobuf-client/src/main/java/com/mesosphere/mesos/rx/java/protobuf/SchedulerCalls.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.protobuf;
18 |
19 | import com.google.protobuf.ByteString;
20 | import org.apache.mesos.v1.Protos;
21 | import org.apache.mesos.v1.scheduler.Protos.Call;
22 | import org.jetbrains.annotations.NotNull;
23 |
24 | import java.util.List;
25 |
26 | import static org.apache.mesos.v1.scheduler.Protos.Call.*;
27 |
28 | /**
29 | * A set of factory methods that make {@link Call Call}s easier to create.
30 | */
31 | public final class SchedulerCalls {
32 |
33 | private SchedulerCalls() {}
34 |
35 | /**
36 | * Utility method to more succinctly construct a {@link Call Call} of type {@link Type#ACKNOWLEDGE ACKNOWLEDGE}.
37 | *
38 | *
39 | * @param frameworkId The {@link Protos.FrameworkID} to be set on the {@link Call}
40 | * @param uuid The {@link Protos.TaskStatus#getUuid() uuid} from the
41 | * {@link org.apache.mesos.v1.scheduler.Protos.Event.Update#getStatus() TaskStatus} received from Mesos.
42 | * @param agentId The {@link Protos.TaskStatus#getAgentId() agentId} from the
43 | * {@link org.apache.mesos.v1.scheduler.Protos.Event.Update#getStatus() TaskStatus} received from Mesos.
44 | * @param taskId The {@link Protos.TaskStatus#getTaskId() taskId} from the
45 | * {@link org.apache.mesos.v1.scheduler.Protos.Event.Update#getStatus() TaskStatus} received from Mesos.
46 | * @return A {@link Call} with a configured {@link Acknowledge}.
47 | */
48 | @NotNull
49 | public static Call ackUpdate(
50 | @NotNull final Protos.FrameworkID frameworkId,
51 | @NotNull final ByteString uuid,
52 | @NotNull final Protos.AgentID agentId,
53 | @NotNull final Protos.TaskID taskId
54 | ) {
55 | return newBuilder()
56 | .setFrameworkId(frameworkId)
57 | .setType(Type.ACKNOWLEDGE)
58 | .setAcknowledge(
59 | Acknowledge.newBuilder()
60 | .setUuid(uuid)
61 | .setAgentId(agentId)
62 | .setTaskId(taskId)
63 | .build()
64 | )
65 | .build();
66 | }
67 |
68 | /**
69 | * Utility method to more succinctly construct a {@link Call Call} of type {@link Type#DECLINE DECLINE}.
70 | *
71 | *
72 | * @param frameworkId The {@link Protos.FrameworkID FrameworkID} to be set on the {@link Call}
73 | * @param offerIds A list of {@link Protos.OfferID OfferID} from the
74 | * {@link Protos.Offer}s received from Mesos.
75 | * @return A {@link Call} with a configured {@link Decline}.
76 | */
77 | @NotNull
78 | public static Call decline(@NotNull final Protos.FrameworkID frameworkId, @NotNull final List offerIds) {
79 | return newBuilder()
80 | .setFrameworkId(frameworkId)
81 | .setType(Type.DECLINE)
82 | .setDecline(
83 | Decline.newBuilder()
84 | .addAllOfferIds(offerIds)
85 | )
86 | .build();
87 | }
88 |
89 | /**
90 | * Utility method to more succinctly construct a {@link Call Call} of type {@link Type#SUBSCRIBE SUBSCRIBE}.
91 | *
92 | *
93 | * @param frameworkId The frameworkId to set on the {@link Protos.FrameworkInfo FrameworkInfo} and
94 | * {@link Call Call} messages.
95 | * @param user The user to set on the {@link Protos.FrameworkInfo FrameworkInfo} message.
96 | * @param frameworkName The name to set on the {@link Protos.FrameworkInfo FrameworkInfo} message.
97 | * @param failoverTimeoutSeconds The failoverTimeoutSeconds to set on the
98 | * {@link Protos.FrameworkInfo FrameworkInfo} message.
99 | * @return An {@link Call Call} of type {@link Type#SUBSCRIBE SUBSCRIBE} with the configured
100 | * {@link Subscribe Subscribe} sub-message.
101 | */
102 | @NotNull
103 | public static Call subscribe(
104 | @NotNull final String frameworkId,
105 | @NotNull final String user,
106 | @NotNull final String frameworkName,
107 | final long failoverTimeoutSeconds
108 | ) {
109 | final Protos.FrameworkID frameworkID = Protos.FrameworkID.newBuilder().setValue(frameworkId).build();
110 | return subscribe(frameworkID, user, frameworkName, failoverTimeoutSeconds);
111 | }
112 |
113 | /**
114 | * Utility method to more succinctly construct a {@link Call Call} of type {@link Type#SUBSCRIBE SUBSCRIBE}.
115 | *
116 | *
117 | * @param frameworkId The frameworkId to set on the {@link Protos.FrameworkInfo FrameworkInfo} and
118 | * {@link Call Call} messages.
119 | * @param user The user to set on the {@link Protos.FrameworkInfo FrameworkInfo} message.
120 | * @param frameworkName The name to set on the {@link Protos.FrameworkInfo FrameworkInfo} message.
121 | * @param failoverTimeoutSeconds The failoverTimeoutSeconds to set on the
122 | * {@link Protos.FrameworkInfo FrameworkInfo} message.
123 | * @return An {@link Call Call} of type {@link Type#SUBSCRIBE SUBSCRIBE} with the configured
124 | * {@link Subscribe Subscribe} sub-message.
125 | */
126 | @NotNull
127 | public static Call subscribe(
128 | @NotNull final Protos.FrameworkID frameworkId,
129 | @NotNull final String user,
130 | @NotNull final String frameworkName,
131 | final long failoverTimeoutSeconds
132 | ) {
133 | final Protos.FrameworkInfo frameworkInfo = Protos.FrameworkInfo.newBuilder()
134 | .setId(frameworkId)
135 | .setUser(user)
136 | .setName(frameworkName)
137 | .setFailoverTimeout(failoverTimeoutSeconds)
138 | .build();
139 | return subscribe(frameworkId, frameworkInfo);
140 | }
141 |
142 | /**
143 | * Utility method to more succinctly construct a {@link Call Call} of type {@link Type#SUBSCRIBE SUBSCRIBE}.
144 | *
145 | *
146 | * @param frameworkId The frameworkId to set on the {@link Call Call} messages.
147 | * @param frameworkInfo The frameworkInfo to set on the {@link Subscribe Subscribe} sub-message.
148 | * @return An {@link Call Call} of type {@link Type#SUBSCRIBE SUBSCRIBE} with the configured
149 | * {@link Subscribe Subscribe} sub-message.
150 | */
151 | @NotNull
152 | public static Call subscribe(
153 | @NotNull final Protos.FrameworkID frameworkId,
154 | @NotNull final Protos.FrameworkInfo frameworkInfo
155 | ) {
156 | return newBuilder()
157 | .setFrameworkId(frameworkId)
158 | .setType(Type.SUBSCRIBE)
159 | .setSubscribe(
160 | Subscribe.newBuilder()
161 | .setFrameworkInfo(frameworkInfo)
162 | )
163 | .build();
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/mesos-rxjava-protobuf-client/src/main/java/com/mesosphere/mesos/rx/java/protobuf/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * This package provides a builder and corresponding {@link com.mesosphere.mesos.rx.java.util.MessageCodec MessageCodec}s
19 | * necessary to create a client (using protobuf) to interact with an
20 | * {@link rx.Observable} of {@link org.apache.mesos.v1.scheduler.Protos.Event} coming from Apache Mesos.
21 | *
22 | * The following is a full Apache Mesos Framework that declines all resource offers:
23 | *
{@code
24 | * import static org.apache.mesos.v1.Protos.*;
25 | * import static org.apache.mesos.v1.scheduler.Protos.*;
26 | *
27 | * final Protos.FrameworkID fwId = FrameworkID.newBuilder().setValue(UUID.randomUUID().toString()).build();
28 | * com.mesosphere.mesos.rx.java.ProtobufMesosClientBuilder.schedulerUsingProtos()
29 | * .mesosUri(URI.create("http://localhost:5050/api/v1/scheduler"))
30 | * .applicationUserAgentEntry(UserAgentEntries.literal("decline-framework", "0.1.0-SNAPSHOT"))
31 | * .subscribe(
32 | * Call.newBuilder()
33 | * .setFrameworkId(fwId)
34 | * .setType(Call.Type.SUBSCRIBE)
35 | * .setSubscribe(
36 | * Call.Subscribe.newBuilder()
37 | * .setFrameworkInfo(
38 | * Protos.FrameworkInfo.newBuilder()
39 | * .setId(fwId)
40 | * .setUser("root")
41 | * .setName("decline-framework")
42 | * .setFailoverTimeout(0)
43 | * )
44 | * )
45 | * .build()
46 | * )
47 | * .processStream(es ->
48 | * es.filter(event -> event.getType() == Event.Type.OFFERS)
49 | * .flatMap(event -> Observable.from(event.getOffers().getOffersList()))
50 | * .map(offer ->
51 | * Call.newBuilder()
52 | * .setFrameworkId(fwId)
53 | * .setType(Call.Type.DECLINE)
54 | * .setDecline(
55 | * Call.Decline.newBuilder()
56 | * .addOfferIds(offer.getId())
57 | * )
58 | * .build()
59 | * )
60 | * .map(SinkOperations::create)
61 | * .map(Optional::of)
62 | * )
63 | * .build()
64 | * .openStream()
65 | * .await();
66 | * }
67 | */
68 | package com.mesosphere.mesos.rx.java.protobuf;
69 |
--------------------------------------------------------------------------------
/mesos-rxjava-protobuf-client/src/test/java/com/mesosphere/mesos/rx/java/protobuf/ProtobufMessageCodecsTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.protobuf;
18 |
19 | import org.apache.mesos.v1.scheduler.Protos;
20 | import org.jetbrains.annotations.NotNull;
21 | import org.junit.Test;
22 |
23 | import java.util.Base64;
24 |
25 | import static org.assertj.core.api.Assertions.assertThat;
26 |
27 | public final class ProtobufMessageCodecsTest {
28 |
29 | @NotNull
30 | private static final Protos.Event HEARTBEAT = Protos.Event.newBuilder()
31 | .setType(Protos.Event.Type.HEARTBEAT)
32 | .build();
33 |
34 | @NotNull
35 | private static final Protos.Event SUBSCRIBED = SchedulerEvents.subscribed("a7cfd25c-79bd-481c-91cc-692e5db1ec3d", 15);
36 |
37 | @NotNull
38 | private static final Protos.Event OFFER = SchedulerEvents.resourceOffer("host1", "offer1", "agent1", "frw1", 8d, 8192, 8192);
39 |
40 | @NotNull
41 | private static final byte[] SERIALIZED_HEARTBEAT = Base64.getDecoder().decode("CAg=");
42 |
43 | @NotNull
44 | private static final byte[] SERIALIZED_OFFER = Base64.getDecoder().decode(
45 | "CAIabApqCggKBm9mZmVyMRIGCgRmcncxGggKBmFnZW50MSIFaG9zdDEqFgoEY3B1cxAAGgkJAAAAAAAAIEAyASoqFQoDbWVtEAAaCQkAAAA" +
46 | "AAADAQDIBKioWCgRkaXNrEAAaCQkAAAAAAADAQDIBKg=="
47 | );
48 |
49 | @NotNull
50 | private static final byte[] SERIALIZED_SUBSCRIBED =
51 | Base64.getDecoder().decode("CAESMQomCiRhN2NmZDI1Yy03OWJkLTQ4MWMtOTFjYy02OTJlNWRiMWVjM2QRAAAAAAAALkA=");
52 |
53 | @Test
54 | public void testEncode() {
55 | assertThat(SERIALIZED_HEARTBEAT).isEqualTo(ProtobufMessageCodecs.SCHEDULER_EVENT.encode(HEARTBEAT));
56 | assertThat(SERIALIZED_OFFER).isEqualTo(ProtobufMessageCodecs.SCHEDULER_EVENT.encode(OFFER));
57 | assertThat(SERIALIZED_SUBSCRIBED).isEqualTo(ProtobufMessageCodecs.SCHEDULER_EVENT.encode(SUBSCRIBED));
58 | }
59 |
60 | @Test
61 | public void testDecodeSuccess() {
62 | assertThat(HEARTBEAT).isEqualTo(ProtobufMessageCodecs.SCHEDULER_EVENT.decode(SERIALIZED_HEARTBEAT));
63 | assertThat(OFFER).isEqualTo(ProtobufMessageCodecs.SCHEDULER_EVENT.decode(SERIALIZED_OFFER));
64 | assertThat(SUBSCRIBED).isEqualTo(ProtobufMessageCodecs.SCHEDULER_EVENT.decode(SERIALIZED_SUBSCRIBED));
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/mesos-rxjava-recordio/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
20 |
21 | com.mesosphere.mesos.rx.java
22 | mesos-rxjava
23 | 0.2.1-SNAPSHOT
24 |
25 | 4.0.0
26 |
27 | mesos-rxjava-recordio
28 |
29 | Mesos RxJava :: RecordIO Operator
30 |
31 |
32 | rx.Observable.Operator for extracting RecordIO messages from a stream of ByteBuf
33 |
34 |
35 |
36 | false
37 |
38 |
39 |
40 |
41 | io.reactivex
42 | rxjava
43 |
44 |
45 | io.netty
46 | netty-buffer
47 | ${version.netty}
48 |
49 |
50 |
51 |
52 | org.slf4j
53 | slf4j-api
54 |
55 |
56 |
57 |
58 |
59 | org.apache.mesos
60 | mesos
61 | test
62 |
63 |
64 | com.mesosphere.mesos.rx.java
65 | mesos-rxjava-test
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/mesos-rxjava-recordio/src/main/java/com/mesosphere/mesos/rx/java/recordio/RecordIOOperator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.recordio;
18 |
19 | import io.netty.buffer.ByteBuf;
20 | import io.netty.buffer.ByteBufInputStream;
21 | import org.jetbrains.annotations.NotNull;
22 | import org.slf4j.Logger;
23 | import org.slf4j.LoggerFactory;
24 | import rx.Observable.Operator;
25 | import rx.Subscriber;
26 |
27 | import java.nio.charset.StandardCharsets;
28 | import java.util.ArrayList;
29 | import java.util.List;
30 |
31 | /**
32 | * An {@link Operator} that can be applied to a stream of {@link ByteBuf} and produce
33 | * a stream of {@code byte[]} messages following the RecordIO format.
34 | *
35 | * @see RecordIO
36 | * @see rx.Observable#lift(Operator)
37 | */
38 | public final class RecordIOOperator implements Operator {
39 |
40 | @Override
41 | public Subscriber call(final Subscriber super byte[]> subscriber) {
42 | return new RecordIOSubscriber(subscriber);
43 | }
44 |
45 | /**
46 | * A {@link Subscriber} that can process the contents of a {@link ByteBuf} and emit 0-to-many
47 | * {@code byte[]} messages. The format of the data stream represented by the {@link ByteBuf}
48 | * is in
49 | *
50 | * RecordIO format. If a single {@link ByteBuf} does not represent a full message, the data will be
51 | * buffered until a full message can be obtained.
52 | *
53 | *
54 | * Due to the way arrays in Java work, there is an effective limitation to message size of
55 | * 2gb. This is because arrays are indexed with signed 32-bit integers.
56 | *
57 | */
58 | static final class RecordIOSubscriber extends Subscriber {
59 | private static final Logger LOGGER = LoggerFactory.getLogger(RecordIOSubscriber.class);
60 |
61 | @NotNull
62 | final Subscriber super byte[]> child;
63 |
64 | /**
65 | * The message size from the stream is provided as a base-10 String representation of an
66 | * unsigned 64 bit integer (uint64). Since there is a possibility (since the spec isn't
67 | * formal on this, and the HTTP chunked Transfer-Encoding applied to the data stream can
68 | * allow chunks to be any size) this field functions as the bytes that have been read
69 | * since the end of the last message. When the next '\n' is encountered in the byte
70 | * stream, these bytes are turned into a {@code byte[]} and converted to a UTF-8 String.
71 | * This string representation is then read as a {@code long} using
72 | * {@link Long#valueOf(String, int)}.
73 | */
74 | @NotNull
75 | final List messageSizeBytesBuffer = new ArrayList<>();
76 |
77 | /**
78 | * Flag used to signify that we've reached the point in the stream that we should have
79 | * the full set of bytes needed in order to decode the message length.
80 | */
81 | boolean allSizeBytesBuffered = false;
82 |
83 | /**
84 | * The allocated {@code byte[]} for the current message being read from the stream.
85 | * Once all the bytes of the message have been read this reference will be
86 | * nulled out until the next message size has been resolved.
87 | */
88 | byte[] messageBytes = null;
89 | /**
90 | * The number of bytes in the encoding is specified as an unsigned (uint64)
91 | * However, since arrays in java are addressed and indexed by int we drop the
92 | * precision early so that working with the arrays is easier.
93 | * Also, a byte[Integer.MAX_VALUE] is 2gb so I seriously doubt we'll be receiving
94 | * a message that large.
95 | */
96 | int remainingBytesForMessage = 0;
97 |
98 | RecordIOSubscriber(@NotNull final Subscriber super byte[]> child) {
99 | super(child);
100 | this.child = child;
101 | }
102 |
103 | @Override
104 | public void onStart() {
105 | request(Long.MAX_VALUE);
106 | }
107 |
108 | /**
109 | * When a {@link ByteBuf} is passed into this method it is completely "drained".
110 | * Meaning all bytes are read from it and any message(s) contained in it will be
111 | * extracted and then sent to the child via {@link Subscriber#onNext(Object)}.
112 | * If any error is encountered (exception) {@link RecordIOSubscriber#onError(Throwable)}
113 | * will be called and the method will terminate without attempting to do any
114 | * sort of recovery.
115 | *
116 | * @param t The {@link ByteBuf} to process
117 | */
118 | @Override
119 | public void onNext(final ByteBuf t) {
120 | try {
121 | final ByteBufInputStream in = new ByteBufInputStream(t);
122 | while (t.readableBytes() > 0) {
123 | // New message
124 | if (remainingBytesForMessage == 0) {
125 |
126 | // Figure out the size of the message
127 | byte b;
128 | while ((b = (byte) in.read()) != -1) {
129 | if (b == (byte) '\n') {
130 | allSizeBytesBuffered = true;
131 | break;
132 | } else {
133 | messageSizeBytesBuffer.add(b);
134 | }
135 | }
136 |
137 | // Allocate the byte[] for the message and get ready to read it
138 | if (allSizeBytesBuffered) {
139 | final byte[] bytes = getByteArray(messageSizeBytesBuffer);
140 | allSizeBytesBuffered = false;
141 | final String sizeString = new String(bytes, StandardCharsets.UTF_8);
142 | messageSizeBytesBuffer.clear();
143 | final long l = Long.valueOf(sizeString, 10);
144 | if (l > Integer.MAX_VALUE) {
145 | LOGGER.warn("specified message size ({}) is larger than Integer.MAX_VALUE. Value will be truncated to int");
146 | remainingBytesForMessage = Integer.MAX_VALUE;
147 | // TODO: Possibly make this more robust to account for things larger than 2g
148 | } else {
149 | remainingBytesForMessage = (int) l;
150 | }
151 |
152 | messageBytes = new byte[remainingBytesForMessage];
153 | }
154 | }
155 |
156 | // read bytes until we either reach the end of the ByteBuf or the message is fully read.
157 | final int readableBytes = t.readableBytes();
158 | if (readableBytes > 0) {
159 | final int writeStart = messageBytes.length - remainingBytesForMessage;
160 | final int numBytesToCopy = Math.min(readableBytes, remainingBytesForMessage);
161 | final int read = in.read(messageBytes, writeStart, numBytesToCopy);
162 | remainingBytesForMessage -= read;
163 | }
164 |
165 | // Once we've got a full message send it on downstream.
166 | if (remainingBytesForMessage == 0 && messageBytes != null) {
167 | child.onNext(messageBytes);
168 | messageBytes = null;
169 | }
170 | }
171 | } catch (Exception e) {
172 | onError(e);
173 | }
174 | }
175 |
176 | @Override
177 | public void onError(final Throwable e) {
178 | child.onError(e);
179 | }
180 |
181 | @Override
182 | public void onCompleted() {
183 | child.onCompleted();
184 | }
185 |
186 | private static byte[] getByteArray(@NotNull final List list) {
187 | final byte[] bytes = new byte[list.size()];
188 | for (int i = 0; i < list.size(); i++) {
189 | bytes[i] = list.get(i);
190 | }
191 | return bytes;
192 | }
193 |
194 | }
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/mesos-rxjava-recordio/src/main/java/com/mesosphere/mesos/rx/java/recordio/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * This package provides an {@link rx.Observable.Operator} to process a RecordIO encoded stream of data.
19 | */
20 | package com.mesosphere.mesos.rx.java.recordio;
21 |
--------------------------------------------------------------------------------
/mesos-rxjava-recordio/src/test/java/com/mesosphere/mesos/rx/java/recordio/RecordIOOperatorChunkSizesTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.recordio;
18 |
19 | import com.mesosphere.mesos.rx.java.test.RecordIOUtils;
20 | import com.mesosphere.mesos.rx.java.util.CollectionUtils;
21 | import io.netty.buffer.ByteBuf;
22 | import io.netty.buffer.Unpooled;
23 | import org.apache.mesos.v1.scheduler.Protos.Event;
24 | import org.junit.Test;
25 | import org.junit.runner.RunWith;
26 | import org.junit.runners.Parameterized;
27 | import org.junit.runners.Parameterized.Parameters;
28 |
29 | import java.util.List;
30 |
31 | import static com.google.common.collect.Lists.newArrayList;
32 | import static org.assertj.core.api.Assertions.assertThat;
33 |
34 | @RunWith(Parameterized.class)
35 | public final class RecordIOOperatorChunkSizesTest {
36 |
37 | @Parameters(name = "chunkSize={0}")
38 | public static List chunkSizes() {
39 | return newArrayList(1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 60);
40 | }
41 |
42 | private final int chunkSize;
43 |
44 | public RecordIOOperatorChunkSizesTest(final int chunkSize) {
45 | if (chunkSize <= 0) {
46 | throw new IllegalArgumentException("Chunk size must be positive");
47 | }
48 |
49 | this.chunkSize = chunkSize;
50 | }
51 |
52 | @Test
53 | public void test() {
54 | final byte[] chunk = RecordIOUtils.createChunk(TestingProtos.SUBSCRIBED.toByteArray());
55 | final List bytes = RecordIOOperatorTest.partitionIntoArraysOfSize(chunk, chunkSize);
56 | final List chunks = CollectionUtils.listMap(bytes, Unpooled::copiedBuffer);
57 |
58 | final List events = RecordIOOperatorTest.runTestOnChunks(chunks);
59 | final List eventTypes = CollectionUtils.listMap(events, Event::getType);
60 |
61 | assertThat(eventTypes).isEqualTo(newArrayList(Event.Type.SUBSCRIBED));
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/mesos-rxjava-recordio/src/test/java/com/mesosphere/mesos/rx/java/recordio/RecordIOOperatorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.recordio;
18 |
19 | import com.google.common.primitives.Bytes;
20 | import com.google.protobuf.AbstractMessageLite;
21 | import com.google.protobuf.InvalidProtocolBufferException;
22 | import com.mesosphere.mesos.rx.java.test.RecordIOUtils;
23 | import com.mesosphere.mesos.rx.java.util.CollectionUtils;
24 | import io.netty.buffer.ByteBuf;
25 | import io.netty.buffer.Unpooled;
26 | import org.apache.mesos.v1.scheduler.Protos.Event;
27 | import org.jetbrains.annotations.NotNull;
28 | import org.junit.Test;
29 | import rx.Subscriber;
30 | import rx.observers.TestSubscriber;
31 |
32 | import java.io.InputStream;
33 | import java.util.ArrayList;
34 | import java.util.Arrays;
35 | import java.util.List;
36 | import java.util.stream.Collectors;
37 |
38 | import static com.google.common.collect.Lists.newArrayList;
39 | import static org.assertj.core.api.Assertions.assertThat;
40 |
41 | public class RecordIOOperatorTest {
42 |
43 | private static final List EVENT_PROTOS = newArrayList(
44 | TestingProtos.SUBSCRIBED,
45 | TestingProtos.HEARTBEAT,
46 | TestingProtos.HEARTBEAT,
47 | TestingProtos.HEARTBEAT,
48 | TestingProtos.HEARTBEAT,
49 | TestingProtos.HEARTBEAT,
50 | TestingProtos.HEARTBEAT,
51 | TestingProtos.HEARTBEAT,
52 | TestingProtos.HEARTBEAT,
53 | TestingProtos.HEARTBEAT,
54 | TestingProtos.HEARTBEAT
55 | );
56 |
57 | private static final List EVENT_CHUNKS = EVENT_PROTOS.stream()
58 | .map(AbstractMessageLite::toByteArray)
59 | .map(RecordIOUtils::createChunk)
60 | .collect(Collectors.toList());
61 |
62 | @Test
63 | public void correctlyAbleToReadEventsFromEventsBinFile() throws Exception {
64 | final InputStream inputStream = this.getClass().getResourceAsStream("/events.bin");
65 |
66 | final List chunks = new ArrayList<>();
67 | final byte[] bytes = new byte[100];
68 |
69 | int read;
70 | while ((read = inputStream.read(bytes)) != -1) {
71 | chunks.add(Unpooled.copiedBuffer(bytes, 0, read));
72 | }
73 |
74 | final List events = runTestOnChunks(chunks);
75 | final List eventTypes = CollectionUtils.listMap(events, Event::getType);
76 |
77 | assertThat(eventTypes).isEqualTo(newArrayList(
78 | Event.Type.SUBSCRIBED,
79 | Event.Type.HEARTBEAT,
80 | Event.Type.OFFERS,
81 | Event.Type.OFFERS,
82 | Event.Type.OFFERS,
83 | Event.Type.HEARTBEAT,
84 | Event.Type.OFFERS,
85 | Event.Type.OFFERS,
86 | Event.Type.OFFERS,
87 | Event.Type.HEARTBEAT,
88 | Event.Type.OFFERS,
89 | Event.Type.OFFERS,
90 | Event.Type.HEARTBEAT,
91 | Event.Type.OFFERS,
92 | Event.Type.OFFERS,
93 | Event.Type.HEARTBEAT,
94 | Event.Type.OFFERS,
95 | Event.Type.OFFERS,
96 | Event.Type.OFFERS,
97 | Event.Type.HEARTBEAT,
98 | Event.Type.OFFERS,
99 | Event.Type.OFFERS,
100 | Event.Type.HEARTBEAT,
101 | Event.Type.OFFERS,
102 | Event.Type.OFFERS,
103 | Event.Type.OFFERS,
104 | Event.Type.HEARTBEAT,
105 | Event.Type.OFFERS,
106 | Event.Type.OFFERS,
107 | Event.Type.OFFERS,
108 | Event.Type.HEARTBEAT,
109 | Event.Type.OFFERS,
110 | Event.Type.OFFERS,
111 | Event.Type.HEARTBEAT,
112 | Event.Type.OFFERS,
113 | Event.Type.OFFERS,
114 | Event.Type.OFFERS,
115 | Event.Type.HEARTBEAT,
116 | Event.Type.OFFERS,
117 | Event.Type.HEARTBEAT,
118 | Event.Type.HEARTBEAT,
119 | Event.Type.HEARTBEAT
120 | ));
121 | }
122 |
123 | @Test
124 | public void readEvents_eventsNotSpanningMultipleChunks() throws Exception {
125 | final List eventBufs = CollectionUtils.listMap(EVENT_CHUNKS, Unpooled::copiedBuffer);
126 |
127 | final List events = runTestOnChunks(eventBufs);
128 | assertThat(events).isEqualTo(EVENT_PROTOS);
129 | }
130 |
131 | @Test
132 | public void readEvents_eventsSpanningMultipleChunks() throws Exception {
133 | final byte[] allBytes = concatAllChunks(EVENT_CHUNKS);
134 | final List arrayChunks = partitionIntoArraysOfSize(allBytes, 10);
135 | final List bufChunks = CollectionUtils.listMap(arrayChunks, Unpooled::copiedBuffer);
136 |
137 | final List events = runTestOnChunks(bufChunks);
138 | assertThat(events).isEqualTo(EVENT_PROTOS);
139 | }
140 |
141 | @Test
142 | public void readEvents_multipleEventsInOneChunk() throws Exception {
143 | final List subHbOffer = newArrayList(
144 | TestingProtos.SUBSCRIBED,
145 | TestingProtos.HEARTBEAT,
146 | TestingProtos.OFFER
147 | );
148 | final List eventChunks = subHbOffer.stream()
149 | .map(AbstractMessageLite::toByteArray)
150 | .map(RecordIOUtils::createChunk)
151 | .collect(Collectors.toList());
152 | final List singleChunk = newArrayList(Unpooled.copiedBuffer(concatAllChunks(eventChunks)));
153 |
154 | final List events = runTestOnChunks(singleChunk);
155 | assertThat(events).isEqualTo(subHbOffer);
156 | }
157 |
158 | @NotNull
159 | static List runTestOnChunks(@NotNull final List chunks) {
160 | final TestSubscriber child = new TestSubscriber<>();
161 | final Subscriber call = new RecordIOOperator().call(child);
162 |
163 | assertThat(call).isInstanceOf(RecordIOOperator.RecordIOSubscriber.class);
164 |
165 | final RecordIOOperator.RecordIOSubscriber subscriber = (RecordIOOperator.RecordIOSubscriber) call;
166 | chunks.stream().forEach(subscriber::onNext);
167 | child.assertNoErrors();
168 | child.assertNotCompleted();
169 | child.assertNoTerminalEvent();
170 | assertThat(subscriber.messageSizeBytesBuffer).isEmpty();
171 | assertThat(subscriber.messageBytes).isNull();
172 | assertThat(subscriber.remainingBytesForMessage).isEqualTo(0);
173 |
174 | return CollectionUtils.listMap(child.getOnNextEvents(), (bs) -> {
175 | try {
176 | return Event.parseFrom(bs);
177 | } catch (InvalidProtocolBufferException e) {
178 | throw new RuntimeException(e);
179 | }
180 | });
181 | }
182 |
183 | @NotNull
184 | static List partitionIntoArraysOfSize(@NotNull final byte[] allBytes, final int chunkSize) {
185 | final List chunks = new ArrayList<>();
186 | final int numFullChunks = allBytes.length / chunkSize;
187 |
188 | int chunkStart = 0;
189 | int chunkEnd = 0;
190 |
191 | for (int i = 0; i < numFullChunks; i++) {
192 | chunkEnd += chunkSize;
193 | chunks.add(Arrays.copyOfRange(allBytes, chunkStart, chunkEnd));
194 | chunkStart = chunkEnd;
195 | }
196 |
197 | if (chunkStart < allBytes.length) {
198 | chunks.add(Arrays.copyOfRange(allBytes, chunkStart, allBytes.length));
199 | }
200 |
201 | return chunks;
202 | }
203 |
204 |
205 | @NotNull
206 | static byte[] concatAllChunks(@NotNull final List chunks) {
207 | return Bytes.concat(chunks.toArray(new byte[chunks.size()][]));
208 | }
209 |
210 | }
211 |
--------------------------------------------------------------------------------
/mesos-rxjava-recordio/src/test/java/com/mesosphere/mesos/rx/java/recordio/TestingProtos.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.recordio;
18 |
19 | import com.google.common.collect.Lists;
20 | import org.apache.mesos.v1.scheduler.Protos;
21 | import org.jetbrains.annotations.NotNull;
22 |
23 | /**
24 | * A set of utilities that are useful when testing code that requires instances of Mesos Protos
25 | */
26 | public final class TestingProtos {
27 |
28 | private TestingProtos() {}
29 |
30 | /**
31 | * A predefined instance of {@link org.apache.mesos.v1.scheduler.Protos.Event Event} representing a
32 | * {@link org.apache.mesos.v1.scheduler.Protos.Event.Type#HEARTBEAT HEARTBEAT} message.
33 | */
34 | @NotNull
35 | public static final Protos.Event HEARTBEAT = Protos.Event.newBuilder()
36 | .setType(Protos.Event.Type.HEARTBEAT)
37 | .build();
38 |
39 | /**
40 | * A predefined instance of {@link org.apache.mesos.v1.scheduler.Protos.Event Event} representing a
41 | * {@link org.apache.mesos.v1.scheduler.Protos.Event.Type#SUBSCRIBED SUBSCRIBED} message with the following values
42 | * set:
43 | *
44 | * - {@code frameworkId = "a7cfd25c-79bd-481c-91cc-692e5db1ec3d"}
45 | * - {@code heartbeatIntervalSeconds = 15}
46 | *
47 | */
48 | @NotNull
49 | public static final Protos.Event SUBSCRIBED = Protos.Event.newBuilder()
50 | .setType(Protos.Event.Type.SUBSCRIBED)
51 | .setSubscribed(
52 | Protos.Event.Subscribed.newBuilder()
53 | .setFrameworkId(org.apache.mesos.v1.Protos.FrameworkID.newBuilder()
54 | .setValue("a7cfd25c-79bd-481c-91cc-692e5db1ec3d")
55 | )
56 | .setHeartbeatIntervalSeconds(15)
57 | )
58 | .build();
59 |
60 | /**
61 | * A predefined instance of {@link org.apache.mesos.v1.scheduler.Protos.Event Event} representing a
62 | * {@link org.apache.mesos.v1.scheduler.Protos.Event.Type#OFFERS OFFERS} message containing a single offer with the
63 | * following values set:
64 | *
65 | * - {@code hostname = "host1"}
66 | * - {@code offerId = "offer1"}
67 | * - {@code agentId = "agent1"}
68 | * - {@code frameworkId = "frw1"}
69 | * - {@code cpus = 8.0}
70 | * - {@code mem = 8192}
71 | * - {@code disk = 8192}
72 | *
73 | */
74 | @NotNull
75 | public static final Protos.Event OFFER = Protos.Event.newBuilder()
76 | .setType(Protos.Event.Type.OFFERS)
77 | .setOffers(
78 | Protos.Event.Offers.newBuilder()
79 | .addAllOffers(Lists.newArrayList(
80 | org.apache.mesos.v1.Protos.Offer.newBuilder()
81 | .setHostname("host1")
82 | .setId(org.apache.mesos.v1.Protos.OfferID.newBuilder().setValue("offer1"))
83 | .setAgentId(org.apache.mesos.v1.Protos.AgentID.newBuilder().setValue("agent1"))
84 | .setFrameworkId(org.apache.mesos.v1.Protos.FrameworkID.newBuilder().setValue("frw1"))
85 | .addResources(org.apache.mesos.v1.Protos.Resource.newBuilder()
86 | .setName("cpus")
87 | .setRole("*")
88 | .setType(org.apache.mesos.v1.Protos.Value.Type.SCALAR)
89 | .setScalar(org.apache.mesos.v1.Protos.Value.Scalar.newBuilder().setValue(8d)))
90 | .addResources(org.apache.mesos.v1.Protos.Resource.newBuilder()
91 | .setName("mem")
92 | .setRole("*")
93 | .setType(org.apache.mesos.v1.Protos.Value.Type.SCALAR)
94 | .setScalar(org.apache.mesos.v1.Protos.Value.Scalar.newBuilder().setValue((long) 8192)))
95 | .addResources(org.apache.mesos.v1.Protos.Resource.newBuilder()
96 | .setName("disk")
97 | .setRole("*")
98 | .setType(org.apache.mesos.v1.Protos.Value.Type.SCALAR)
99 | .setScalar(org.apache.mesos.v1.Protos.Value.Scalar.newBuilder().setValue((long) 8192)))
100 | .build()
101 | ))
102 | )
103 | .build();
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/mesos-rxjava-recordio/src/test/resources/events.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d2iq-archive/mesos-rxjava/026afeb542f8e06eaa6f10fc4ed5b816e7014ad3/mesos-rxjava-recordio/src/test/resources/events.bin
--------------------------------------------------------------------------------
/mesos-rxjava-recordio/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | true
20 |
21 |
22 |
23 |
24 | %date %-5.5level [%-30.30thread] %-36.36logger{36} - %message%n
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
20 |
21 | com.mesosphere.mesos.rx.java
22 | mesos-rxjava
23 | 0.2.1-SNAPSHOT
24 |
25 | 4.0.0
26 |
27 | mesos-rxjava-test
28 |
29 | Mesos RxJava :: Test Tools
30 |
31 |
32 | A set of tools to help when writing tests.
33 |
34 |
35 |
36 | false
37 |
38 |
39 |
40 |
41 | com.mesosphere.mesos.rx.java
42 | mesos-rxjava-util
43 |
44 |
45 | io.reactivex
46 | rxnetty
47 |
48 |
49 | io.reactivex
50 | rxjava
51 |
52 |
53 | io.netty
54 | netty-codec-http
55 |
56 |
57 | io.netty
58 | netty-handler
59 |
60 |
61 |
62 | junit
63 | junit
64 | compile
65 |
66 |
67 |
68 |
69 | org.slf4j
70 | slf4j-api
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/src/main/java/com/mesosphere/mesos/rx/java/test/Async.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.test;
18 |
19 | import io.netty.util.concurrent.DefaultThreadFactory;
20 | import org.jetbrains.annotations.NotNull;
21 | import org.junit.rules.Verifier;
22 | import org.junit.runners.model.MultipleFailureException;
23 |
24 | import java.util.ArrayList;
25 | import java.util.Collections;
26 | import java.util.List;
27 | import java.util.Optional;
28 | import java.util.concurrent.*;
29 | import java.util.concurrent.atomic.AtomicInteger;
30 | import java.util.stream.Collectors;
31 |
32 | /**
33 | * A JUnit {@link org.junit.rules.TestRule Rule} that provides a convenience method to run async tasks.
34 | *
35 | * Async tasks that are run will be verfied to have completed at the end of the test run, if not an
36 | * {@link AssertionError} will be throw.
37 | *
38 | * If an {@link AssertionError} is the result of the async task (for example doing an assertion as part of the
39 | * task), that {@code AssertionError} will be rethrown.
40 | *
41 | *
42 | * public class TestClass {
43 | *
44 | * @Rule
45 | * public Async async = new Async();
46 | *
47 | * @Test
48 | * public void testWithAsyncWork() {
49 | * async.run(() ->
50 | * assertTrue(true)
51 | * );
52 | * }
53 | * }
54 | *
55 | */
56 | public final class Async extends Verifier {
57 |
58 | @NotNull
59 | private final AtomicInteger counter;
60 |
61 | @NotNull
62 | private final ExecutorService executor;
63 |
64 | @NotNull
65 | private final List tasks;
66 |
67 | public Async() {
68 | counter = new AtomicInteger(0);
69 | executor = Executors.newCachedThreadPool(new DefaultThreadFactory(Async.class, true));
70 | tasks = Collections.synchronizedList(new ArrayList<>());
71 | }
72 |
73 | /**
74 | * Run {@code r} on a background CachedThreadPool
75 | * @param r The task to run
76 | */
77 | public void run(@NotNull final ErrorableRunnable r) {
78 | run(String.format("Async-%d", counter.getAndIncrement()), r);
79 | }
80 |
81 | /**
82 | * Run {@code r} on a background CachedThreadPool
83 | * @param taskName The name of the task (used for error messages)
84 | * @param r The task to run
85 | */
86 | public void run(@NotNull final String taskName, @NotNull final ErrorableRunnable r) {
87 | tasks.add(new Task(taskName, executor.submit(r)));
88 | }
89 |
90 | @Override
91 | protected void verify() throws Throwable {
92 | final List errors = tasks.stream()
93 | .map(task -> {
94 | try {
95 | task.future.get(10, TimeUnit.MILLISECONDS);
96 | return Optional.empty();
97 | } catch (ExecutionException e) {
98 | final Throwable cause = e.getCause();
99 | if (cause != null && cause instanceof AssertionError) {
100 | return Optional.of(cause);
101 | }
102 | final String baseMessage = "Error while running Async";
103 | final String message = Optional.ofNullable(cause)
104 | .map(c -> baseMessage + ": " + c.getMessage())
105 | .orElse(baseMessage);
106 | return Optional.of(new AssertionError(message, cause));
107 | } catch (TimeoutException te) {
108 | return Optional.of(new AssertionError(String.format("Task [%s] still running after test completion", task.name)));
109 | } catch (InterruptedException e) {
110 | return Optional.of(new AssertionError(e));
111 | }
112 | })
113 | .filter(Optional::isPresent)
114 | .map(Optional::get)
115 | .collect(Collectors.toList());
116 | executor.shutdown();
117 | MultipleFailureException.assertEmpty(errors);
118 | }
119 |
120 | /**
121 | * Convenience type that allows submitting a runnable that may throw a checked exception.
122 | *
123 | * If a checked exception is throw while running the task it will be wrapped in a {@link RuntimeException} and
124 | * rethrown. If an {@link AssertionError} or {@link RuntimeException} is throw it will be rethrown without being
125 | * wrapped.
126 | */
127 | @FunctionalInterface
128 | public interface ErrorableRunnable extends Runnable {
129 | void invoke() throws Throwable;
130 | default void run() {
131 | try {
132 | invoke();
133 | } catch(AssertionError | RuntimeException e) {
134 | throw e;
135 | } catch (Throwable throwable) {
136 | throw new RuntimeException(throwable);
137 | }
138 | }
139 | }
140 |
141 | private static final class Task {
142 | @NotNull
143 | private final Future> future;
144 | @NotNull
145 | private final String name;
146 |
147 | public Task(@NotNull final String name, @NotNull final Future> future) {
148 | this.future = future;
149 | this.name = name;
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/src/main/java/com/mesosphere/mesos/rx/java/test/RecordIOUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.test;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 |
21 | import java.nio.charset.StandardCharsets;
22 |
23 | import static com.mesosphere.mesos.rx.java.util.Validations.checkNotNull;
24 |
25 | /**
26 | * A set of utilities for dealing with the RecordIO format.
27 | * @see RecordIO
28 | */
29 | public final class RecordIOUtils {
30 | private static final byte NEW_LINE_BYTE = "\n".getBytes(StandardCharsets.UTF_8)[0];
31 | private static final int NEW_LINE_BYTE_SIZE = 1;
32 |
33 | private RecordIOUtils() {}
34 |
35 | @NotNull
36 | public static byte[] createChunk(@NotNull final byte[] bytes) {
37 | checkNotNull(bytes, "bytes must not be null");
38 | final byte[] messageSize = Integer.toString(bytes.length).getBytes(StandardCharsets.UTF_8);
39 |
40 | final int messageSizeLength = messageSize.length;
41 | final int chunkSize = messageSizeLength + NEW_LINE_BYTE_SIZE + bytes.length;
42 | final byte[] chunk = new byte[chunkSize];
43 | System.arraycopy(messageSize, 0, chunk, 0, messageSizeLength);
44 | chunk[messageSizeLength] = NEW_LINE_BYTE;
45 | System.arraycopy(bytes, 0, chunk, messageSizeLength + 1, bytes.length);
46 | return chunk;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/src/main/java/com/mesosphere/mesos/rx/java/test/StringMessageCodec.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.test;
18 |
19 | import com.mesosphere.mesos.rx.java.util.MessageCodec;
20 | import org.jetbrains.annotations.NotNull;
21 |
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.io.InputStreamReader;
25 | import java.io.Reader;
26 | import java.nio.CharBuffer;
27 | import java.nio.charset.StandardCharsets;
28 |
29 | /**
30 | * {@link MessageCodec} for a {@link StandardCharsets#UTF_8 UTF-8} String
31 | * @see StandardCharsets#UTF_8
32 | */
33 | public final class StringMessageCodec implements MessageCodec {
34 |
35 | @NotNull
36 | public static final MessageCodec UTF8_STRING = new StringMessageCodec();
37 |
38 | private StringMessageCodec() {}
39 |
40 | @NotNull
41 | @Override
42 | public byte[] encode(@NotNull final String message) {
43 | return message.getBytes(StandardCharsets.UTF_8);
44 | }
45 |
46 | @NotNull
47 | @Override
48 | public String decode(@NotNull final byte[] bytes) {
49 | return new String(bytes, StandardCharsets.UTF_8);
50 | }
51 |
52 | @NotNull
53 | @Override
54 | public String decode(@NotNull final InputStream in) {
55 | try {
56 | final StringBuilder sb = new StringBuilder();
57 | final Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
58 | final CharBuffer buffer = CharBuffer.allocate(0x800);// 2k chars (4k bytes)
59 | while (reader.read(buffer) != -1) {
60 | buffer.flip();
61 | sb.append(buffer);
62 | buffer.clear();
63 | }
64 | return sb.toString();
65 | } catch (IOException e) {
66 | throw new RuntimeException(e);
67 | }
68 | }
69 |
70 | @NotNull
71 | @Override
72 | public String mediaType() {
73 | return "text/plain;charset=utf-8";
74 | }
75 |
76 | @NotNull
77 | @Override
78 | public String show(@NotNull final String message) {
79 | return message;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/src/main/java/com/mesosphere/mesos/rx/java/test/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * This package provides a set of tools to facilitate testing Mesos RxJava clients.
19 | */
20 | package com.mesosphere.mesos.rx.java.test;
21 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/src/main/java/com/mesosphere/mesos/rx/java/test/simulation/AwaitableEventSubscriberDecorator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.test.simulation;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 | import rx.Subscriber;
23 |
24 | import java.util.concurrent.Semaphore;
25 |
26 | /**
27 | * A {@link Subscriber} that will decorate a provided {@code Subscriber} adding the ability for a client to "block"
28 | * until an event is observed, at which point the client can proceed. This class is really only useful in the context
29 | * of writing tests.
30 | *
31 | * {@code TRACE} level logging can be enable for this class to see exactly what is happening in the subscriber
32 | * while events are flowing in or while {@link #awaitEvent() awaiting event(s)}.
33 | * @param The type of event that will be sent into this subscriber.
34 | */
35 | public final class AwaitableEventSubscriberDecorator extends Subscriber {
36 | private static final Logger LOGGER = LoggerFactory.getLogger(AwaitableEventSubscriberDecorator.class);
37 |
38 | @NotNull
39 | private final Subscriber obs;
40 |
41 | @NotNull
42 | private final Semaphore sem;
43 |
44 | /**
45 | * Creates the decorator based on wrapping the provided {@code obs}.
46 | * @param obs An {@code AwaitableEventSubscriberDecorator} that can be used to wait for event(s)
47 | */
48 | public AwaitableEventSubscriberDecorator(@NotNull final Subscriber obs) {
49 | this.obs = obs;
50 | this.sem = new Semaphore(0, true);
51 | }
52 |
53 | @Override
54 | public void onNext(final T t) {
55 | LOGGER.trace("onNext");
56 | obs.onNext(t);
57 | sem.release();
58 | LOGGER.trace("--- (queueLength, available) = ({}, {})", sem.getQueueLength(), sem.availablePermits());
59 | }
60 |
61 | @Override
62 | public void onError(final Throwable e) {
63 | LOGGER.trace("onError", e);
64 | obs.onError(e);
65 | releaseAll();
66 | LOGGER.trace("--- (queueLength, available) = ({}, {})", sem.getQueueLength(), sem.availablePermits());
67 | }
68 |
69 | @Override
70 | public void onCompleted() {
71 | LOGGER.trace("onCompleted");
72 | obs.onCompleted();
73 | releaseAll();
74 | LOGGER.trace("--- (queueLength, available) = ({}, {})", sem.getQueueLength(), sem.availablePermits());
75 | }
76 |
77 | /**
78 | * Block the invoking thread until {@code 1} event is observed by this instance. Any of {@link #onNext(Object)},
79 | * {@link #onError(Throwable)} or {@link #onCompleted()} are considered to be events.
80 | * @throws InterruptedException Throw in the waiting thread is interrupted.
81 | */
82 | public void awaitEvent() throws InterruptedException {
83 | awaitEvent(1);
84 | }
85 |
86 | /**
87 | * Block the invoking thread until {@code eventCount} events are observed by this instance. Any of
88 | * {@link #onNext(Object)}, {@link #onError(Throwable)} or {@link #onCompleted()} are considered to be events.
89 | *
90 | * In the case of {@link #onError(Throwable)} or {@link #onCompleted()} being invoked this method will return
91 | * regardless of the value of {@code eventCount}.
92 | * @param eventCount The number of events to block and wait for
93 | * @throws InterruptedException Throw in the waiting thread is interrupted.
94 | */
95 | public void awaitEvent(final int eventCount) throws InterruptedException {
96 | LOGGER.trace("awaitEvent(eventCount : {})", eventCount);
97 | LOGGER.trace(">>> (queueLength, available) = ({}, {})", sem.getQueueLength(), sem.availablePermits());
98 | sem.acquire(eventCount);
99 | LOGGER.trace("<<< (queueLength, available) = ({}, {})", sem.getQueueLength(), sem.availablePermits());
100 | }
101 |
102 | private void releaseAll() {
103 | sem.release(Integer.MAX_VALUE - sem.availablePermits());
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/src/main/java/com/mesosphere/mesos/rx/java/test/simulation/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | /**
17 | * This package provides a set of tools that can be used to run simulations of Apache Mesos components.
18 | * The primary goal is to facilitate making it easier to write self contained tests for Apache Mesos Frameworks.
19 | */
20 | package com.mesosphere.mesos.rx.java.test.simulation;
21 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/src/test/java/com/mesosphere/mesos/rx/java/test/AsyncTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.test;
18 |
19 | import com.google.common.collect.Sets;
20 | import org.junit.Test;
21 | import org.junit.runners.model.MultipleFailureException;
22 |
23 | import java.io.IOException;
24 | import java.util.Set;
25 | import java.util.stream.Collectors;
26 |
27 | import static org.assertj.core.api.Assertions.assertThat;
28 | import static org.junit.Assert.fail;
29 |
30 | public final class AsyncTest {
31 |
32 | @Test
33 | public void exceptionInTaskFailsTest_runtimeException() throws Throwable {
34 | final Async async = new Async();
35 | final IllegalStateException kaboom = new IllegalStateException("should fail");
36 | async.run(() -> {
37 | throw kaboom;
38 | });
39 |
40 | try {
41 | async.verify();
42 | } catch (AssertionError e) {
43 | assertThat(e).hasCauseExactlyInstanceOf(IllegalStateException.class);
44 | assertThat(e.getCause()).hasMessage("should fail");
45 | }
46 | }
47 |
48 | @Test
49 | public void exceptionInTaskFailsTest_checkedException() throws Throwable {
50 | final Async async = new Async();
51 | final IOException kaboom = new IOException("should fail");
52 | async.run(() -> {
53 | throw kaboom;
54 | });
55 |
56 | try {
57 | async.verify();
58 | } catch (AssertionError e) {
59 | assertThat(e).hasCauseExactlyInstanceOf(RuntimeException.class);
60 | assertThat(e.getCause()).hasCauseExactlyInstanceOf(IOException.class);
61 | assertThat(e.getCause().getCause()).hasMessage("should fail");
62 | }
63 | }
64 |
65 | @Test
66 | public void exceptionInTaskFailsTest_assertionError() throws Throwable {
67 | final Async async = new Async();
68 | async.run(() -> assertThat(false).isTrue());
69 |
70 | try {
71 | async.verify();
72 | } catch (AssertionError e) {
73 | assertThat(e).hasNoCause();
74 | }
75 | }
76 |
77 | @Test
78 | public void exceptionInTaskFailsTest_taskRunningAfterTestComplete() throws Throwable {
79 | final Async async = new Async();
80 | async.run("sleep", () -> Thread.sleep(500));
81 |
82 | try {
83 | async.verify();
84 | } catch (AssertionError e) {
85 | assertThat(e).hasNoCause();
86 | assertThat(e).hasMessage("Task [sleep] still running after test completion");
87 | }
88 | }
89 |
90 | @Test
91 | public void exceptionInTaskFailsTest_taskRunningAfterTestComplete_noName() throws Throwable {
92 | final Async async = new Async();
93 | async.run(() -> Thread.sleep(500));
94 |
95 | try {
96 | async.verify();
97 | } catch (AssertionError e) {
98 | assertThat(e).hasNoCause();
99 | assertThat(e).hasMessage("Task [Async-0] still running after test completion");
100 | }
101 | }
102 |
103 | @Test
104 | public void cleanTaskResultsInNoError() throws Throwable {
105 | final Async async = new Async();
106 | async.run(() -> assertThat(true).isTrue());
107 |
108 | async.verify();
109 | }
110 |
111 | @Test
112 | public void multipleExceptionReportedWhenTheyOccur() {
113 | final Async async = new Async();
114 | final IllegalStateException exception1 = new IllegalStateException("exception-1");
115 | final IllegalStateException exception2 = new IllegalStateException("exception-2");
116 | final IllegalStateException exception3 = new IllegalStateException("exception-3");
117 | async.run(() -> {
118 | throw exception1;
119 | });
120 | async.run(() -> {
121 | throw exception2;
122 | });
123 | async.run(() -> {
124 | throw exception3;
125 | });
126 |
127 | try {
128 | async.verify();
129 | fail("Exception should have been thrown in verify");
130 | } catch (MultipleFailureException mfe) {
131 | final Set errorMessages = mfe.getFailures().stream()
132 | .map(Throwable::getCause)
133 | .map(Throwable::getMessage)
134 | .collect(Collectors.toSet());
135 | assertThat(errorMessages).isEqualTo(Sets.newHashSet("exception-1", "exception-2", "exception-3"));
136 | assertThat(mfe.getMessage()).contains("Error while running Async: exception-1");
137 | } catch (Throwable e) {
138 | fail("Expected MultipleFailureException but got: " + e.getClass().getName());
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/src/test/java/com/mesosphere/mesos/rx/java/test/TcpSocketProxyTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.test;
18 |
19 | import io.netty.buffer.ByteBuf;
20 | import io.reactivex.netty.RxNetty;
21 | import io.reactivex.netty.protocol.http.AbstractHttpContentHolder;
22 | import io.reactivex.netty.protocol.http.client.HttpClient;
23 | import io.reactivex.netty.protocol.http.client.HttpClientRequest;
24 | import io.reactivex.netty.protocol.http.server.HttpServer;
25 | import org.junit.Before;
26 | import org.junit.Rule;
27 | import org.junit.Test;
28 | import org.junit.rules.Timeout;
29 | import org.slf4j.Logger;
30 | import org.slf4j.LoggerFactory;
31 |
32 | import java.io.IOException;
33 | import java.net.InetSocketAddress;
34 | import java.net.URI;
35 | import java.nio.charset.StandardCharsets;
36 | import java.util.concurrent.TimeUnit;
37 |
38 | import static org.assertj.core.api.Assertions.assertThat;
39 | import static org.junit.Assert.fail;
40 |
41 | public class TcpSocketProxyTest {
42 | private static final Logger LOGGER = LoggerFactory.getLogger(TcpSocketProxyTest.class);
43 |
44 | @Rule
45 | public Timeout timeout = new Timeout(2, TimeUnit.SECONDS);
46 |
47 | private HttpServer server;
48 |
49 | @Before
50 | public void setUp() throws Exception {
51 | server = RxNetty.createHttpServer(0, (request, response) -> {
52 | response.writeString("Hello World");
53 | return response.close();
54 | });
55 | server.start();
56 | }
57 |
58 | @Test
59 | public void testConnectionTerminatedOnClose() throws Exception {
60 | final TcpSocketProxy proxy = new TcpSocketProxy(
61 | new InetSocketAddress("localhost", 0),
62 | new InetSocketAddress("localhost", server.getServerPort())
63 | );
64 | proxy.start();
65 |
66 | final int listenPort = proxy.getListenPort();
67 | final HttpClient client = RxNetty.createHttpClient("localhost", listenPort);
68 |
69 | final String first = client.submit(HttpClientRequest.createGet("/"))
70 | .flatMap(AbstractHttpContentHolder::getContent)
71 | .map(bb -> bb.toString(StandardCharsets.UTF_8))
72 | .toBlocking()
73 | .first();
74 |
75 | assertThat(first).isEqualTo("Hello World");
76 | LOGGER.info("first request done");
77 | proxy.shutdown();
78 | if (proxy.isShutdown()) {
79 | proxy.close();
80 | } else {
81 | fail("proxy should have been shutdown");
82 | }
83 |
84 | try {
85 | final URI uri = URI.create(String.format("http://localhost:%d/", listenPort));
86 | uri.toURL().getContent();
87 | fail("Shouldn't have been able to get content");
88 | } catch (IOException e) {
89 | // expected
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/src/test/java/com/mesosphere/mesos/rx/java/test/simulation/AwaitableEventSubscriberDecoratorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.test.simulation;
18 |
19 | import com.mesosphere.mesos.rx.java.test.Async;
20 | import org.junit.Rule;
21 | import org.junit.Test;
22 | import org.junit.rules.Timeout;
23 | import rx.Subscription;
24 | import rx.observers.TestSubscriber;
25 | import rx.subjects.BehaviorSubject;
26 |
27 | import java.util.concurrent.TimeUnit;
28 |
29 | public final class AwaitableEventSubscriberDecoratorTest {
30 |
31 | @Rule
32 | public Timeout timeout = new Timeout(1000, TimeUnit.MILLISECONDS);
33 |
34 | @Rule
35 | public Async async = new Async();
36 |
37 | @Test
38 | public void awaitEventWorks_onNext() throws Exception {
39 | final TestSubscriber testSubscriber = new TestSubscriber<>();
40 | final AwaitableEventSubscriberDecorator sub = new AwaitableEventSubscriberDecorator<>(testSubscriber);
41 |
42 | final BehaviorSubject subject = BehaviorSubject.create();
43 |
44 | final Subscription subscription = subject.subscribe(sub);
45 |
46 | async.run(() -> subject.onNext("hello"));
47 |
48 | sub.awaitEvent();
49 | testSubscriber.assertValue("hello");
50 | testSubscriber.assertNoTerminalEvent();
51 | subscription.unsubscribe();
52 | }
53 |
54 | @Test
55 | public void awaitEventWorks_onError() throws Exception {
56 | final TestSubscriber testSubscriber = new TestSubscriber<>();
57 | final AwaitableEventSubscriberDecorator sub = new AwaitableEventSubscriberDecorator<>(testSubscriber);
58 |
59 | final BehaviorSubject subject = BehaviorSubject.create();
60 |
61 | final Subscription subscription = subject.subscribe(sub);
62 |
63 | final RuntimeException e = new RuntimeException("doesn't matter");
64 | async.run(() -> subject.onError(e));
65 |
66 | sub.awaitEvent();
67 | testSubscriber.assertNoValues();
68 | testSubscriber.assertError(e);
69 | subscription.unsubscribe();
70 | }
71 |
72 | @Test
73 | public void awaitEventWorks_onCompleted() throws Exception {
74 | final TestSubscriber testSubscriber = new TestSubscriber<>();
75 | final AwaitableEventSubscriberDecorator sub = new AwaitableEventSubscriberDecorator<>(testSubscriber);
76 |
77 | final BehaviorSubject subject = BehaviorSubject.create();
78 |
79 | final Subscription subscription = subject.subscribe(sub);
80 |
81 | async.run(subject::onCompleted);
82 |
83 | sub.awaitEvent();
84 | testSubscriber.assertNoValues();
85 | testSubscriber.assertCompleted();
86 | subscription.unsubscribe();
87 | }
88 |
89 | @Test
90 | public void awaitEventWorks() throws Exception {
91 | final TestSubscriber testSubscriber = new TestSubscriber<>();
92 | final AwaitableEventSubscriberDecorator sub = new AwaitableEventSubscriberDecorator<>(testSubscriber);
93 |
94 | final BehaviorSubject subject = BehaviorSubject.create();
95 |
96 | final Subscription subscription = subject.subscribe(sub);
97 |
98 | async.run(() -> {
99 | subject.onNext("hello");
100 | subject.onNext("world");
101 | subject.onNext("!");
102 | subject.onCompleted();
103 | });
104 |
105 | sub.awaitEvent(Integer.MAX_VALUE);
106 | testSubscriber.assertValues("hello", "world", "!");
107 | testSubscriber.assertCompleted();
108 | testSubscriber.assertNoErrors();
109 | subscription.unsubscribe();
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/src/test/java/com/mesosphere/mesos/rx/java/test/simulation/MesosServerSimulationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.test.simulation;
18 |
19 | import com.mesosphere.mesos.rx.java.test.StringMessageCodec;
20 | import io.netty.buffer.ByteBuf;
21 | import io.netty.handler.codec.http.HttpResponseStatus;
22 | import io.reactivex.netty.RxNetty;
23 | import io.reactivex.netty.protocol.http.client.*;
24 | import org.jetbrains.annotations.NotNull;
25 | import org.junit.After;
26 | import org.junit.Before;
27 | import org.junit.Test;
28 | import rx.subjects.BehaviorSubject;
29 |
30 | import java.net.URI;
31 | import java.nio.charset.StandardCharsets;
32 |
33 | import static org.assertj.core.api.Assertions.assertThat;
34 |
35 | public final class MesosServerSimulationTest {
36 |
37 | private final MesosServerSimulation mesosServerSimulation = new MesosServerSimulation<>(
38 | BehaviorSubject.create(),
39 | StringMessageCodec.UTF8_STRING,
40 | StringMessageCodec.UTF8_STRING,
41 | "subscribe"::equals
42 | );
43 | private int serverPort;
44 |
45 | @Before
46 | public void setUp() throws Exception {
47 | serverPort = mesosServerSimulation.start();
48 | }
49 |
50 | @After
51 | public void tearDown() throws Exception {
52 | mesosServerSimulation.shutdown();
53 | }
54 |
55 | @Test
56 | public void server202_forValidCall() throws Exception {
57 | final URI uri = URI.create(String.format("http://localhost:%d/api/v1/scheduler", serverPort));
58 | final HttpClientResponse response = sendCall(uri, "decline");
59 |
60 | assertThat(response.getStatus()).isEqualTo(HttpResponseStatus.ACCEPTED);
61 | final HttpResponseHeaders headers = response.getHeaders();
62 | assertThat(headers.getHeader("Accept")).isEqualTo(StringMessageCodec.UTF8_STRING.mediaType());
63 |
64 | assertThat(mesosServerSimulation.getCallsReceived()).hasSize(1);
65 | assertThat(mesosServerSimulation.getCallsReceived()).contains("decline");
66 | }
67 |
68 | @Test
69 | public void server404() throws Exception {
70 | final URI uri = URI.create(String.format("http://localhost:%d/something", serverPort));
71 | final HttpClientResponse response = sendCall(uri, "decline");
72 |
73 | assertThat(response.getStatus()).isEqualTo(HttpResponseStatus.NOT_FOUND);
74 | final HttpResponseHeaders headers = response.getHeaders();
75 | assertThat(headers.getHeader("Accept")).isEqualTo(StringMessageCodec.UTF8_STRING.mediaType());
76 |
77 | assertThat(mesosServerSimulation.getCallsReceived()).hasSize(0);
78 | }
79 |
80 | @Test
81 | public void server400_nonPost() throws Exception {
82 | final URI uri = URI.create(String.format("http://localhost:%d/api/v1/scheduler", serverPort));
83 | final HttpClient httpClient = RxNetty.newHttpClientBuilder(uri.getHost(), uri.getPort())
84 | .pipelineConfigurator(new HttpClientPipelineConfigurator<>())
85 | .build();
86 |
87 | final HttpClientRequest request = HttpClientRequest.createGet(uri.getPath())
88 | .withHeader("Accept", StringMessageCodec.UTF8_STRING.mediaType());
89 |
90 | final HttpClientResponse response = httpClient.submit(request)
91 | .toBlocking()
92 | .last();
93 |
94 | assertThat(response.getStatus()).isEqualTo(HttpResponseStatus.BAD_REQUEST);
95 | final HttpResponseHeaders headers = response.getHeaders();
96 | assertThat(headers.getHeader("Accept")).isEqualTo(StringMessageCodec.UTF8_STRING.mediaType());
97 |
98 | assertThat(mesosServerSimulation.getCallsReceived()).hasSize(0);
99 | }
100 |
101 | @Test
102 | public void server400_invalidContentType() throws Exception {
103 | final URI uri = URI.create(String.format("http://localhost:%d/api/v1/scheduler", serverPort));
104 | final HttpClient httpClient = RxNetty.newHttpClientBuilder(uri.getHost(), uri.getPort())
105 | .pipelineConfigurator(new HttpClientPipelineConfigurator<>())
106 | .build();
107 |
108 | final byte[] data = StringMessageCodec.UTF8_STRING.encode("decline");
109 | final HttpClientRequest request = HttpClientRequest.createPost(uri.getPath())
110 | .withHeader("Content-Type", "application/octet-stream")
111 | .withHeader("Accept", "application/octet-stream")
112 | .withContent(data);
113 |
114 | final HttpClientResponse response = httpClient.submit(request)
115 | .toBlocking()
116 | .last();
117 |
118 | assertThat(response.getStatus()).isEqualTo(HttpResponseStatus.BAD_REQUEST);
119 | final HttpResponseHeaders headers = response.getHeaders();
120 | assertThat(headers.getHeader("Accept")).isEqualTo(StringMessageCodec.UTF8_STRING.mediaType());
121 |
122 | assertThat(mesosServerSimulation.getCallsReceived()).hasSize(0);
123 | }
124 |
125 | @Test
126 | public void server400_emptyBody() throws Exception {
127 | final URI uri = URI.create(String.format("http://localhost:%d/api/v1/scheduler", serverPort));
128 | final HttpClient httpClient = RxNetty.newHttpClientBuilder(uri.getHost(), uri.getPort())
129 | .pipelineConfigurator(new HttpClientPipelineConfigurator<>())
130 | .build();
131 |
132 | final HttpClientRequest request = HttpClientRequest.createPost(uri.getPath())
133 | .withHeader("Content-Type", StringMessageCodec.UTF8_STRING.mediaType())
134 | .withHeader("Accept", StringMessageCodec.UTF8_STRING.mediaType())
135 | .withContent(new byte[]{});
136 |
137 | final HttpClientResponse response = httpClient.submit(request)
138 | .toBlocking()
139 | .last();
140 |
141 | assertThat(response.getStatus()).isEqualTo(HttpResponseStatus.BAD_REQUEST);
142 | final HttpResponseHeaders headers = response.getHeaders();
143 | assertThat(headers.getHeader("Accept")).isEqualTo(StringMessageCodec.UTF8_STRING.mediaType());
144 |
145 | assertThat(mesosServerSimulation.getCallsReceived()).hasSize(0);
146 | }
147 |
148 | @NotNull
149 | private static HttpClientResponse sendCall(final URI uri, final String call) {
150 | final HttpClient httpClient = RxNetty.newHttpClientBuilder(uri.getHost(), uri.getPort())
151 | .pipelineConfigurator(new HttpClientPipelineConfigurator<>())
152 | .build();
153 |
154 | final byte[] data = call.getBytes(StandardCharsets.UTF_8);
155 | final HttpClientRequest request = HttpClientRequest.createPost(uri.getPath())
156 | .withHeader("Content-Type", StringMessageCodec.UTF8_STRING.mediaType())
157 | .withHeader("Accept", StringMessageCodec.UTF8_STRING.mediaType())
158 | .withContent(data);
159 |
160 | return httpClient.submit(request)
161 | .toBlocking()
162 | .last();
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/mesos-rxjava-test/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | true
20 |
21 |
22 |
23 |
24 | %date %-5.5level [%-30.30thread] %-36.36logger{36} - %message%n
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/mesos-rxjava-util/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
21 |
22 | mesos-rxjava
23 | com.mesosphere.mesos.rx.java
24 | 0.2.1-SNAPSHOT
25 |
26 | 4.0.0
27 |
28 | mesos-rxjava-util
29 |
30 | Mesos RxJava :: Util
31 |
32 |
33 | A set of common classes and types used across all modules of the project.
34 |
35 |
36 |
37 | false
38 |
39 |
40 |
41 |
47 |
48 | io.netty
49 | netty-codec-http
50 | test
51 |
52 |
53 | io.netty
54 | netty-handler
55 | test
56 |
57 |
58 | io.reactivex
59 | rxnetty
60 | test
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/mesos-rxjava-util/src/main/java/com/mesosphere/mesos/rx/java/util/CollectionUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.util;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 |
21 | import java.util.Arrays;
22 | import java.util.List;
23 | import java.util.function.Function;
24 | import java.util.stream.Collectors;
25 |
26 | /**
27 | * Set of utility methods that make it easier to work with certain types of Collections
28 | */
29 | public final class CollectionUtils {
30 | private CollectionUtils() {}
31 |
32 | @NotNull
33 | public static List listMap(@NotNull final List input, @NotNull final Function mapper) {
34 | return input
35 | .stream()
36 | .map(mapper)
37 | .collect(Collectors.toList());
38 | }
39 |
40 | /**
41 | * The contract of {@link List#equals(Object)} is that a "deep equals" is performed, however the actual
42 | * "deep equals" depends on the elements being compared. In the case of arrays the default
43 | * {@link Object#equals(Object)} is used.
44 | *
45 | * This method is a convenience utility to check if two lists of byte[] are "equal"
46 | * @param a The first List of byte[]s
47 | * @param b The second List of byte[]s
48 | * @return {@code true} if {@code a} and {@code b} are the exact same array (determined by {@code ==}) OR only if
49 | * {@code a} and {@code b} are the same size AND {@link Arrays#equals(byte[], byte[])} returns {@code true}
50 | * for every index matching element from {@code a} and {@code b}.
51 | */
52 | public static boolean deepEquals(@NotNull final List a, @NotNull final List b) {
53 | if (a == b) {
54 | return true;
55 | }
56 | if (a.size() != b.size()) {
57 | return false;
58 | }
59 | for (int i = 0; i < a.size(); i++) {
60 | if (!Arrays.equals(a.get(i), b.get(i))) {
61 | return false;
62 | }
63 | }
64 | return true;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/mesos-rxjava-util/src/main/java/com/mesosphere/mesos/rx/java/util/MessageCodec.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.util;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 |
21 | import java.io.InputStream;
22 |
23 | /**
24 | * A {@code MessageCodec} defines how values of type {@code T} can be serialized to and deserialized from sequences
25 | * of bytes.
26 | *
27 | * There should be at least one implementation of this interface for every type of message that can be sent to or
28 | * received.
29 | *
30 | * @param the message type that this codec is defined for
31 | */
32 | public interface MessageCodec {
33 |
34 | /**
35 | * Serialize the given {@code message} into an array of bytes.
36 | *
37 | * @param message the message to serialize
38 | * @return the serialized message
39 | */
40 | @NotNull
41 | byte[] encode(@NotNull final T message);
42 |
43 | /**
44 | * Deserialize the given byte array into a message.
45 | *
46 | * @param bytes the bytes to deserialize
47 | * @return the deserialized message
48 | * @throws RuntimeException If an error occurs when decoding {@code bytes}. If a checked exception is possible
49 | * it should be wrapped in a RuntimeException.
50 | */
51 | @NotNull
52 | T decode(@NotNull final byte[] bytes);
53 |
54 | /**
55 | * Deserialize the given byte array into a message.
56 | *
57 | * @param in The {@link InputStream} to deserialize the message from
58 | * @return the deserialized message
59 | * @throws RuntimeException If an error occurs when decoding {@code bytes}. If a checked exception is possible
60 | * it should be wrapped in a RuntimeException.
61 | */
62 | @NotNull
63 | T decode(@NotNull final InputStream in);
64 |
65 | /**
66 | * Returns the IANA media type of the serialized message
67 | * format handled by this object.
68 | *
69 | * The value returned by this method will be used in the {@code Content-Type} and {@code Accept} headers for
70 | * messages sent to and received from Mesos, respectively.
71 | *
72 | * @return the media type identifier
73 | */
74 | @NotNull
75 | String mediaType();
76 |
77 | /**
78 | * Renders the given {@code message} to informative, human-readable text.
79 | *
80 | * The intent of this method is to allow messages to be easily read in program logs and while debugging.
81 | *
82 | * @param message the message to render
83 | * @return the rendered message
84 | */
85 | @NotNull
86 | String show(@NotNull final T message);
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/mesos-rxjava-util/src/main/java/com/mesosphere/mesos/rx/java/util/UserAgent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.util;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 |
21 | import java.util.Arrays;
22 | import java.util.Collections;
23 | import java.util.List;
24 | import java.util.function.Function;
25 | import java.util.stream.Collectors;
26 |
27 | /**
28 | * This class represents an HTTP User-Agent Header
29 | */
30 | public final class UserAgent {
31 |
32 | @NotNull
33 | private final List entries;
34 | @NotNull
35 | private final String toStringValue;
36 |
37 | @SafeVarargs
38 | public UserAgent(@NotNull final Function, UserAgentEntry>... entries) {
39 | this.entries =
40 | Collections.unmodifiableList(
41 | Arrays.asList(entries)
42 | .stream()
43 | .map(f -> f.apply(this.getClass()))
44 | .collect(Collectors.toList())
45 | );
46 | this.toStringValue = this.entries
47 | .stream()
48 | .map(UserAgentEntry::toString)
49 | .collect(Collectors.joining(" "));
50 | }
51 |
52 | @NotNull
53 | public List getEntries() {
54 | return entries;
55 | }
56 |
57 | @Override
58 | public String toString() {
59 | return toStringValue;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/mesos-rxjava-util/src/main/java/com/mesosphere/mesos/rx/java/util/UserAgentEntries.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.util;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 | import org.jetbrains.annotations.Nullable;
21 |
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.util.Properties;
25 | import java.util.function.Function;
26 |
27 | /**
28 | * A set of utility methods that can be used to easily create {@link UserAgentEntry} objects.
29 | */
30 | public final class UserAgentEntries {
31 |
32 | private UserAgentEntries() {}
33 |
34 | /**
35 | * Convenience method used to construct a {@link UserAgentEntry} for passing to
36 | * {@link UserAgent#UserAgent(Function[])}.
37 | *
38 | * @param name The {@code name} for the constructed {@code UserAgentEntry}
39 | * @param version The {@code version} for the constructed {@code UserAgentEntry}
40 | * @return A function that will produce a new {@code UserAgentEntry} with the specified {@code name}
41 | * and {@code version}
42 | */
43 | @NotNull
44 | public static Function, UserAgentEntry> literal(@NotNull final String name, @NotNull final String version) {
45 | return (Class> c) -> new UserAgentEntry(name, version);
46 | }
47 |
48 | /**
49 | * Convenience method used to construct a {@link UserAgentEntry} for passing to
50 | * {@link UserAgent#UserAgent(Function[])}.
51 | *
52 | * @param name The {@code name} for the constructed {@code UserAgentEntry}
53 | * @param version The {@code version} for the constructed {@code UserAgentEntry}
54 | * @param details The {@code details} for the constructed {@code UserAgentEntry}
55 | * @return A function that will produce a new {@code UserAgentEntry} with the specified {@code name},
56 | * {@code version} and {@code details}
57 | */
58 | @NotNull
59 | public static Function, UserAgentEntry> literal(
60 | @NotNull final String name,
61 | @NotNull final String version,
62 | @Nullable final String details
63 | ) {
64 | return (Class> c) -> new UserAgentEntry(name, version, details);
65 | }
66 |
67 | /**
68 | * Creates a {@link Function} capable of creating a {@link UserAgentEntry} for the Gradle artifact designated by
69 | * {@code artifactId}. The UserAgentEntry returned will be of the form "artifactId / Implementation-Version", where
70 | * {@code Implementation-Version} is resolved from the artifact.properties generated by a Gradle build.
71 | *
72 | * The process of locating the specified artifact on the classpath will be evaluated relative to the class provided
73 | * when the resulting function is invoked (Typically when calling {@link UserAgent#UserAgent(Function[])}.
74 | *
75 | * @param artifactId Artifact Id used to resolve the Gradle artifact on the classpath.
76 | * @return A function that will attempt to resolve the {@code Implementation-Version} of a Gradle artifact.
77 | */
78 | @NotNull
79 | public static Function, UserAgentEntry> userAgentEntryForGradleArtifact(@NotNull final String artifactId) {
80 | return (Class> c) -> {
81 | final Properties props = loadProperties(c, String.format("/META-INF/%s.properties", artifactId));
82 | return new UserAgentEntry(props.getProperty("artifactId", artifactId), props.getProperty("Implementation-Version", "unknown-version"));
83 | };
84 | }
85 |
86 | /**
87 | * Creates a {@link Function} capable of creating a {@link UserAgentEntry} for the Maven artifact designated by
88 | * {@code artifactId}. The UserAgentEntry returned will be of the form "artifactId / version", where
89 | * {@code version} is resolved from the pom.properties generated by a Maven build.
90 | *
91 | * The process of locating the specified artifact on the classpath will be evaluated relative to the class provided
92 | * when the resulting function is invoked (Typically when calling {@link UserAgent#UserAgent(Function[])}.
93 | *
94 | * @param groupId Group Id used to resolve the Maven artifact on the classpath.
95 | * @param artifactId Artifact Id used to resolve the Maven artifact on the classpath.
96 | * @return A function that will attempt to resolve the {@code Implementation-Version} of a Maven artifact.
97 | */
98 | @NotNull
99 | public static Function, UserAgentEntry> userAgentEntryForMavenArtifact(@NotNull final String groupId, @NotNull final String artifactId) {
100 | return (Class> c) -> {
101 | final Properties props = loadProperties(c, String.format("/META-INF/maven/%s/%s/pom.properties", groupId, artifactId));
102 | return new UserAgentEntry(props.getProperty("artifactId", artifactId), props.getProperty("version", "unknown-version"));
103 | };
104 | }
105 |
106 | @NotNull
107 | private static Properties loadProperties(@NotNull final Class c, @NotNull final String resourcePath) {
108 | final Properties props = new Properties();
109 | try {
110 | final InputStream resourceAsStream = c.getResourceAsStream(resourcePath);
111 | if (resourceAsStream != null) {
112 | props.load(resourceAsStream);
113 | resourceAsStream.close();
114 | }
115 | } catch (IOException e) {
116 | throw new RuntimeException("Unable to load classpath resource " + resourcePath, e);
117 | }
118 | return props;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/mesos-rxjava-util/src/main/java/com/mesosphere/mesos/rx/java/util/UserAgentEntry.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.util;
18 |
19 | import org.jetbrains.annotations.NotNull;
20 | import org.jetbrains.annotations.Nullable;
21 |
22 | import java.util.Objects;
23 |
24 | /**
25 | * A specific Entry to be listed in the HTTP User-Agent header
26 | */
27 | public final class UserAgentEntry {
28 | @NotNull
29 | private final String name;
30 | @NotNull
31 | private final String version;
32 | @Nullable
33 | private final String details;
34 |
35 | public UserAgentEntry(@NotNull final String name, @NotNull final String version) {
36 | this(name, version, null);
37 | }
38 |
39 | public UserAgentEntry(@NotNull final String name, @NotNull final String version, @Nullable final String details) {
40 | this.name = name;
41 | this.version = version;
42 | this.details = details;
43 | }
44 |
45 | @NotNull
46 | public String getName() {
47 | return name;
48 | }
49 |
50 | @NotNull
51 | public String getVersion() {
52 | return version;
53 | }
54 |
55 | @Nullable
56 | public String getDetails() {
57 | return details;
58 | }
59 |
60 | @Override
61 | public boolean equals(final Object o) {
62 | if (this == o) {
63 | return true;
64 | }
65 | if (o == null || getClass() != o.getClass()) {
66 | return false;
67 | }
68 | final UserAgentEntry that = (UserAgentEntry) o;
69 | return Objects.equals(name, that.name) &&
70 | Objects.equals(version, that.version) &&
71 | Objects.equals(details, that.details);
72 | }
73 |
74 | @Override
75 | public int hashCode() {
76 | return Objects.hash(name, version, details);
77 | }
78 |
79 | @Override
80 | public String toString() {
81 | if (details != null) {
82 | return String.format("%s/%s (%s)", name, version, details);
83 | } else {
84 | return String.format("%s/%s", name, version);
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/mesos-rxjava-util/src/main/java/com/mesosphere/mesos/rx/java/util/Validations.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.util;
18 |
19 | import org.jetbrains.annotations.Nullable;
20 |
21 | /**
22 | * A collection of utility methods replicating functionality provided by the {@code Preconditions} class in Guava.
23 | * These methods are replicated here so that our projects production libraries don't have a dependency on Guava.
24 | */
25 | public class Validations {
26 |
27 | /**
28 | * Check that {@code ref} is non-null, throw a {@link NullPointerException} otherwise
29 |
30 | * @param ref The reference to check for nullity, and be returned if non-null
31 | * @param The type of ref
32 | * @return {@code ref} if non-null
33 | * @throws NullPointerException if {@code ref} is null
34 | */
35 | public static T checkNotNull(@Nullable T ref) {
36 | return checkNotNull(ref, null);
37 | }
38 |
39 | /**
40 | * Check that {@code ref} is non-null, throw a {@link NullPointerException} otherwise
41 | *
42 | * @param ref The reference to check for nullity, and be returned if non-null
43 | * @param errorMessage The error message to set on the {@link NullPointerException}
44 | * @param The type of ref
45 | * @return {@code ref} if non-null
46 | * @throws NullPointerException if {@code ref} is null
47 | */
48 | public static T checkNotNull(@Nullable T ref, @Nullable final String errorMessage) {
49 | if (ref == null) {
50 | final String nonNullErrorMessage = String.valueOf(errorMessage); // if null, the string will be "null"
51 | throw new NullPointerException(nonNullErrorMessage);
52 | }
53 | return ref;
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/mesos-rxjava-util/src/main/java/com/mesosphere/mesos/rx/java/util/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * Set of utilities used by mesos-rxjava modules and frameworks built on it.
19 | */
20 | package com.mesosphere.mesos.rx.java.util;
21 |
--------------------------------------------------------------------------------
/mesos-rxjava-util/src/test/java/com/mesosphere/mesos/rx/java/UserAgentTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java;
18 |
19 | import com.mesosphere.mesos.rx.java.util.UserAgent;
20 | import org.junit.Test;
21 |
22 | import java.util.regex.Pattern;
23 |
24 | import static com.mesosphere.mesos.rx.java.util.UserAgentEntries.literal;
25 | import static com.mesosphere.mesos.rx.java.util.UserAgentEntries.userAgentEntryForGradleArtifact;
26 | import static com.mesosphere.mesos.rx.java.util.UserAgentEntries.userAgentEntryForMavenArtifact;
27 | import static org.assertj.core.api.Assertions.assertThat;
28 |
29 | public final class UserAgentTest {
30 |
31 | @Test
32 | public void testArtifactPropertyResolutionFunctionsCorrectly_gradle() throws Exception {
33 | final UserAgent agent = new UserAgent(
34 | userAgentEntryForGradleArtifact("rxnetty")
35 | );
36 | assertThat(agent.toString()).matches(Pattern.compile("rxnetty/\\d+\\.\\d+\\.\\d+"));
37 | }
38 |
39 | @Test
40 | public void testArtifactPropertyResolutionFunctionsCorrectly_maven() throws Exception {
41 | final UserAgent agent = new UserAgent(
42 | userAgentEntryForMavenArtifact("org.assertj", "assertj-core")
43 | );
44 | assertThat(agent.toString()).matches(Pattern.compile("assertj-core/\\d+\\.\\d+\\.\\d+"));
45 | }
46 |
47 | @Test
48 | public void testEntriesOutputInCorrectOrder() throws Exception {
49 | final UserAgent agent = new UserAgent(
50 | literal("first", "1"),
51 | literal("second", "2"),
52 | literal("third", "3")
53 | );
54 |
55 | assertThat(agent.toString()).isEqualTo("first/1 second/2 third/3");
56 | }
57 |
58 | @Test
59 | public void testEntriesOutputInCorrectOrder_withDetails() throws Exception {
60 | final UserAgent agent = new UserAgent(
61 | literal("first", "1"),
62 | literal("second", "2", "details"),
63 | literal("third", "3")
64 | );
65 |
66 | assertThat(agent.toString()).isEqualTo("first/1 second/2 (details) third/3");
67 | }
68 |
69 | @Test
70 | public void unfoundResourceThrowsRuntimeException() throws Exception {
71 | try {
72 | final UserAgent agent = new UserAgent(
73 | userAgentEntryForGradleArtifact("something-that-does-not-exist")
74 | );
75 | } catch (RuntimeException e) {
76 | assertThat(e.getMessage()).isEqualTo("Unable to load classpath resource /META-INF/something-that-does-not-exist.properties");
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/mesos-rxjava-util/src/test/java/com/mesosphere/mesos/rx/java/util/CollectionUtilsTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Mesosphere, Inc
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mesosphere.mesos.rx.java.util;
18 |
19 | import org.junit.Test;
20 |
21 | import java.util.ArrayList;
22 |
23 | import static com.google.common.collect.Lists.newArrayList;
24 | import static java.util.Collections.unmodifiableList;
25 | import static org.assertj.core.api.Assertions.assertThat;
26 |
27 | public final class CollectionUtilsTest {
28 |
29 | @Test
30 | public void deepEquals_bothDistinctUnmodifiableArrayList() throws Exception {
31 | assertThat(CollectionUtils.deepEquals(
32 | unmodifiableList(newArrayList(b(1), b(2), b(3))),
33 | unmodifiableList(newArrayList(b(1), b(2), b(3)))
34 | )).isTrue();
35 | }
36 |
37 | @Test
38 | public void deepEquals_unmodifiableArrayListVsArrayList() throws Exception {
39 | assertThat(CollectionUtils.deepEquals(
40 | unmodifiableList(newArrayList(b(1), b(2), b(3))),
41 | newArrayList(b(1), b(2), b(3))
42 | )).isTrue();
43 | }
44 |
45 | @Test
46 | public void deepEquals_arrayListVsUnmodifiableArrayList() throws Exception {
47 | assertThat(CollectionUtils.deepEquals(
48 | newArrayList(b(1), b(2), b(3)),
49 | unmodifiableList(newArrayList(b(1), b(2), b(3)))
50 | )).isTrue();
51 | }
52 |
53 | @Test
54 | public void deepEquals_arrayListVsArrayList() throws Exception {
55 | assertThat(CollectionUtils.deepEquals(
56 | newArrayList(b(1), b(2), b(3)),
57 | newArrayList(b(1), b(2), b(3))
58 | )).isTrue();
59 | }
60 |
61 | @Test
62 | public void deepEquals_sameList() throws Exception {
63 | final ArrayList list = newArrayList(b(1), b(2), b(3));
64 | assertThat(CollectionUtils.deepEquals(
65 | list, list
66 | )).isTrue();
67 | }
68 |
69 | @Test
70 | public void deepEquals_differentSizeLists() throws Exception {
71 | assertThat(CollectionUtils.deepEquals(
72 | newArrayList(b(1), b(2), b(3), b(4)),
73 | newArrayList(b(1), b(2), b(3))
74 | )).isFalse();
75 | }
76 |
77 | @Test
78 | public void deepEquals_differentContents() throws Exception {
79 | assertThat(CollectionUtils.deepEquals(
80 | newArrayList(b(11), b(12), b(13)),
81 | newArrayList(b(1), b(2), b(3))
82 | )).isFalse();
83 | }
84 |
85 | private static byte[] b(final int b) {
86 | return new byte[]{(byte) b};
87 | }
88 | }
89 |
--------------------------------------------------------------------------------