├── .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 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 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 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 | --------------------------------------------------------------------------------