├── .travis.yml ├── CHANGES.md ├── LICENSE ├── NOTICE ├── README.md ├── pom.xml └── src ├── build └── findbugs-exclude.xml ├── main ├── generated-sources │ └── protobuf │ │ └── mesos │ │ └── internal │ │ ├── Messages.java │ │ └── state │ │ └── State.java ├── java │ └── com │ │ └── groupon │ │ └── mesos │ │ ├── JesosExecutorDriver.java │ │ ├── JesosSchedulerDriver.java │ │ ├── executor │ │ ├── ExecutorCallback.java │ │ ├── ExecutorDriverContext.java │ │ ├── ExecutorMessageEnvelope.java │ │ ├── InternalExecutorDriver.java │ │ └── LocalExecutorMessageProcessor.java │ │ ├── scheduler │ │ ├── InternalSchedulerDriver.java │ │ ├── LocalSchedulerMessageProcessor.java │ │ ├── SchedulerCallback.java │ │ ├── SchedulerDriverContext.java │ │ └── SchedulerMessageEnvelope.java │ │ ├── state │ │ ├── JLevelDBState.java │ │ ├── JVariable.java │ │ ├── JZookeeperState.java │ │ └── ZookeeperVariable.java │ │ ├── util │ │ ├── AbstractMessageEnvelope.java │ │ ├── CloseableExecutors.java │ │ ├── HttpProtocolReceiver.java │ │ ├── HttpProtocolSender.java │ │ ├── Log.java │ │ ├── ManagedEventBus.java │ │ ├── NetworkUtil.java │ │ ├── ProtobufRegistry.java │ │ ├── TimeUtil.java │ │ ├── UPID.java │ │ └── UUIDUtil.java │ │ └── zookeeper │ │ ├── DetectMessage.java │ │ ├── MasterUpdateMessage.java │ │ └── ZookeeperMasterDetector.java └── protobuf │ ├── mesos │ └── mesos.proto │ ├── messages.proto │ └── state.proto └── test ├── generated-sources └── protobuf │ └── com │ └── groupon │ └── jesos │ └── TestProtos.java ├── java └── com │ └── groupon │ └── mesos │ ├── state │ ├── AbstractTestState.java │ ├── TestJLevelDBState.java │ ├── TestJVariable.java │ └── TestJZookeeperState.java │ ├── testutil │ └── EmbeddedZookeeper.java │ └── util │ ├── TestProtobufRegistry.java │ └── TestUUIDUtil.java └── protobuf └── test.proto /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk7 4 | - openjdk7 5 | - oraclejdk8 6 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 1.4 4 | 5 | * 2015-03-24 - Add new Mesos 0.22.0 API call (acknowledgeStatusUpdate) 6 | 7 | ## Version 1.3 8 | 9 | retracted. Do not use. 10 | 11 | ## Version 1.2 12 | 13 | * 2015-03-16 - Add Extension registry to allow extension parsing for 14 | protobuf messages. 15 | 16 | ## Version 1.1 17 | 18 | * 2014-12-01 - Upgrade Mesos dependencies to 0.21.0 19 | * 2014-11-12 - Lock number of worker threads for the protocol receiver, 20 | otherwise, on a server with many cores, there will be hundreds 21 | of idle threads created. 22 | 23 | ## Version 1.0 24 | 25 | * 2014-09-29 - First public release 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2012-present Apache Software Foundation 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | 205 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | JMesos - Pure Java API for Apache Mesos 2 | Copyright 2014-2015, Groupon, Inc. 3 | Licensed under the Apache Software License v2. 4 | 5 | This product includes software developed at 6 | The Apache Software Foundation (http://www.apache.org/). 7 | 8 | This product includes software that is 9 | Copyright 2010 Proofpoint, Inc. 10 | Licensed under the Apache License, Version 2.0 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jesos - A pure Java implementation of the Apache Mesos APIs. 2 | 3 | * Requires Apache Mesos 0.19.0 or later. Does not work with any 4 | earlier version of Apache Mesos. 5 | 6 | * Version 1.0 is built against Apache Mesos 0.20.1 7 | * Version 1.1 is built against Apache Mesos 0.21.0 8 | * Version 1.2 is built against Apache Mesos 0.21.0 9 | * Version 1.3 is built against Apache Mesos 0.22.0 10 | * Version 1.4 is built against Apache Mesos 0.22.0 11 | 12 | Any version will work any older version of Apache Mesos, as 13 | long as it supports the HTTP protocol (which is the 0.19.0 release 14 | and later). 15 | 16 | * Only works with Apache Zookeeper state management. Does not support 17 | local master (master must be `zk:.../`). 18 | 19 | * Does not do the SASL dance; Apache Mesos authentication is not 20 | implemented. 21 | 22 | * Does not do Apache Zookeeper authentication (needs ripping out 23 | zkclient to do that). There is an experimental pull request to use 24 | Apache Curator, but that is just that: experimental. 25 | 26 | ## Status 27 | 28 | * SchedulerDriver - code completed, tested with various schedulers 29 | * ExecutorDriver - code completed, lightly tested 30 | * State Management - code completed, tests for leveldb and ZooKeeper. 31 | 32 | ## TODO 33 | 34 | * More tests. Spin up mesos from tests and do end-to-end testing. 35 | * Get war stories running Marathon, Aurora, Singularity etc. on top of jesos. 36 | 37 | ## Usage 38 | 39 | * Install using `maven clean install` 40 | * Replace usage of `org.apache.mesos.MesosSchedulerDriver` with `com.groupon.mesos.JesosSchedulerDriver` 41 | * Replace usage of `org.apache.mesos.MesosExecutorDriver` with `com.groupon.mesos.JesosExecutorDriver` 42 | * Replace usage of `org.apache.mesos.state.LevelDBState` with `com.groupon.mesos.JLevelDBState` 43 | * Replace usage of `org.apache.mesos.state.ZooKeeperState` with `com.groupon.mesos.JZookeeperState` 44 | * Profit 45 | 46 | 47 | ---- 48 | Copyright (C) 2014-2015, Groupon, Inc. 49 | Licensed under the Apache Software License V2 (see the LICENSE file in this folder). 50 | 51 | [![Build Status](https://travis-ci.org/groupon/jesos.svg?branch=master)](https://travis-ci.org/groupon/jesos) 52 | -------------------------------------------------------------------------------- /src/build/findbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/JesosExecutorDriver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos; 15 | 16 | import java.io.Closeable; 17 | import java.io.IOException; 18 | 19 | import com.groupon.mesos.executor.InternalExecutorDriver; 20 | import com.groupon.mesos.util.UPID; 21 | 22 | import org.apache.mesos.Executor; 23 | import org.apache.mesos.ExecutorDriver; 24 | import org.apache.mesos.Protos.ExecutorID; 25 | import org.apache.mesos.Protos.FrameworkID; 26 | import org.apache.mesos.Protos.SlaveID; 27 | 28 | public class JesosExecutorDriver 29 | extends InternalExecutorDriver 30 | implements ExecutorDriver, Closeable 31 | 32 | { 33 | public JesosExecutorDriver(final Executor executor) throws IOException 34 | { 35 | this(executor, 36 | UPID.create(System.getenv("MESOS_SLAVE_PID")), 37 | SlaveID.newBuilder().setValue(System.getenv("MESOS_SLAVE_ID")).build(), 38 | FrameworkID.newBuilder().setValue(System.getenv("MESOS_FRAMEWORK_ID")).build(), 39 | ExecutorID.newBuilder().setValue(System.getenv("MESOS_EXECUTOR_ID")).build()); 40 | } 41 | 42 | public JesosExecutorDriver(final Executor executor, 43 | final UPID slaveUpid, 44 | final SlaveID slaveId, 45 | final FrameworkID frameworkId, 46 | final ExecutorID executorId) throws IOException 47 | { 48 | super(executor, slaveUpid, slaveId, frameworkId, executorId); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/JesosSchedulerDriver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos; 15 | 16 | import java.io.Closeable; 17 | import java.io.IOException; 18 | 19 | import com.groupon.mesos.scheduler.InternalSchedulerDriver; 20 | 21 | import org.apache.mesos.Protos.Credential; 22 | import org.apache.mesos.Protos.FrameworkInfo; 23 | import org.apache.mesos.Scheduler; 24 | import org.apache.mesos.SchedulerDriver; 25 | 26 | public class JesosSchedulerDriver 27 | extends InternalSchedulerDriver 28 | implements SchedulerDriver, Closeable 29 | { 30 | /** 31 | * Creates a new driver for the specified scheduler. The master 32 | * must be specified as 33 | * 34 | * zk://host1:port1,host2:port2,.../path 35 | * zk://username:password@host1:port1,host2:port2,.../path 36 | * 37 | * The driver will attempt to "failover" if the specified 38 | * FrameworkInfo includes a valid FrameworkID. 39 | */ 40 | public JesosSchedulerDriver(final Scheduler scheduler, 41 | final FrameworkInfo frameworkInfo, 42 | final String master) throws IOException 43 | { 44 | super(scheduler, frameworkInfo, master, true, null); 45 | } 46 | 47 | public JesosSchedulerDriver(final Scheduler scheduler, 48 | final FrameworkInfo frameworkInfo, 49 | final String master, 50 | final Credential credential) 51 | throws IOException 52 | { 53 | super(scheduler, frameworkInfo, master, true, credential); 54 | } 55 | 56 | public JesosSchedulerDriver(final Scheduler scheduler, 57 | final FrameworkInfo frameworkInfo, 58 | final String master, 59 | boolean implicitAcknowledges) throws IOException 60 | { 61 | super(scheduler, frameworkInfo, master, implicitAcknowledges, null); 62 | } 63 | 64 | public JesosSchedulerDriver(final Scheduler scheduler, 65 | final FrameworkInfo frameworkInfo, 66 | final String master, 67 | boolean implicitAcknowledges, 68 | final Credential credential) 69 | throws IOException 70 | { 71 | super(scheduler, frameworkInfo, master, implicitAcknowledges, credential); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/executor/ExecutorCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.executor; 15 | 16 | import org.apache.mesos.Executor; 17 | import org.apache.mesos.ExecutorDriver; 18 | 19 | /** 20 | * Callbacks into the executor, Runnables should be executed with a Threadpool for decoupling. 21 | */ 22 | interface ExecutorCallback 23 | { 24 | Runnable getCallback(Executor executor, ExecutorDriver executorDriver); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/executor/ExecutorDriverContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.executor; 15 | 16 | import static org.apache.mesos.Protos.Status.DRIVER_NOT_STARTED; 17 | 18 | import java.io.IOException; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.UUID; 22 | import java.util.concurrent.BlockingQueue; 23 | import java.util.concurrent.ConcurrentHashMap; 24 | import java.util.concurrent.ConcurrentMap; 25 | import java.util.concurrent.LinkedBlockingQueue; 26 | import java.util.concurrent.atomic.AtomicReference; 27 | 28 | import com.google.common.net.HostAndPort; 29 | import com.google.common.util.concurrent.ListenableFuture; 30 | import com.google.common.util.concurrent.SettableFuture; 31 | import com.groupon.mesos.util.NetworkUtil; 32 | import com.groupon.mesos.util.UPID; 33 | 34 | import org.apache.mesos.Protos; 35 | import org.apache.mesos.Protos.ExecutorID; 36 | import org.apache.mesos.Protos.FrameworkID; 37 | import org.apache.mesos.Protos.SlaveID; 38 | import org.apache.mesos.Protos.Status; 39 | 40 | import mesos.internal.Messages.StatusUpdate; 41 | 42 | /** 43 | * Context for the Executor. 44 | */ 45 | class ExecutorDriverContext 46 | { 47 | private final AtomicReference stateMachine = new AtomicReference<>(DRIVER_NOT_STARTED); 48 | private final BlockingQueue> stateMachineFutures = new LinkedBlockingQueue<>(); 49 | 50 | private final UPID driverUpid; 51 | 52 | private final UPID slaveUpid; 53 | private final SlaveID slaveId; 54 | private final FrameworkID frameworkId; 55 | private final ExecutorID executorId; 56 | 57 | private final ConcurrentMap updates = new ConcurrentHashMap<>(16, 0.75f, 3); 58 | 59 | ExecutorDriverContext(final String hostName, 60 | final UPID slaveUpid, 61 | final SlaveID slaveId, 62 | final FrameworkID frameworkId, 63 | final ExecutorID executorId) 64 | throws IOException 65 | { 66 | this.slaveUpid = slaveUpid; 67 | this.slaveId = slaveId; 68 | this.frameworkId = frameworkId; 69 | this.executorId = executorId; 70 | 71 | this.driverUpid = UPID.fromParts(UUID.randomUUID().toString(), 72 | HostAndPort.fromParts(hostName, NetworkUtil.findUnusedPort())); 73 | } 74 | 75 | UPID getSlaveUPID() 76 | { 77 | return slaveUpid; 78 | } 79 | 80 | SlaveID getSlaveId() 81 | { 82 | return slaveId; 83 | } 84 | 85 | FrameworkID getFrameworkId() 86 | { 87 | return frameworkId; 88 | } 89 | 90 | ExecutorID getExecutorId() 91 | { 92 | return executorId; 93 | } 94 | 95 | UPID getDriverUPID() 96 | { 97 | return driverUpid; 98 | } 99 | 100 | // 101 | // Status update management 102 | // 103 | 104 | void addUpdate(final UUID key, final StatusUpdate update) 105 | { 106 | updates.put(key, update); 107 | } 108 | 109 | void removeUpdate(final UUID key) 110 | { 111 | updates.remove(key); 112 | } 113 | 114 | Iterable getUpdates() 115 | { 116 | return updates.values(); 117 | } 118 | 119 | // 120 | // State machine management 121 | // 122 | 123 | synchronized void setStateMachine(final Status status) 124 | { 125 | final Status oldStatus = stateMachine.getAndSet(status); 126 | 127 | if (status != oldStatus) { 128 | final List> settableFutures = new ArrayList<>(stateMachineFutures.size()); 129 | stateMachineFutures.drainTo(settableFutures); 130 | 131 | for (final SettableFuture future : settableFutures) { 132 | future.set(status); 133 | } 134 | } 135 | } 136 | 137 | synchronized Status getStateMachine() 138 | { 139 | return stateMachine.get(); 140 | } 141 | 142 | synchronized ListenableFuture waitForStateChange(final Status expectedStatus) 143 | { 144 | final SettableFuture future = SettableFuture.create(); 145 | if (!isStateMachine(expectedStatus)) { 146 | // Current status is not the expected status. Return 147 | // it immediately. 148 | future.set(stateMachine.get()); 149 | } 150 | else { 151 | // Current status is expected status: Queue up for a status change. 152 | stateMachineFutures.add(future); 153 | } 154 | return future; 155 | } 156 | 157 | synchronized boolean isStateMachine(final Protos.Status ... statusWanted) 158 | { 159 | final Protos.Status currentState = stateMachine.get(); 160 | for (final Protos.Status status : statusWanted) { 161 | if (currentState == status) { 162 | return true; 163 | } 164 | } 165 | return false; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/executor/ExecutorMessageEnvelope.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.executor; 15 | 16 | import com.google.protobuf.Message; 17 | import com.groupon.mesos.util.AbstractMessageEnvelope; 18 | import com.groupon.mesos.util.UPID; 19 | 20 | import mesos.internal.Messages.ExecutorRegisteredMessage; 21 | import mesos.internal.Messages.ExecutorReregisteredMessage; 22 | import mesos.internal.Messages.FrameworkToExecutorMessage; 23 | import mesos.internal.Messages.KillTaskMessage; 24 | import mesos.internal.Messages.ReconnectExecutorMessage; 25 | import mesos.internal.Messages.RunTaskMessage; 26 | import mesos.internal.Messages.ShutdownExecutorMessage; 27 | import mesos.internal.Messages.StatusUpdateAcknowledgementMessage; 28 | 29 | /** 30 | * Executor related messages. 31 | */ 32 | public abstract class ExecutorMessageEnvelope extends AbstractMessageEnvelope 33 | { 34 | protected ExecutorMessageEnvelope(final UPID sender, final UPID recipient, final T message) 35 | { 36 | super(sender, recipient, message); 37 | } 38 | 39 | /** 40 | * Send a message to the Mesos framework. 41 | */ 42 | public static class RemoteMessageEnvelope extends ExecutorMessageEnvelope 43 | { 44 | public RemoteMessageEnvelope(final UPID sender, final UPID recipient, final Message message) 45 | { 46 | super(sender, recipient, message); 47 | } 48 | } 49 | 50 | public static class ExecutorRegisteredMessageEnvelope extends ExecutorMessageEnvelope 51 | { 52 | public ExecutorRegisteredMessageEnvelope(final UPID sender, final UPID recipient, final ExecutorRegisteredMessage message) 53 | { 54 | super(sender, recipient, message); 55 | } 56 | } 57 | 58 | public static class ExecutorReregisteredMessageEnvelope extends ExecutorMessageEnvelope 59 | { 60 | public ExecutorReregisteredMessageEnvelope(final UPID sender, final UPID recipient, final ExecutorReregisteredMessage message) 61 | { 62 | super(sender, recipient, message); 63 | } 64 | } 65 | 66 | public static class ReconnectExecutorMessageEnvelope extends ExecutorMessageEnvelope 67 | { 68 | public ReconnectExecutorMessageEnvelope(final UPID sender, final UPID recipient, final ReconnectExecutorMessage message) 69 | { 70 | super(sender, recipient, message); 71 | } 72 | } 73 | 74 | public static class RunTaskMessageEnvelope extends ExecutorMessageEnvelope 75 | { 76 | public RunTaskMessageEnvelope(final UPID sender, final UPID recipient, final RunTaskMessage message) 77 | { 78 | super(sender, recipient, message); 79 | } 80 | } 81 | 82 | public static class KillTaskMessageEnvelope extends ExecutorMessageEnvelope 83 | { 84 | public KillTaskMessageEnvelope(final UPID sender, final UPID recipient, final KillTaskMessage message) 85 | { 86 | super(sender, recipient, message); 87 | } 88 | } 89 | 90 | public static class StatusUpdateAcknowledgementMessageEnvelope extends ExecutorMessageEnvelope 91 | { 92 | public StatusUpdateAcknowledgementMessageEnvelope(final UPID sender, final UPID recipient, final StatusUpdateAcknowledgementMessage message) 93 | { 94 | super(sender, recipient, message); 95 | } 96 | } 97 | 98 | public static class FrameworkToExecutorMessageEnvelope extends ExecutorMessageEnvelope 99 | { 100 | public FrameworkToExecutorMessageEnvelope(final UPID sender, final UPID recipient, final FrameworkToExecutorMessage message) 101 | { 102 | super(sender, recipient, message); 103 | } 104 | } 105 | 106 | public static class ShutdownExecutorMessageEnvelope extends ExecutorMessageEnvelope 107 | { 108 | public ShutdownExecutorMessageEnvelope(final UPID sender, final UPID recipient, final ShutdownExecutorMessage message) 109 | { 110 | super(sender, recipient, message); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/executor/InternalExecutorDriver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.executor; 15 | 16 | import static java.lang.String.format; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | import static com.google.common.base.Preconditions.checkState; 20 | 21 | import static org.apache.mesos.Protos.Status.DRIVER_ABORTED; 22 | import static org.apache.mesos.Protos.Status.DRIVER_NOT_STARTED; 23 | import static org.apache.mesos.Protos.Status.DRIVER_RUNNING; 24 | import static org.apache.mesos.Protos.Status.DRIVER_STOPPED; 25 | import static org.apache.mesos.Protos.TaskState.TASK_STAGING; 26 | 27 | import java.io.Closeable; 28 | import java.io.IOException; 29 | import java.util.UUID; 30 | import java.util.concurrent.ExecutionException; 31 | import java.util.concurrent.Executors; 32 | import java.util.concurrent.Future; 33 | import java.util.concurrent.ScheduledExecutorService; 34 | 35 | import com.google.common.base.Throwables; 36 | import com.google.common.eventbus.Subscribe; 37 | import com.google.common.io.Closer; 38 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 39 | import com.google.protobuf.ByteString; 40 | import com.google.protobuf.Message; 41 | import com.groupon.mesos.executor.ExecutorMessageEnvelope.RemoteMessageEnvelope; 42 | import com.groupon.mesos.util.CloseableExecutors; 43 | import com.groupon.mesos.util.HttpProtocolReceiver; 44 | import com.groupon.mesos.util.HttpProtocolSender; 45 | import com.groupon.mesos.util.Log; 46 | import com.groupon.mesos.util.ManagedEventBus; 47 | import com.groupon.mesos.util.NetworkUtil; 48 | import com.groupon.mesos.util.TimeUtil; 49 | import com.groupon.mesos.util.UPID; 50 | import com.groupon.mesos.util.UUIDUtil; 51 | 52 | import org.apache.mesos.Executor; 53 | import org.apache.mesos.ExecutorDriver; 54 | import org.apache.mesos.Protos.ExecutorID; 55 | import org.apache.mesos.Protos.FrameworkID; 56 | import org.apache.mesos.Protos.SlaveID; 57 | import org.apache.mesos.Protos.Status; 58 | import org.apache.mesos.Protos.TaskStatus; 59 | 60 | import mesos.internal.Messages.ExecutorToFrameworkMessage; 61 | import mesos.internal.Messages.RegisterExecutorMessage; 62 | import mesos.internal.Messages.StatusUpdate; 63 | import mesos.internal.Messages.StatusUpdateMessage; 64 | 65 | public abstract class InternalExecutorDriver 66 | implements ExecutorDriver, Closeable 67 | { 68 | private static final Log LOG = Log.getLog(InternalExecutorDriver.class); 69 | 70 | private final Executor executor; 71 | 72 | private final HttpProtocolReceiver receiver; 73 | private final HttpProtocolSender sender; 74 | 75 | private final ScheduledExecutorService callbackExecutor; 76 | 77 | private final ManagedEventBus eventBus; 78 | 79 | private final LocalExecutorMessageProcessor localMessageProcessor; 80 | 81 | private final Closer closer = Closer.create(); 82 | 83 | private final ExecutorDriverContext context; 84 | 85 | protected InternalExecutorDriver(final Executor executor, 86 | final UPID slaveUpid, 87 | final SlaveID slaveId, 88 | final FrameworkID frameworkId, 89 | final ExecutorID executorId) throws IOException 90 | { 91 | this.executor = checkNotNull(executor, "executor is null"); 92 | 93 | checkNotNull(slaveUpid, "slaveUpid is null"); 94 | checkNotNull(slaveId, "slaveId is null"); 95 | checkNotNull(frameworkId, "frameworkId is null"); 96 | checkNotNull(executorId, "executorId is null"); 97 | 98 | LOG.debug("Slave UPID: %s", slaveUpid.asString()); 99 | LOG.debug("Slave ID: %s", slaveId.getValue()); 100 | LOG.debug("Framework ID: %s", frameworkId.getValue()); 101 | LOG.debug("Executor ID: %s", executorId.getValue()); 102 | 103 | // Enforce using of the IP, when using the hostname this might "flap" between IPs which in turn 104 | // confuses the heck out of Mesos. 105 | final String hostName = NetworkUtil.findPublicIp(); 106 | 107 | LOG.debug("Host name: %s", hostName); 108 | 109 | this.context = new ExecutorDriverContext(hostName, slaveUpid, slaveId, frameworkId, executorId); 110 | 111 | this.eventBus = new ManagedEventBus("executor"); 112 | 113 | this.localMessageProcessor = new LocalExecutorMessageProcessor(context, eventBus); 114 | 115 | // Closer closes in reverse registration order. 116 | 117 | // Close the callback executor last, so that everything that was still scheduled to be delivered to the framework still has a chance. 118 | this.callbackExecutor = closer.register(CloseableExecutors.decorate(Executors.newScheduledThreadPool(5, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("executor-callback-%d").build()))); 119 | 120 | this.receiver = closer.register(new HttpProtocolReceiver(context.getDriverUPID(), ExecutorMessageEnvelope.class, eventBus)); 121 | 122 | // The sender is closed before the receiver, so that possible responses are still caught 123 | this.sender = closer.register(new HttpProtocolSender(context.getDriverUPID())); 124 | 125 | // Make sure that the event bus is drained first at shutdown. 126 | closer.register(eventBus); 127 | } 128 | 129 | @Override 130 | public void close() throws IOException 131 | { 132 | stop(); 133 | } 134 | 135 | private void driverStart() 136 | { 137 | eventBus.register(this); 138 | eventBus.register(localMessageProcessor); 139 | 140 | this.receiver.start(); 141 | } 142 | 143 | // 144 | // ======================================================================== 145 | // 146 | // Mesos ExecutorDriver API 147 | // 148 | // ======================================================================== 149 | // 150 | 151 | @Override 152 | public Status start() 153 | { 154 | if (!context.isStateMachine(DRIVER_NOT_STARTED)) { 155 | return context.getStateMachine(); 156 | } 157 | 158 | try { 159 | driverStart(); 160 | 161 | // 162 | // Register with Mesos Slave 163 | // 164 | final RegisterExecutorMessage message = RegisterExecutorMessage.newBuilder() 165 | .setFrameworkId(context.getFrameworkId()) 166 | .setExecutorId(context.getExecutorId()) 167 | .build(); 168 | 169 | eventBus.post(new RemoteMessageEnvelope(context.getDriverUPID(), context.getSlaveUPID(), message)); 170 | 171 | context.setStateMachine(DRIVER_RUNNING); 172 | } 173 | catch (final Exception e) { 174 | context.setStateMachine(DRIVER_ABORTED); 175 | LOG.error(e, "Failed to create executor process for '%s'", context.getSlaveUPID()); 176 | 177 | eventBus.post(new ExecutorCallback() { 178 | @Override 179 | public Runnable getCallback(final Executor executor, final ExecutorDriver executorDriver) 180 | { 181 | return new Runnable() { 182 | @Override 183 | public void run() 184 | { 185 | final String message = format("Failed to create scheduler process for '%s': %s", context.getSlaveUPID(), e.getMessage()); 186 | LOG.debug("calling error(driver, %s)", message); 187 | executor.error(executorDriver, message); 188 | } 189 | }; 190 | } 191 | }); 192 | } 193 | 194 | return context.getStateMachine(); 195 | } 196 | 197 | @Override 198 | public Status stop() 199 | { 200 | Status status = context.getStateMachine(); 201 | 202 | if (!context.isStateMachine(DRIVER_RUNNING, DRIVER_ABORTED)) { 203 | return status; 204 | } 205 | 206 | try { 207 | closer.close(); 208 | } 209 | catch (final IOException e) { 210 | LOG.warn(e, "While stopping"); 211 | } 212 | 213 | context.setStateMachine(DRIVER_STOPPED); 214 | 215 | // If the driver was aborted, preserve that 216 | // state on the return. 217 | if (status != DRIVER_ABORTED) { 218 | status = DRIVER_STOPPED; 219 | } 220 | 221 | return status; 222 | } 223 | 224 | @Override 225 | public Status abort() 226 | { 227 | if (!context.isStateMachine(DRIVER_RUNNING)) { 228 | return context.getStateMachine(); 229 | } 230 | 231 | context.setStateMachine(DRIVER_ABORTED); 232 | 233 | return context.getStateMachine(); 234 | } 235 | 236 | @Override 237 | @SuppressWarnings("PMD.PreserveStackTrace") 238 | public Status join() 239 | { 240 | if (!context.isStateMachine(DRIVER_RUNNING)) { 241 | return context.getStateMachine(); 242 | } 243 | 244 | final Future statusFuture = context.waitForStateChange(DRIVER_RUNNING); 245 | try { 246 | return statusFuture.get(); 247 | } 248 | catch (final InterruptedException e) { 249 | Thread.currentThread().interrupt(); 250 | } 251 | catch (final ExecutionException e) { 252 | final Throwable t = e.getCause(); 253 | throw Throwables.propagate(t); 254 | } 255 | 256 | return context.getStateMachine(); 257 | } 258 | 259 | @Override 260 | public Status run() 261 | { 262 | start(); 263 | 264 | if (context.isStateMachine(DRIVER_RUNNING)) { 265 | join(); 266 | } 267 | 268 | return context.getStateMachine(); 269 | } 270 | 271 | @Override 272 | public Status sendStatusUpdate(final TaskStatus taskStatus) 273 | { 274 | checkNotNull(taskStatus, "status is null"); 275 | 276 | if (!context.isStateMachine(DRIVER_RUNNING)) { 277 | return context.getStateMachine(); 278 | } 279 | 280 | if (taskStatus.getState() == TASK_STAGING) { 281 | LOG.error("Executor is not allowed to send TASK_STAGING status update. Aborting!"); 282 | 283 | eventBus.post(new ExecutorCallback() { 284 | @Override 285 | public Runnable getCallback(final Executor executor, final ExecutorDriver executorDriver) 286 | { 287 | return new Runnable() { 288 | @Override 289 | public void run() 290 | { 291 | executorDriver.abort(); 292 | 293 | final String message = "Executor is not allowed to send TASK_STAGING status update. Aborting!"; 294 | LOG.debug("calling error(driver, %s)", message); 295 | executor.error(executorDriver, message); 296 | } 297 | }; 298 | } 299 | }); 300 | 301 | return context.getStateMachine(); 302 | } 303 | 304 | final UUID uuid = UUID.randomUUID(); 305 | 306 | final long now = TimeUtil.currentTime(); 307 | final StatusUpdateMessage message = StatusUpdateMessage.newBuilder() 308 | .setPid(context.getDriverUPID().asString()) 309 | .setUpdate(StatusUpdate.newBuilder() 310 | .setFrameworkId(context.getFrameworkId()) 311 | .setExecutorId(context.getExecutorId()) 312 | .setSlaveId(context.getSlaveId()) 313 | .setStatus(TaskStatus.newBuilder(taskStatus).setTimestamp(now)) 314 | .setTimestamp(now) 315 | .setUuid(UUIDUtil.uuidBytes(UUID.randomUUID()))) 316 | .build(); 317 | 318 | context.addUpdate(uuid, message.getUpdate()); 319 | 320 | eventBus.post(new RemoteMessageEnvelope(context.getDriverUPID(), context.getSlaveUPID(), message)); 321 | 322 | return context.getStateMachine(); 323 | } 324 | 325 | @Override 326 | public Status sendFrameworkMessage(final byte[] data) 327 | { 328 | checkNotNull(data, "data is null"); 329 | 330 | if (!context.isStateMachine(DRIVER_RUNNING)) { 331 | return context.getStateMachine(); 332 | } 333 | 334 | final ExecutorToFrameworkMessage message = ExecutorToFrameworkMessage.newBuilder() 335 | .setSlaveId(context.getSlaveId()) 336 | .setFrameworkId(context.getFrameworkId()) 337 | .setExecutorId(context.getExecutorId()) 338 | .setData(ByteString.copyFrom(data)) 339 | .build(); 340 | 341 | eventBus.post(new RemoteMessageEnvelope(context.getDriverUPID(), context.getSlaveUPID(), message)); 342 | 343 | return context.getStateMachine(); 344 | } 345 | 346 | /** 347 | * Remote message delivery. Called by the event bus on an event bus thread to transfer a message 348 | * to another mesos host. This must not called directly to ensure the thread separation between 349 | * caller threads, event bus threads and executor threads. 350 | */ 351 | @Subscribe 352 | public void sendMessage(final RemoteMessageEnvelope envelope) throws Exception 353 | { 354 | final Message message = envelope.getMessage(); 355 | final UPID recipient = envelope.getRecipient(); 356 | 357 | checkState(!recipient.equals(context.getDriverUPID()), "Received a message with local recipient! (%s)", message); 358 | 359 | sender.sendHttpMessage(recipient, message); 360 | } 361 | 362 | // 363 | // Executor callback delivery. 364 | // 365 | 366 | @Subscribe 367 | public void processExecutorCallback(final ExecutorCallback callback) 368 | { 369 | callbackExecutor.submit(callback.getCallback(executor, this)); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/executor/LocalExecutorMessageProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.executor; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | import static com.google.common.base.Preconditions.checkState; 18 | 19 | import static org.apache.mesos.Protos.Status.DRIVER_ABORTED; 20 | 21 | import java.util.concurrent.ConcurrentMap; 22 | 23 | import com.google.common.collect.Maps; 24 | import com.google.common.eventbus.Subscribe; 25 | import com.groupon.mesos.executor.ExecutorMessageEnvelope.ExecutorRegisteredMessageEnvelope; 26 | import com.groupon.mesos.executor.ExecutorMessageEnvelope.ExecutorReregisteredMessageEnvelope; 27 | import com.groupon.mesos.executor.ExecutorMessageEnvelope.FrameworkToExecutorMessageEnvelope; 28 | import com.groupon.mesos.executor.ExecutorMessageEnvelope.KillTaskMessageEnvelope; 29 | import com.groupon.mesos.executor.ExecutorMessageEnvelope.ReconnectExecutorMessageEnvelope; 30 | import com.groupon.mesos.executor.ExecutorMessageEnvelope.RemoteMessageEnvelope; 31 | import com.groupon.mesos.executor.ExecutorMessageEnvelope.RunTaskMessageEnvelope; 32 | import com.groupon.mesos.executor.ExecutorMessageEnvelope.ShutdownExecutorMessageEnvelope; 33 | import com.groupon.mesos.executor.ExecutorMessageEnvelope.StatusUpdateAcknowledgementMessageEnvelope; 34 | import com.groupon.mesos.util.Log; 35 | import com.groupon.mesos.util.ManagedEventBus; 36 | import com.groupon.mesos.util.UUIDUtil; 37 | 38 | import org.apache.mesos.Executor; 39 | import org.apache.mesos.ExecutorDriver; 40 | import org.apache.mesos.Protos.TaskID; 41 | import org.apache.mesos.Protos.TaskInfo; 42 | 43 | import mesos.internal.Messages.ExecutorRegisteredMessage; 44 | import mesos.internal.Messages.ExecutorReregisteredMessage; 45 | import mesos.internal.Messages.FrameworkToExecutorMessage; 46 | import mesos.internal.Messages.KillTaskMessage; 47 | import mesos.internal.Messages.ReconnectExecutorMessage; 48 | import mesos.internal.Messages.ReregisterExecutorMessage; 49 | import mesos.internal.Messages.RunTaskMessage; 50 | import mesos.internal.Messages.StatusUpdateAcknowledgementMessage; 51 | 52 | /** 53 | * Local Message processing. Accepts messages from the outside and deliver them to the local executor. 54 | */ 55 | class LocalExecutorMessageProcessor 56 | { 57 | private static final Log LOG = Log.getLog(LocalExecutorMessageProcessor.class); 58 | 59 | private final ConcurrentMap tasks = Maps.newConcurrentMap(); 60 | 61 | private final ExecutorDriverContext context; 62 | private final ManagedEventBus eventBus; 63 | 64 | LocalExecutorMessageProcessor(final ExecutorDriverContext context, 65 | final ManagedEventBus eventBus) 66 | { 67 | this.context = checkNotNull(context, "context is null"); 68 | this.eventBus = checkNotNull(eventBus, "eventBus is null"); 69 | } 70 | 71 | @Subscribe 72 | public void executorRegistered(final ExecutorRegisteredMessageEnvelope envelope) 73 | { 74 | checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); 75 | 76 | if (context.isStateMachine(DRIVER_ABORTED)) { 77 | LOG.warn("driver is aborted!"); 78 | return; 79 | } 80 | 81 | final ExecutorRegisteredMessage message = envelope.getMessage(); 82 | 83 | eventBus.post(new ExecutorCallback() { 84 | @Override 85 | public Runnable getCallback(final Executor executor, final ExecutorDriver executorDriver) 86 | { 87 | return new Runnable() { 88 | @Override 89 | public void run() 90 | { 91 | executor.registered(executorDriver, message.getExecutorInfo(), message.getFrameworkInfo(), message.getSlaveInfo()); 92 | } 93 | 94 | @Override 95 | public String toString() 96 | { 97 | return "callback for registered()"; 98 | } 99 | }; 100 | } 101 | }); 102 | } 103 | 104 | @Subscribe 105 | public void executorReregistered(final ExecutorReregisteredMessageEnvelope envelope) 106 | { 107 | checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); 108 | 109 | if (context.isStateMachine(DRIVER_ABORTED)) { 110 | LOG.warn("driver is aborted!"); 111 | return; 112 | } 113 | 114 | final ExecutorReregisteredMessage message = envelope.getMessage(); 115 | 116 | eventBus.post(new ExecutorCallback() { 117 | @Override 118 | public Runnable getCallback(final Executor executor, final ExecutorDriver executorDriver) 119 | { 120 | return new Runnable() { 121 | @Override 122 | public void run() 123 | { 124 | executor.reregistered(executorDriver, message.getSlaveInfo()); 125 | } 126 | 127 | @Override 128 | public String toString() 129 | { 130 | return "callback for reregistered()"; 131 | } 132 | }; 133 | } 134 | }); 135 | } 136 | 137 | @Subscribe 138 | public void reconnectExecutor(final ReconnectExecutorMessageEnvelope envelope) 139 | { 140 | checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); 141 | 142 | if (context.isStateMachine(DRIVER_ABORTED)) { 143 | LOG.warn("driver is aborted!"); 144 | return; 145 | } 146 | 147 | final ReconnectExecutorMessage message = envelope.getMessage(); 148 | 149 | checkState(message.getSlaveId().equals(context.getSlaveId()), "Received reconnect from slave %s (expected %s)", message.getSlaveId().getValue(), context.getSlaveId().getValue()); 150 | 151 | final ReregisterExecutorMessage.Builder builder = ReregisterExecutorMessage.newBuilder() 152 | .setExecutorId(context.getExecutorId()) 153 | .setFrameworkId(context.getFrameworkId()) 154 | .addAllUpdates(context.getUpdates()) 155 | .addAllTasks(tasks.values()); 156 | 157 | eventBus.post(new RemoteMessageEnvelope(context.getDriverUPID(), context.getSlaveUPID(), builder.build())); 158 | } 159 | 160 | @Subscribe 161 | public void runTask(final RunTaskMessageEnvelope envelope) 162 | { 163 | checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); 164 | 165 | if (context.isStateMachine(DRIVER_ABORTED)) { 166 | LOG.warn("driver is aborted!"); 167 | return; 168 | } 169 | 170 | final RunTaskMessage message = envelope.getMessage(); 171 | 172 | final TaskInfo task = message.getTask(); 173 | 174 | checkState(!tasks.containsKey(task.getTaskId()), "Task %s already started!", task.getTaskId().getValue()); 175 | 176 | tasks.put(task.getTaskId(), task); 177 | 178 | eventBus.post(new ExecutorCallback() { 179 | @Override 180 | public Runnable getCallback(final Executor executor, final ExecutorDriver executorDriver) 181 | { 182 | return new Runnable() { 183 | @Override 184 | public void run() 185 | { 186 | executor.launchTask(executorDriver, task); 187 | } 188 | 189 | @Override 190 | public String toString() 191 | { 192 | return "callback for launchTask()"; 193 | } 194 | }; 195 | } 196 | }); 197 | } 198 | 199 | @Subscribe 200 | public void killTask(final KillTaskMessageEnvelope envelope) 201 | { 202 | checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); 203 | 204 | if (context.isStateMachine(DRIVER_ABORTED)) { 205 | LOG.warn("driver is aborted!"); 206 | return; 207 | } 208 | 209 | final KillTaskMessage message = envelope.getMessage(); 210 | 211 | eventBus.post(new ExecutorCallback() { 212 | @Override 213 | public Runnable getCallback(final Executor executor, final ExecutorDriver executorDriver) 214 | { 215 | return new Runnable() { 216 | @Override 217 | public void run() 218 | { 219 | executor.killTask(executorDriver, message.getTaskId()); 220 | } 221 | 222 | @Override 223 | public String toString() 224 | { 225 | return "callback for killTask()"; 226 | } 227 | }; 228 | } 229 | }); 230 | } 231 | 232 | @Subscribe 233 | public void statusUpdateAcknowledgement(final StatusUpdateAcknowledgementMessageEnvelope envelope) 234 | { 235 | checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); 236 | 237 | if (context.isStateMachine(DRIVER_ABORTED)) { 238 | LOG.warn("driver is aborted!"); 239 | return; 240 | } 241 | 242 | final StatusUpdateAcknowledgementMessage message = envelope.getMessage(); 243 | 244 | context.removeUpdate(UUIDUtil.bytesUuid(message.getUuid())); 245 | tasks.remove(message.getTaskId()); 246 | } 247 | 248 | @Subscribe 249 | public void frameworkToExecutor(final FrameworkToExecutorMessageEnvelope envelope) 250 | { 251 | checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); 252 | 253 | if (context.isStateMachine(DRIVER_ABORTED)) { 254 | LOG.warn("driver is aborted!"); 255 | return; 256 | } 257 | 258 | final FrameworkToExecutorMessage message = envelope.getMessage(); 259 | 260 | eventBus.post(new ExecutorCallback() { 261 | @Override 262 | public Runnable getCallback(final Executor executor, final ExecutorDriver executorDriver) 263 | { 264 | return new Runnable() { 265 | @Override 266 | public void run() 267 | { 268 | executor.frameworkMessage(executorDriver, message.getData().toByteArray()); 269 | } 270 | 271 | @Override 272 | public String toString() 273 | { 274 | return "callback for frameworkMessage()"; 275 | } 276 | }; 277 | } 278 | }); 279 | } 280 | 281 | @Subscribe 282 | public void shutdownExecutor(final ShutdownExecutorMessageEnvelope envelope) 283 | { 284 | checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); 285 | 286 | if (context.isStateMachine(DRIVER_ABORTED)) { 287 | LOG.warn("driver is aborted!"); 288 | return; 289 | } 290 | 291 | eventBus.post(new ExecutorCallback() { 292 | @Override 293 | public Runnable getCallback(final Executor executor, final ExecutorDriver executorDriver) 294 | { 295 | return new Runnable() { 296 | @Override 297 | public void run() 298 | { 299 | executorDriver.abort(); 300 | executor.shutdown(executorDriver); 301 | } 302 | 303 | @Override 304 | public String toString() 305 | { 306 | return "callback for abort()"; 307 | } 308 | }; 309 | } 310 | }); 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/scheduler/SchedulerCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.scheduler; 15 | 16 | import org.apache.mesos.Scheduler; 17 | import org.apache.mesos.SchedulerDriver; 18 | 19 | /** 20 | * Callbacks into the scheduler, Runnables should be executed with a Threadpool for decoupling. 21 | */ 22 | interface SchedulerCallback 23 | { 24 | Runnable getCallback(Scheduler scheduler, SchedulerDriver schedulerDriver); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/scheduler/SchedulerDriverContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.scheduler; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | 18 | import static org.apache.mesos.Protos.Status.DRIVER_ABORTED; 19 | import static org.apache.mesos.Protos.Status.DRIVER_NOT_STARTED; 20 | 21 | import java.io.IOException; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.UUID; 26 | import java.util.concurrent.BlockingQueue; 27 | import java.util.concurrent.ConcurrentHashMap; 28 | import java.util.concurrent.LinkedBlockingQueue; 29 | import java.util.concurrent.atomic.AtomicBoolean; 30 | import java.util.concurrent.atomic.AtomicReference; 31 | 32 | import com.google.common.collect.HashBasedTable; 33 | import com.google.common.collect.Table; 34 | import com.google.common.net.HostAndPort; 35 | import com.google.common.util.concurrent.ListenableFuture; 36 | import com.google.common.util.concurrent.SettableFuture; 37 | import com.groupon.mesos.util.Log; 38 | import com.groupon.mesos.util.NetworkUtil; 39 | import com.groupon.mesos.util.UPID; 40 | 41 | import org.apache.mesos.Protos; 42 | import org.apache.mesos.Protos.FrameworkID; 43 | import org.apache.mesos.Protos.FrameworkInfo; 44 | import org.apache.mesos.Protos.MasterInfo; 45 | import org.apache.mesos.Protos.OfferID; 46 | import org.apache.mesos.Protos.SlaveID; 47 | import org.apache.mesos.Protos.Status; 48 | 49 | /** 50 | * Glues all the pieces of the scheduler together, keeps track of state etc. This is a too big and random collection of things. 51 | */ 52 | class SchedulerDriverContext 53 | { 54 | private static final Log LOG = Log.getLog(SchedulerDriverContext.class); 55 | 56 | private final AtomicReference stateMachine = new AtomicReference<>(DRIVER_NOT_STARTED); 57 | private final AtomicReference frameworkInfo = new AtomicReference<>(); 58 | private final AtomicReference masterInfo = new AtomicReference<>(); 59 | private final AtomicReference masterUpid = new AtomicReference<>(); 60 | 61 | private final BlockingQueue> stateMachineFutures = new LinkedBlockingQueue<>(); 62 | 63 | private final AtomicBoolean connected = new AtomicBoolean(); 64 | private final AtomicBoolean failover = new AtomicBoolean(false); 65 | private final UPID driverUpid; 66 | 67 | private final Table offerCache = HashBasedTable.create(); 68 | private final Map slaveCache = new ConcurrentHashMap<>(16, 0.75f, 3); 69 | 70 | SchedulerDriverContext(final FrameworkInfo frameworkInfo) 71 | throws IOException 72 | { 73 | this.frameworkInfo.set(checkNotNull(frameworkInfo, "frameworkInfo is null")); 74 | 75 | this.driverUpid = UPID.fromParts(UUID.randomUUID().toString(), 76 | HostAndPort.fromParts(frameworkInfo.getHostname(), NetworkUtil.findUnusedPort())); 77 | 78 | // If the framework info sent in has an id, we are in failover mode from the start. 79 | failover.set(hasFrameworkId(frameworkInfo)); 80 | } 81 | 82 | UPID getDriverUPID() 83 | { 84 | return driverUpid; 85 | } 86 | 87 | // 88 | // connected status 89 | // 90 | 91 | boolean connected() 92 | { 93 | return connected.getAndSet(true); 94 | } 95 | 96 | /** 97 | * Disconnect and return the previous state. 98 | */ 99 | boolean disconnected() 100 | { 101 | return connected.getAndSet(false); 102 | } 103 | 104 | boolean isConnected() 105 | { 106 | return connected.get(); 107 | } 108 | 109 | // 110 | // failover status 111 | // 112 | 113 | void setFailover(final boolean failover) 114 | { 115 | this.failover.set(failover); 116 | } 117 | 118 | boolean isFailover() 119 | { 120 | return failover.get(); 121 | } 122 | 123 | // 124 | // Master information 125 | // 126 | 127 | synchronized MasterInfo getMaster() 128 | { 129 | return masterInfo.get(); 130 | } 131 | 132 | synchronized MasterInfo connectedMaster() 133 | { 134 | if (isStateMachine(DRIVER_ABORTED)) { 135 | LOG.debug("driver is aborted!"); 136 | return null; 137 | } 138 | 139 | if (!isConnected()) { 140 | LOG.debug("Not connected!"); 141 | return null; 142 | } 143 | 144 | return masterInfo.get(); 145 | } 146 | 147 | synchronized void setMaster(final MasterInfo newMasterInfo) 148 | { 149 | masterInfo.set(newMasterInfo); 150 | masterUpid.set(newMasterInfo == null ? null : UPID.create(newMasterInfo.getPid())); 151 | } 152 | 153 | synchronized UPID getMasterUPID() 154 | { 155 | return masterUpid.get(); 156 | } 157 | 158 | void setFrameworkId(final FrameworkID frameworkId) 159 | { 160 | checkNotNull(frameworkId, "frameworkId is null"); 161 | 162 | frameworkInfo.set(FrameworkInfo.newBuilder(frameworkInfo.get()) 163 | .setId(frameworkId) 164 | .build()); 165 | } 166 | 167 | // 168 | // Framework Id 169 | // 170 | 171 | FrameworkID getFrameworkId() 172 | { 173 | return frameworkInfo.get().getId(); 174 | } 175 | 176 | boolean hasFrameworkId() 177 | { 178 | return hasFrameworkId(frameworkInfo.get()); 179 | } 180 | 181 | FrameworkInfo getFrameworkInfo() 182 | { 183 | return frameworkInfo.get(); 184 | } 185 | 186 | // 187 | // State machine management 188 | // 189 | 190 | synchronized void setStateMachine(final Status status) 191 | { 192 | final Status oldStatus = stateMachine.getAndSet(status); 193 | 194 | if (status != oldStatus) { 195 | // Fire all the futures waiting for a status change. 196 | final List> settableFutures = new ArrayList<>(stateMachineFutures.size()); 197 | stateMachineFutures.drainTo(settableFutures); 198 | 199 | for (final SettableFuture future : settableFutures) { 200 | future.set(status); 201 | } 202 | } 203 | } 204 | 205 | synchronized Status getStateMachine() 206 | { 207 | return stateMachine.get(); 208 | } 209 | 210 | synchronized ListenableFuture waitForStateChange(final Status expectedStatus) 211 | { 212 | final SettableFuture future = SettableFuture.create(); 213 | if (!isStateMachine(expectedStatus)) { 214 | // Current status is not the expected status. Return 215 | // it immediately. 216 | future.set(stateMachine.get()); 217 | } 218 | else { 219 | // Current status is expected status: Queue up for a status change. 220 | stateMachineFutures.add(future); 221 | } 222 | return future; 223 | } 224 | 225 | synchronized boolean isStateMachine(final Protos.Status ... statusWanted) 226 | { 227 | final Protos.Status currentState = stateMachine.get(); 228 | for (final Protos.Status status : statusWanted) { 229 | if (currentState == status) { 230 | return true; 231 | } 232 | } 233 | return false; 234 | } 235 | 236 | // 237 | // Offer cache management 238 | // 239 | 240 | void addOffer(final OfferID offerId, final SlaveID slaveId, final UPID pid) 241 | { 242 | synchronized (offerCache) { 243 | offerCache.put(offerId, slaveId, pid); 244 | } 245 | } 246 | 247 | void removeAllOffers(final OfferID offerId) 248 | { 249 | synchronized (offerCache) { 250 | offerCache.row(offerId).clear(); 251 | } 252 | } 253 | 254 | boolean hasOffers(final OfferID offerId) 255 | { 256 | synchronized (offerCache) { 257 | return offerCache.containsRow(offerId); 258 | } 259 | } 260 | 261 | boolean hasOffer(final OfferID offerId, final SlaveID slaveId) 262 | { 263 | synchronized (offerCache) { 264 | return offerCache.contains(offerId, slaveId); 265 | } 266 | } 267 | 268 | UPID getOffer(final OfferID offerId, final SlaveID slaveId) 269 | { 270 | synchronized (offerCache) { 271 | return offerCache.get(offerId, slaveId); 272 | } 273 | } 274 | 275 | // 276 | // Slave cache management 277 | // 278 | 279 | void addSlave(final SlaveID slaveId, final UPID upid) 280 | { 281 | slaveCache.put(slaveId, upid); 282 | } 283 | 284 | void removeSlave(final SlaveID slaveId) 285 | { 286 | slaveCache.remove(slaveId); 287 | } 288 | 289 | boolean containsSlave(final SlaveID slaveId) 290 | { 291 | return slaveCache.containsKey(slaveId); 292 | } 293 | 294 | UPID getSlaveUPID(final SlaveID slaveId) 295 | { 296 | return slaveCache.get(slaveId); 297 | } 298 | 299 | /** 300 | * Static helper for use in the c'tor 301 | */ 302 | private static boolean hasFrameworkId(final FrameworkInfo frameworkInfo) 303 | { 304 | return frameworkInfo.hasId() && !"".equals(frameworkInfo.getId().getValue()); 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/scheduler/SchedulerMessageEnvelope.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.scheduler; 15 | 16 | import com.google.protobuf.Message; 17 | import com.groupon.mesos.util.AbstractMessageEnvelope; 18 | import com.groupon.mesos.util.UPID; 19 | 20 | import mesos.internal.Messages.ExecutorToFrameworkMessage; 21 | import mesos.internal.Messages.FrameworkErrorMessage; 22 | import mesos.internal.Messages.FrameworkRegisteredMessage; 23 | import mesos.internal.Messages.FrameworkReregisteredMessage; 24 | import mesos.internal.Messages.LostSlaveMessage; 25 | import mesos.internal.Messages.RescindResourceOfferMessage; 26 | import mesos.internal.Messages.ResourceOffersMessage; 27 | import mesos.internal.Messages.StatusUpdateMessage; 28 | 29 | /** 30 | * Scheduler related messages. 31 | */ 32 | public abstract class SchedulerMessageEnvelope extends AbstractMessageEnvelope 33 | { 34 | protected SchedulerMessageEnvelope(final UPID sender, final UPID recipient, final T message) 35 | { 36 | super(sender, recipient, message); 37 | } 38 | 39 | /** 40 | * Send a message to the Mesos framework. 41 | */ 42 | public static class RemoteMessageEnvelope extends SchedulerMessageEnvelope 43 | { 44 | public RemoteMessageEnvelope(final UPID sender, final UPID recipient, final Message message) 45 | { 46 | super(sender, recipient, message); 47 | } 48 | } 49 | 50 | /** 51 | * {@link mesos.internal.Messages.FrameworkRegisteredMessage} received from Mesos. 52 | */ 53 | public static class FrameworkRegisteredMessageEnvelope extends SchedulerMessageEnvelope 54 | { 55 | public FrameworkRegisteredMessageEnvelope(final UPID sender, final UPID recipient, final FrameworkRegisteredMessage message) 56 | { 57 | super(sender, recipient, message); 58 | } 59 | } 60 | 61 | /** 62 | * {@link mesos.internal.Messages.FrameworkReregisteredMessage} received from Mesos. 63 | */ 64 | public static class FrameworkReregisteredMessageEnvelope extends SchedulerMessageEnvelope 65 | { 66 | public FrameworkReregisteredMessageEnvelope(final UPID sender, final UPID recipient, final FrameworkReregisteredMessage message) 67 | { 68 | super(sender, recipient, message); 69 | } 70 | } 71 | 72 | /** 73 | * {@link mesos.internal.Messages.ResourceOffersMessage} received from Mesos. 74 | */ 75 | public static class ResourceOffersMessageEnvelope extends SchedulerMessageEnvelope 76 | { 77 | public ResourceOffersMessageEnvelope(final UPID sender, final UPID recipient, final ResourceOffersMessage message) 78 | { 79 | super(sender, recipient, message); 80 | } 81 | } 82 | 83 | /** 84 | * {@link mesos.internal.Messages.FrameworkErrorMessage} received from Mesos. 85 | */ 86 | public static class FrameworkErrorMessageEnvelope extends SchedulerMessageEnvelope 87 | { 88 | public FrameworkErrorMessageEnvelope(final UPID sender, final UPID recipient, final FrameworkErrorMessage message) 89 | { 90 | super(sender, recipient, message); 91 | } 92 | } 93 | 94 | /** 95 | * {@link mesos.internal.Messages.ExecutorToFrameworkMessage} received from Mesos. 96 | */ 97 | public static class ExecutorToFrameworkMessageEnvelope extends SchedulerMessageEnvelope 98 | { 99 | public ExecutorToFrameworkMessageEnvelope(final UPID sender, final UPID recipient, final ExecutorToFrameworkMessage message) 100 | { 101 | super(sender, recipient, message); 102 | } 103 | } 104 | 105 | /** 106 | * {@link mesos.internal.Messages.LostSlaveMessage} received from Mesos. 107 | */ 108 | public static class LostSlaveMessageEnvelope extends SchedulerMessageEnvelope 109 | { 110 | public LostSlaveMessageEnvelope(final UPID sender, final UPID recipient, final LostSlaveMessage message) 111 | { 112 | super(sender, recipient, message); 113 | } 114 | } 115 | 116 | /** 117 | * {@link mesos.internal.Messages.RescindResourceOfferMessage} received from Mesos. 118 | */ 119 | public static class RescindResourceOfferMessageEnvelope extends SchedulerMessageEnvelope 120 | { 121 | public RescindResourceOfferMessageEnvelope(final UPID sender, final UPID recipient, final RescindResourceOfferMessage message) 122 | { 123 | super(sender, recipient, message); 124 | } 125 | } 126 | 127 | /** 128 | * {@link mesos.internal.Messages.StatusUpdateMessage} received from Mesos. 129 | */ 130 | public static class StatusUpdateMessageEnvelope extends SchedulerMessageEnvelope 131 | { 132 | public StatusUpdateMessageEnvelope(final UPID sender, final UPID recipient, final StatusUpdateMessage message) 133 | { 134 | super(sender, recipient, message); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/state/JLevelDBState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.state; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | import static com.google.common.base.Preconditions.checkState; 18 | 19 | import static org.iq80.leveldb.impl.Iq80DBFactory.asString; 20 | import static org.iq80.leveldb.impl.Iq80DBFactory.bytes; 21 | 22 | import java.io.Closeable; 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.util.Iterator; 26 | import java.util.Map; 27 | import java.util.concurrent.Callable; 28 | import java.util.concurrent.ExecutorService; 29 | import java.util.concurrent.Executors; 30 | import java.util.concurrent.Future; 31 | import java.util.concurrent.TimeUnit; 32 | import java.util.concurrent.atomic.AtomicBoolean; 33 | 34 | import com.google.common.collect.AbstractIterator; 35 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 36 | import com.groupon.mesos.util.Log; 37 | 38 | import org.apache.mesos.state.State; 39 | import org.apache.mesos.state.Variable; 40 | import org.iq80.leveldb.DB; 41 | import org.iq80.leveldb.DBIterator; 42 | import org.iq80.leveldb.Options; 43 | import org.iq80.leveldb.WriteBatch; 44 | import org.iq80.leveldb.WriteOptions; 45 | import org.iq80.leveldb.impl.Iq80DBFactory; 46 | 47 | import mesos.internal.state.State.Entry; 48 | 49 | public class JLevelDBState implements State, Closeable 50 | { 51 | private static final Log LOG = Log.getLog(JLevelDBState.class); 52 | 53 | private final DB db; 54 | private final AtomicBoolean closed = new AtomicBoolean(false); 55 | private final ExecutorService executor = Executors.newFixedThreadPool(10, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("JLevelDB-State-%d").build()); 56 | 57 | public JLevelDBState(final String path) 58 | throws IOException 59 | { 60 | checkNotNull(path, "path is null"); 61 | final Options options = new Options(); 62 | options.createIfMissing(true); 63 | this.db = Iq80DBFactory.factory.open(new File(path), options); 64 | } 65 | 66 | @Override 67 | public void close() throws IOException 68 | { 69 | if (!closed.getAndSet(true)) { 70 | executor.shutdown(); 71 | 72 | try { 73 | executor.awaitTermination(1, TimeUnit.DAYS); 74 | } 75 | catch (final InterruptedException e) { 76 | Thread.currentThread().interrupt(); 77 | } 78 | 79 | db.close(); 80 | } 81 | } 82 | 83 | @Override 84 | public Future fetch(final String name) 85 | { 86 | checkNotNull(name, "name is null"); 87 | checkState(!closed.get(), "already closed"); 88 | 89 | return executor.submit(new Callable() { 90 | @Override 91 | public Variable call() throws Exception 92 | { 93 | // Interning the string will make sure that all 94 | // synchronized blocks use the same java object monitor 95 | // for the same string value, which in turn serves 96 | // as poor man's row lock. 97 | final String internedName = name.intern(); 98 | synchronized (internedName) { 99 | final JVariable var = load(name); 100 | if (var == null) { 101 | return new JVariable(name, JVariable.EMPTY_BYTES); 102 | } 103 | else { 104 | return var; 105 | } 106 | } 107 | } 108 | }); 109 | } 110 | 111 | @Override 112 | public Future store(final Variable variable) 113 | { 114 | checkNotNull(variable, "variable is null"); 115 | checkState(!closed.get(), "already closed"); 116 | checkState(variable instanceof JVariable, "can not process native variable, use JVariable"); 117 | 118 | final JVariable v = (JVariable) variable; 119 | 120 | return executor.submit(new Callable() { 121 | @Override 122 | public Variable call() throws Exception 123 | { 124 | final WriteOptions writeOptions = new WriteOptions(); 125 | writeOptions.sync(true); 126 | 127 | final String internedName = v.getName().intern(); 128 | synchronized (internedName) { 129 | final JVariable current = load(internedName); 130 | if (current == null || current.getUuid().equals(v.getUuid())) { 131 | final JVariable update = new JVariable(internedName, v.value()); 132 | final WriteBatch writeBatch = db.createWriteBatch(); 133 | writeBatch.delete(bytes(internedName)); 134 | writeBatch.put(bytes(internedName), update.getEntry().toByteArray()); 135 | db.write(writeBatch, writeOptions); 136 | return update; 137 | } 138 | else { 139 | return null; 140 | } 141 | } 142 | } 143 | }); 144 | } 145 | 146 | @Override 147 | public Future expunge(final Variable variable) 148 | { 149 | checkNotNull(variable, "variable is null"); 150 | checkState(!closed.get(), "already closed"); 151 | checkState(variable instanceof JVariable, "can not process native variable, use JVariable"); 152 | 153 | final JVariable v = (JVariable) variable; 154 | 155 | return executor.submit(new Callable() { 156 | @Override 157 | public Boolean call() throws Exception 158 | { 159 | final WriteOptions writeOptions = new WriteOptions(); 160 | writeOptions.sync(true); 161 | 162 | final String internedName = v.getName().intern(); 163 | synchronized (internedName) { 164 | final JVariable current = load(internedName); 165 | if (current != null && current.getUuid().equals(v.getUuid())) { 166 | db.delete(bytes(internedName)); 167 | return Boolean.TRUE; 168 | } 169 | else { 170 | return Boolean.FALSE; 171 | } 172 | } 173 | } 174 | }); 175 | } 176 | 177 | @Override 178 | public Future> names() 179 | { 180 | checkState(!closed.get(), "already closed"); 181 | 182 | return executor.submit(new Callable>() { 183 | @Override 184 | public Iterator call() throws Exception 185 | { 186 | return new ClosingIterator(db.iterator()); 187 | } 188 | }); 189 | } 190 | 191 | private JVariable load(final String name) throws IOException 192 | { 193 | final byte[] value = db.get(bytes(name)); 194 | 195 | if (value == null) { 196 | return null; 197 | } 198 | else { 199 | final Entry entry = Entry.parseFrom(value); 200 | return new JVariable(entry); 201 | } 202 | } 203 | 204 | private static class ClosingIterator 205 | extends AbstractIterator 206 | implements Iterator, Closeable 207 | { 208 | private final DBIterator dbIterator; 209 | private final AtomicBoolean closed = new AtomicBoolean(); 210 | 211 | private ClosingIterator(final DBIterator dbIterator) 212 | { 213 | this.dbIterator = checkNotNull(dbIterator, "dbIterator is null"); 214 | this.dbIterator.seekToFirst(); 215 | } 216 | 217 | @Override 218 | protected String computeNext() 219 | { 220 | if (!closed.get() && dbIterator.hasNext()) { 221 | final Map.Entry value = dbIterator.next(); 222 | return asString(value.getKey()); 223 | } 224 | else { 225 | if (!closed.getAndSet(true)) { 226 | try { 227 | dbIterator.close(); 228 | } 229 | catch (final IOException ioe) { 230 | LOG.warn(ioe, "while closing iterator"); 231 | } 232 | } 233 | return endOfData(); 234 | } 235 | } 236 | 237 | @Override 238 | public void close() throws IOException 239 | { 240 | if (!closed.getAndSet(true)) { 241 | dbIterator.close(); 242 | } 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/state/JVariable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.state; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | import static com.groupon.mesos.util.UUIDUtil.bytesUuid; 18 | import static com.groupon.mesos.util.UUIDUtil.uuidBytes; 19 | 20 | import java.util.UUID; 21 | 22 | import com.google.protobuf.ByteString; 23 | 24 | import org.apache.mesos.state.Variable; 25 | 26 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 27 | import mesos.internal.state.State.Entry; 28 | 29 | public class JVariable 30 | extends Variable 31 | { 32 | static final byte[] EMPTY_BYTES = new byte[0]; 33 | 34 | private final Entry entry; 35 | 36 | JVariable(final String name, final byte[] value) 37 | { 38 | checkNotNull(name, "name is null"); 39 | checkNotNull(value, "value is null"); 40 | 41 | this.entry = Entry.newBuilder() 42 | .setName(name) 43 | .setValue(ByteString.copyFrom(value)) 44 | .setUuid(uuidBytes(UUID.randomUUID())) 45 | .build(); 46 | } 47 | 48 | JVariable(final Entry entry) 49 | { 50 | this.entry = entry; 51 | } 52 | 53 | @Override 54 | public byte[] value() 55 | { 56 | return entry.getValue().toByteArray(); 57 | } 58 | 59 | String getName() 60 | { 61 | return entry.getName(); 62 | } 63 | 64 | UUID getUuid() 65 | { 66 | return bytesUuid(entry.getUuid()); 67 | } 68 | 69 | Entry getEntry() 70 | { 71 | return entry; 72 | } 73 | 74 | @Override 75 | public Variable mutate(final byte[] value) 76 | { 77 | checkNotNull(value, "value is null"); 78 | 79 | return new JVariable(Entry.newBuilder() 80 | .setName(entry.getName()) 81 | .setValue(ByteString.copyFrom(value)) 82 | .setUuid(entry.getUuid()) 83 | .build()); 84 | } 85 | 86 | @Override 87 | @SuppressFBWarnings("FI_NULLIFY_SUPER") 88 | @SuppressWarnings("PMD.EmptyFinalizer") 89 | protected void finalize() 90 | { 91 | // This method must override Variable.finalize() because 92 | // it would throw a stupid UnsatisfiedLinkError during 93 | // finalizing if the mesos native library is not present 94 | // (which is the whole point of this code). 95 | // 96 | // And then both findbugs and pmd start (rightfully) complaining. 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/state/JZookeeperState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.state; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | import static com.google.common.base.Preconditions.checkState; 18 | import static com.groupon.mesos.state.JVariable.EMPTY_BYTES; 19 | 20 | import java.io.Closeable; 21 | import java.io.IOException; 22 | import java.util.Iterator; 23 | import java.util.List; 24 | import java.util.concurrent.Callable; 25 | import java.util.concurrent.ExecutorService; 26 | import java.util.concurrent.Executors; 27 | import java.util.concurrent.Future; 28 | import java.util.concurrent.TimeUnit; 29 | import java.util.concurrent.atomic.AtomicBoolean; 30 | 31 | import com.google.common.primitives.Ints; 32 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 33 | import com.groupon.mesos.util.Log; 34 | 35 | import org.apache.mesos.state.State; 36 | import org.apache.mesos.state.Variable; 37 | import org.apache.zookeeper.CreateMode; 38 | import org.apache.zookeeper.KeeperException; 39 | import org.apache.zookeeper.KeeperException.BadVersionException; 40 | import org.apache.zookeeper.KeeperException.NoNodeException; 41 | import org.apache.zookeeper.KeeperException.NodeExistsException; 42 | import org.apache.zookeeper.WatchedEvent; 43 | import org.apache.zookeeper.Watcher; 44 | import org.apache.zookeeper.ZooDefs.Ids; 45 | import org.apache.zookeeper.ZooKeeper; 46 | import org.apache.zookeeper.data.Stat; 47 | 48 | import mesos.internal.state.State.Entry; 49 | 50 | public class JZookeeperState implements State, Closeable 51 | { 52 | private static final Log LOG = Log.getLog(JZookeeperState.class); 53 | 54 | private final AtomicBoolean closed = new AtomicBoolean(); 55 | private final ExecutorService executor = Executors.newFixedThreadPool(10, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("JZookeeper-State-%d").build()); 56 | 57 | private final ZooKeeper client; 58 | private final String path; 59 | 60 | public JZookeeperState(final String servers, 61 | final long timeout, 62 | final TimeUnit unit, 63 | final String znode) throws IOException 64 | { 65 | this(servers, timeout, unit, znode, null, null); 66 | } 67 | 68 | public JZookeeperState(final String servers, 69 | final long timeout, 70 | final TimeUnit unit, 71 | final String znode, 72 | final String scheme, 73 | final byte[] credentials) throws IOException 74 | { 75 | checkNotNull(servers, "servers is null"); 76 | checkNotNull(unit, "unit is null"); 77 | checkNotNull(znode, "znode is null"); 78 | 79 | checkState(scheme == null && credentials == null, "Authentication is currently not supported!"); 80 | 81 | this.client = new ZooKeeper(servers, Ints.checkedCast(unit.toMillis(timeout)), new StateWatcher()); 82 | 83 | String path = znode.startsWith("/") ? znode : "/" + znode; 84 | path = path.endsWith("/") ? path.substring(0, path.length() - 1) : path; 85 | 86 | this.path = path; 87 | 88 | try { 89 | if (client.exists(path, false) == null) { 90 | LOG.debug("Creating Zookeeper path: %s", path); 91 | try { 92 | client.create(path, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 93 | } 94 | catch (final NodeExistsException e) { 95 | LOG.debug("Node %s already exists", path); 96 | } 97 | } 98 | } 99 | catch (final KeeperException e) { 100 | LOG.warn(e, "While creating path %s", path); 101 | } 102 | catch (final InterruptedException e) { 103 | Thread.currentThread().interrupt(); 104 | } 105 | } 106 | 107 | @Override 108 | public void close() throws IOException 109 | { 110 | if (!closed.getAndSet(true)) { 111 | executor.shutdown(); 112 | 113 | try { 114 | executor.awaitTermination(1, TimeUnit.DAYS); 115 | } 116 | catch (final InterruptedException e) { 117 | Thread.currentThread().interrupt(); 118 | } 119 | 120 | try { 121 | client.close(); 122 | } 123 | catch (final InterruptedException e) { 124 | Thread.currentThread().interrupt(); 125 | } 126 | } 127 | } 128 | 129 | @Override 130 | public Future fetch(final String name) 131 | { 132 | checkNotNull(name, "name is null"); 133 | checkState(!closed.get(), "already closed"); 134 | 135 | return executor.submit(new Callable() { 136 | @Override 137 | public Variable call() throws Exception 138 | { 139 | final ZookeeperVariable var = load(getFullPath(name)); 140 | if (var == null) { 141 | return new ZookeeperVariable(name, EMPTY_BYTES); 142 | } 143 | else { 144 | return var; 145 | } 146 | } 147 | }); 148 | } 149 | 150 | @Override 151 | public Future store(final Variable variable) 152 | { 153 | checkNotNull(variable, "variable is null"); 154 | checkState(!closed.get(), "already closed"); 155 | checkState(variable instanceof ZookeeperVariable, "can not process native variable, use ZookeeperVariable"); 156 | 157 | final ZookeeperVariable v = (ZookeeperVariable) variable; 158 | 159 | checkState(v.asBytes().length < 1_048_576, "Entry size exceeds 1 MB"); 160 | 161 | final String fullName = getFullPath(v.getName()); 162 | 163 | return executor.submit(new Callable() { 164 | @Override 165 | public Variable call() throws Exception 166 | { 167 | final ZookeeperVariable update = new ZookeeperVariable(v.getName(), v.value()); 168 | 169 | ZookeeperVariable current = load(fullName); 170 | 171 | while (true) { 172 | // Node does not exist. Create it. 173 | if (current == null) { 174 | LOG.debug("Node %s does not exist", fullName); 175 | try { 176 | client.create(fullName, update.asBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 177 | LOG.debug("Node %s successfully created", fullName); 178 | return update; 179 | } 180 | catch (final NodeExistsException e) { 181 | // Someone beat us to creating the node. Then load it. 182 | LOG.debug("Lost Node %s race, reloading", fullName); 183 | current = load(fullName); 184 | } 185 | } 186 | 187 | if (current != null) { 188 | 189 | if (!current.getUuid().equals(v.getUuid())) { 190 | return null; 191 | } 192 | 193 | checkState(current.getZookeeperVersion() != null, "store with unknown zookeeper version (%s)", current.getEntry()); 194 | 195 | try { 196 | client.setData(fullName, update.asBytes(), current.getZookeeperVersion()); 197 | return update; 198 | } 199 | catch (BadVersionException | NoNodeException e) { 200 | // Version has changed under us or the node has disappeared. Retry (which will probably fail unless it was deleted). 201 | LOG.debug("Could not change version %d, retry writing", current.getZookeeperVersion()); 202 | } 203 | } 204 | 205 | // Current could be null here if the node was deleted while we were not looking. 206 | current = load(fullName); 207 | } 208 | } 209 | }); 210 | } 211 | 212 | @Override 213 | public Future expunge(final Variable variable) 214 | { 215 | checkNotNull(variable, "variable is null"); 216 | checkState(!closed.get(), "already closed"); 217 | checkState(variable instanceof ZookeeperVariable, "can not process native variable, use ZookeeperVariable"); 218 | 219 | final ZookeeperVariable v = (ZookeeperVariable) variable; 220 | final String fullName = getFullPath(v.getName()); 221 | 222 | return executor.submit(new Callable() { 223 | @Override 224 | public Boolean call() throws Exception 225 | { 226 | ZookeeperVariable current = load(fullName); 227 | 228 | while (true) { 229 | if (current == null) { 230 | return false; 231 | } 232 | 233 | if (!current.getUuid().equals(v.getUuid())) { 234 | return false; 235 | } 236 | 237 | checkState(current.getZookeeperVersion() != null, "expunge with unknown zookeeper version (%s)", current.getEntry()); 238 | 239 | try { 240 | client.delete(fullName, current.getZookeeperVersion()); 241 | return true; 242 | } 243 | catch (BadVersionException | NoNodeException e) { 244 | // Version has changed under us or the node has disappeared. Retry (which will probably fail unless it was deleted). 245 | LOG.debug("Could not change version %d, retry expunging", current.getZookeeperVersion()); 246 | } 247 | 248 | // Current could be null here if the node was deleted while we were not looking. 249 | current = load(fullName); 250 | } 251 | } 252 | }); 253 | } 254 | 255 | @Override 256 | public Future> names() 257 | { 258 | checkState(!closed.get(), "already closed"); 259 | 260 | return executor.submit(new Callable>() { 261 | @Override 262 | public Iterator call() throws Exception 263 | { 264 | // Not very memory efficient if there is a large number 265 | // of children. Ah, well. It is good enough. 266 | final List children = client.getChildren(path, false); 267 | return children.iterator(); 268 | } 269 | }); 270 | } 271 | 272 | private ZookeeperVariable load(final String name) throws KeeperException, IOException 273 | { 274 | final Stat stat = new Stat(); 275 | try { 276 | final Entry entry = Entry.parseFrom(client.getData(name, false, stat)); 277 | return new ZookeeperVariable(entry, stat.getVersion()); 278 | } 279 | catch (final NoNodeException e) { 280 | return null; 281 | } 282 | catch (final InterruptedException e) { 283 | Thread.currentThread().interrupt(); 284 | return null; 285 | } 286 | } 287 | 288 | private String getFullPath(final String name) 289 | { 290 | return String.format("%s/%s", path, name); 291 | } 292 | 293 | private static class StateWatcher implements Watcher 294 | { 295 | 296 | @Override 297 | public void process(final WatchedEvent event) 298 | { 299 | LOG.info("Received watched event %s", event); 300 | } 301 | } 302 | 303 | } 304 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/state/ZookeeperVariable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.state; 15 | 16 | import com.google.protobuf.ByteString; 17 | 18 | import org.apache.mesos.state.Variable; 19 | 20 | import mesos.internal.state.State.Entry; 21 | 22 | public class ZookeeperVariable 23 | extends JVariable 24 | { 25 | private final Integer zookeeperVersion; 26 | 27 | ZookeeperVariable(final String name, final byte[] value) 28 | { 29 | this(name, value, null); 30 | } 31 | 32 | public ZookeeperVariable(final String name, final byte[] value, final Integer zookeeperVersion) 33 | { 34 | super(name, value); 35 | this.zookeeperVersion = zookeeperVersion; 36 | } 37 | 38 | ZookeeperVariable(final Entry entry, final Integer zookeeperVersion) 39 | { 40 | super(entry); 41 | this.zookeeperVersion = zookeeperVersion; 42 | } 43 | 44 | Integer getZookeeperVersion() 45 | { 46 | return zookeeperVersion; 47 | } 48 | 49 | byte[] asBytes() 50 | { 51 | return getEntry().toByteArray(); 52 | } 53 | 54 | @Override 55 | public Variable mutate(final byte[] value) 56 | { 57 | return new ZookeeperVariable(Entry.newBuilder() 58 | .setName(getEntry().getName()) 59 | .setValue(ByteString.copyFrom(value)) 60 | .setUuid(getEntry().getUuid()) 61 | .build(), zookeeperVersion); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/util/AbstractMessageEnvelope.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | 18 | import com.google.common.base.Objects; 19 | import com.google.protobuf.Message; 20 | 21 | /** 22 | * Base class for all the Mesos specific messages that move across the event bus. They are all 23 | * shaped the same way (sender, receiver, protobuf message) so subclassing is used to have the 24 | * event bus dispatch to the correct receivers. 25 | */ 26 | public abstract class AbstractMessageEnvelope 27 | { 28 | private final UPID sender; 29 | private final UPID recipient; 30 | private final T message; 31 | 32 | protected AbstractMessageEnvelope(final UPID sender, final UPID recipient, final T message) 33 | { 34 | this.sender = checkNotNull(sender, "sender is null"); 35 | this.recipient = checkNotNull(recipient, "recipient is null"); 36 | this.message = checkNotNull(message, "message is null"); 37 | } 38 | 39 | public UPID getSender() 40 | { 41 | return sender; 42 | } 43 | 44 | public UPID getRecipient() 45 | { 46 | return recipient; 47 | } 48 | 49 | public T getMessage() 50 | { 51 | return message; 52 | } 53 | 54 | @Override 55 | public String toString() 56 | { 57 | return Objects.toStringHelper(this.getClass()) 58 | .add("sender", sender) 59 | .add("recipient", recipient) 60 | .add("message", message) 61 | .toString(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/util/CloseableExecutors.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | 18 | import java.io.Closeable; 19 | import java.util.List; 20 | import java.util.concurrent.AbstractExecutorService; 21 | import java.util.concurrent.Callable; 22 | import java.util.concurrent.ExecutorService; 23 | import java.util.concurrent.ScheduledExecutorService; 24 | import java.util.concurrent.ScheduledFuture; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | /** 28 | * Decorates {@link ExecutorService} and {@link ScheduledExecutorService} with Closeable 29 | * to allow throwing them into a {@link com.google.common.io.Closer}. 30 | */ 31 | public final class CloseableExecutors 32 | { 33 | private CloseableExecutors() 34 | { 35 | throw new AssertionError("do not instantiate"); 36 | } 37 | 38 | public static CloseableExecutorServiceDecorator decorate(final ExecutorService service) 39 | { 40 | return new CloseableExecutorServiceDecorator(service); 41 | } 42 | 43 | public static CloseableScheduledExecutorServiceDecorator decorate(final ScheduledExecutorService service) 44 | { 45 | return new CloseableScheduledExecutorServiceDecorator(service); 46 | } 47 | 48 | private static class CloseableExecutorServiceDecorator 49 | extends AbstractExecutorService 50 | implements Closeable 51 | { 52 | private final ExecutorService delegate; 53 | 54 | CloseableExecutorServiceDecorator(final ExecutorService delegate) 55 | { 56 | this.delegate = checkNotNull(delegate, "delegate is null"); 57 | } 58 | 59 | @Override 60 | public void close() 61 | { 62 | delegate.shutdown(); 63 | } 64 | 65 | @Override 66 | public boolean awaitTermination(final long timeout, final TimeUnit unit) 67 | throws InterruptedException 68 | { 69 | return delegate.awaitTermination(timeout, unit); 70 | } 71 | 72 | @Override 73 | public boolean isShutdown() 74 | { 75 | return delegate.isShutdown(); 76 | } 77 | 78 | @Override 79 | public boolean isTerminated() 80 | { 81 | return delegate.isTerminated(); 82 | } 83 | 84 | @Override 85 | public void shutdown() 86 | { 87 | delegate.shutdown(); 88 | } 89 | 90 | @Override 91 | public List shutdownNow() 92 | { 93 | return delegate.shutdownNow(); 94 | } 95 | 96 | @Override 97 | public void execute(final Runnable command) 98 | { 99 | delegate.execute(command); 100 | } 101 | } 102 | 103 | private static class CloseableScheduledExecutorServiceDecorator 104 | extends CloseableExecutorServiceDecorator 105 | implements ScheduledExecutorService 106 | { 107 | private final ScheduledExecutorService delegate; 108 | 109 | CloseableScheduledExecutorServiceDecorator(final ScheduledExecutorService delegate) 110 | { 111 | super(delegate); 112 | this.delegate = delegate; 113 | } 114 | 115 | @Override 116 | public void close() 117 | { 118 | delegate.shutdown(); 119 | } 120 | 121 | @Override 122 | public ScheduledFuture schedule(final Runnable command, final long delay, final TimeUnit unit) 123 | { 124 | return delegate.schedule(command, delay, unit); 125 | } 126 | 127 | @Override 128 | public ScheduledFuture schedule(final Callable callable, final long delay, final TimeUnit unit) 129 | { 130 | return delegate.schedule(callable, delay, unit); 131 | } 132 | 133 | @Override 134 | public ScheduledFuture scheduleAtFixedRate(final Runnable command, final long initialDelay, final long period, final TimeUnit unit) 135 | { 136 | return delegate.scheduleAtFixedRate(command, initialDelay, period, unit); 137 | } 138 | 139 | @Override 140 | public ScheduledFuture scheduleWithFixedDelay(final Runnable command, final long initialDelay, final long delay, final TimeUnit unit) 141 | { 142 | return delegate.scheduleWithFixedDelay(command, initialDelay, delay, unit); 143 | } 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/util/HttpProtocolReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | /* 15 | * Licensed under the7 Apache License, Version 2.0 (the "License"); 16 | * you may not use this file except in compliance with the License. 17 | * You may obtain a copy of the License at 18 | * 19 | * http://www.apache.org/licenses/LICENSE-2.0 20 | * 21 | * Unless required by applicable law or agreed to in writing, software 22 | * distributed under the License is distributed on an "AS IS" BASIS, 23 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | * See the License for the specific language governing permissions and 25 | * limitations under the License. 26 | */ 27 | package com.groupon.mesos.util; 28 | 29 | import static com.google.common.base.Preconditions.checkState; 30 | 31 | import java.io.Closeable; 32 | import java.io.IOException; 33 | import java.io.InputStream; 34 | import java.lang.reflect.Constructor; 35 | import java.lang.reflect.Method; 36 | import java.util.Set; 37 | import java.util.concurrent.ConcurrentMap; 38 | 39 | import com.google.common.collect.Maps; 40 | import com.google.common.collect.Sets; 41 | import com.google.protobuf.ExtensionRegistryLite; 42 | 43 | import io.undertow.Undertow; 44 | import io.undertow.server.HttpHandler; 45 | import io.undertow.server.HttpServerExchange; 46 | import io.undertow.server.handlers.BlockingHandler; 47 | import io.undertow.server.handlers.CanonicalPathHandler; 48 | import io.undertow.server.handlers.GracefulShutdownHandler; 49 | import io.undertow.server.handlers.PathHandler; 50 | import mesos.internal.Messages; 51 | 52 | /** 53 | * Receives messages from the Mesos cluster. A message is a Protobuf formatted byte stream sent over http. Every 54 | * message is unconditionally acknowledged with 202. The user agent contains the pid (id, host and port) that identifies 55 | * the sender. 56 | */ 57 | public class HttpProtocolReceiver 58 | implements HttpHandler, Closeable 59 | { 60 | private static final Log LOG = Log.getLog(HttpProtocolReceiver.class); 61 | 62 | private final Undertow httpServer; 63 | private final GracefulShutdownHandler shutdownHandler; 64 | 65 | private final ManagedEventBus eventBus; 66 | private final UPID localAddress; 67 | 68 | private final Class messageBaseClass; 69 | 70 | private final Set typesSeen = Sets.newConcurrentHashSet(); 71 | private final ConcurrentMap parseMethodMap = Maps.newConcurrentMap(); 72 | private final ConcurrentMap> constructorMap = Maps.newConcurrentMap(); 73 | 74 | private final ExtensionRegistryLite extensionRegistry = ProtobufRegistry.INSTANCE.getExtensionRegistry(); 75 | 76 | public HttpProtocolReceiver(final UPID localAddress, 77 | final Class messageBaseClass, 78 | final ManagedEventBus eventBus) 79 | { 80 | this.localAddress = localAddress; 81 | this.messageBaseClass = messageBaseClass; 82 | this.eventBus = eventBus; 83 | 84 | final PathHandler pathHandler = new PathHandler(); 85 | pathHandler.addPrefixPath(localAddress.getId(), new CanonicalPathHandler(new BlockingHandler(this))); 86 | 87 | this.shutdownHandler = new GracefulShutdownHandler(pathHandler); 88 | 89 | this.httpServer = Undertow.builder() 90 | .setIoThreads(2) 91 | .setWorkerThreads(16) 92 | .addHttpListener(localAddress.getPort(), localAddress.getHost()) 93 | .setHandler(shutdownHandler) 94 | .build(); 95 | } 96 | 97 | @Override 98 | public void close() 99 | throws IOException 100 | { 101 | shutdownHandler.shutdown(); 102 | try { 103 | shutdownHandler.awaitShutdown(); 104 | } 105 | catch (final InterruptedException e) { 106 | Thread.currentThread().interrupt(); 107 | } 108 | 109 | httpServer.stop(); 110 | } 111 | 112 | public void start() 113 | { 114 | httpServer.start(); 115 | } 116 | 117 | @Override 118 | public void handleRequest(final HttpServerExchange exchange) throws Exception 119 | { 120 | final UPID sender; 121 | final String libprocessFrom = exchange.getRequestHeaders().getFirst("Libprocess-From"); 122 | 123 | if (libprocessFrom != null) { 124 | sender = UPID.create(libprocessFrom); 125 | } 126 | else { 127 | final String userAgent = exchange.getRequestHeaders().getFirst("User-Agent"); 128 | 129 | checkState(userAgent != null && userAgent.startsWith("libprocess/"), "No User-Agent or Libprocess-From header found! Not a valid message!"); 130 | sender = UPID.create(userAgent.substring(11)); 131 | } 132 | 133 | final int dotIndex = exchange.getRelativePath().lastIndexOf('.'); 134 | final String name = exchange.getRelativePath().substring(dotIndex + 1); 135 | exchange.setResponseCode(202); 136 | 137 | // This is where it gets ugly. Protobuf and dynamic messages don't really mesh. 138 | final Method parseFromMethod; 139 | final Constructor envelopeConstructor; 140 | 141 | if (typesSeen.contains(name)) { 142 | if (!parseMethodMap.containsKey(name)) { 143 | LOG.warn("Unparseable message type %s", name); 144 | return; 145 | } 146 | else { 147 | parseFromMethod = parseMethodMap.get(name); 148 | envelopeConstructor = constructorMap.get(name); 149 | } 150 | } 151 | else { 152 | try { 153 | final Class clazz = Class.forName(Messages.class.getName() + "$" + name); 154 | parseFromMethod = clazz.getMethod("parseFrom", InputStream.class, ExtensionRegistryLite.class); 155 | // This implies that for all messages delivered, an Envelope class exists, which is an inner 156 | // class of the messageBaseClass and ends with 'Envelope'. 157 | final Class envelopeClazz = Class.forName(messageBaseClass.getName() + "$" + name + "Envelope"); 158 | envelopeConstructor = envelopeClazz.getConstructor(UPID.class, UPID.class, clazz); 159 | 160 | parseMethodMap.put(name, parseFromMethod); 161 | constructorMap.put(name, envelopeConstructor); 162 | } 163 | catch (ReflectiveOperationException | SecurityException e) { 164 | LOG.warn(e, "While constructing objects for message type %s", name); 165 | return; 166 | } 167 | finally { 168 | typesSeen.add(name); 169 | } 170 | } 171 | 172 | try { 173 | final Object o = parseFromMethod.invoke(null, exchange.getInputStream(), extensionRegistry); 174 | // Local delivery of the message. 175 | eventBus.post(envelopeConstructor.newInstance(sender, localAddress, o)); 176 | LOG.debug("Received from %s: %s", sender.asString(), o); 177 | } 178 | catch (final ReflectiveOperationException e) { 179 | LOG.warn(e, "Can not decode message type %s", name); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/util/HttpProtocolSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | import static java.lang.String.format; 17 | 18 | import static com.google.common.base.Preconditions.checkArgument; 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | import static com.google.common.base.Preconditions.checkState; 21 | 22 | import java.io.Closeable; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.util.Map; 26 | import java.util.UUID; 27 | import java.util.concurrent.ConcurrentHashMap; 28 | import java.util.concurrent.ExecutionException; 29 | import java.util.concurrent.atomic.AtomicBoolean; 30 | 31 | import com.google.common.collect.ImmutableList; 32 | import com.google.common.util.concurrent.Futures; 33 | import com.google.common.util.concurrent.SettableFuture; 34 | import com.google.protobuf.Message; 35 | import com.squareup.okhttp.Callback; 36 | import com.squareup.okhttp.MediaType; 37 | import com.squareup.okhttp.OkHttpClient; 38 | import com.squareup.okhttp.Protocol; 39 | import com.squareup.okhttp.Request; 40 | import com.squareup.okhttp.RequestBody; 41 | import com.squareup.okhttp.Response; 42 | 43 | /** 44 | * Sends out messages to the mesos master and slaves. A message is sent as a HTTP post with a 45 | * protobuf body. The Mesos 0.19+ framework will acknowledge messages that have no User-Agent: libprocess 46 | * header set with 202 (ACCEPTED). The message must contain a custom (Libprocess-From) header as the 47 | * sender. 48 | */ 49 | public class HttpProtocolSender 50 | implements Callback, Closeable 51 | { 52 | private static final Log LOG = Log.getLog(HttpProtocolSender.class); 53 | 54 | private static final MediaType PROTOBUF_MEDIATYPE = MediaType.parse("application/x-protobuf"); 55 | 56 | private final AtomicBoolean closed = new AtomicBoolean(false); 57 | 58 | private final OkHttpClient client; 59 | private final String sender; 60 | private final Map> inFlight = new ConcurrentHashMap<>(16, 0.75f, 2); 61 | 62 | public HttpProtocolSender(final UPID sender) 63 | { 64 | this.client = new OkHttpClient(); 65 | client.setProtocols(ImmutableList.of(Protocol.HTTP_1_1)); 66 | 67 | this.sender = sender.asString(); 68 | } 69 | 70 | @Override 71 | public void close() throws IOException 72 | { 73 | if (!closed.getAndSet(true)) { 74 | // Drain all outstanding requests. 75 | while (!inFlight.isEmpty()) { 76 | try { 77 | // Wait for all outstanding futures to complete 78 | Futures.allAsList(inFlight.values()).get(); 79 | } 80 | catch (final ExecutionException e) { 81 | LOG.warn(e.getCause(), "While waiting for in flight requests to drain"); 82 | } 83 | catch (final InterruptedException e) { 84 | Thread.currentThread().interrupt(); 85 | return; 86 | } 87 | } 88 | } 89 | } 90 | 91 | public void sendHttpMessage(final UPID recipient, final Message message) throws IOException 92 | { 93 | if (closed.get()) { 94 | return; 95 | } 96 | 97 | checkNotNull(recipient, "recipient is null"); 98 | checkNotNull(message, "message is null"); 99 | checkArgument(recipient.getHost() != null, "%s is not a valid recipient for %s", recipient, message); 100 | checkArgument(recipient.getPort() > 0, "%s is not a valid recipient for %s", recipient, message); 101 | final String path = format("/%s/%s", recipient.getId(), message.getDescriptorForType().getFullName()); 102 | final URL url = new URL("http", recipient.getHost(), recipient.getPort(), path); 103 | 104 | final UUID tag = UUID.randomUUID(); 105 | inFlight.put(tag, SettableFuture.create()); 106 | 107 | final RequestBody body = RequestBody.create(PROTOBUF_MEDIATYPE, message.toByteArray()); 108 | final Request request = new Request.Builder() 109 | .header("Libprocess-From", sender) 110 | .url(url) 111 | .post(body) 112 | .tag(tag) 113 | .build(); 114 | 115 | LOG.debug("Sending from %s to URL %s: %s", sender, url, message); 116 | client.newCall(request).enqueue(this); 117 | } 118 | 119 | @Override 120 | public void onFailure(final Request request, final IOException e) 121 | { 122 | final Object tag = request.tag(); 123 | checkState(tag != null, "saw a request with null tag"); 124 | 125 | final SettableFuture future = inFlight.remove(tag); 126 | checkState(future != null, "Saw tag %s but not in in flight map", tag); 127 | future.setException(e); 128 | 129 | LOG.warn("While running %s %s: %s", request.method(), request.urlString(), e.getMessage()); 130 | } 131 | 132 | @Override 133 | public void onResponse(final Response response) throws IOException 134 | { 135 | final Object tag = response.request().tag(); 136 | checkState(tag != null, "saw a request with null tag"); 137 | 138 | final SettableFuture future = inFlight.remove(tag); 139 | checkState(future != null, "Saw tag %s but not in in flight map", tag); 140 | future.set(null); 141 | 142 | LOG.debug("Response %s %s: %d", response.request().method(), response.request().urlString(), response.code()); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/util/Log.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | import static java.lang.String.format; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | /** 24 | * Tiny weeny wrapper around slf4j to alleviate the worst of the pain. 25 | */ 26 | public final class Log 27 | { 28 | public static Log getLog(final String categoryName) 29 | { 30 | return new Log(categoryName); 31 | } 32 | 33 | public static Log getLog(final Class clazz) 34 | { 35 | checkNotNull(clazz, "clazz is null"); 36 | return new Log(clazz.getName()); 37 | } 38 | 39 | private final Logger logger; 40 | 41 | private Log(final String categoryName) 42 | { 43 | this.logger = LoggerFactory.getLogger(categoryName); 44 | } 45 | 46 | public void debug(final String formatString, final Object ... values) 47 | { 48 | checkNotNull(formatString, "format is null"); 49 | if (values.length == 0) { 50 | logger.debug(formatString); 51 | } 52 | else { 53 | logger.debug(format(formatString, values)); 54 | } 55 | } 56 | 57 | public void info(final String formatString, final Object ... values) 58 | { 59 | checkNotNull(formatString, "format is null"); 60 | if (values.length == 0) { 61 | logger.info(formatString); 62 | } 63 | else { 64 | logger.info(format(formatString, values)); 65 | } 66 | } 67 | 68 | public void warn(final String formatString, final Object ... values) 69 | { 70 | checkNotNull(formatString, "format is null"); 71 | if (values.length == 0) { 72 | logger.warn(formatString); 73 | } 74 | else { 75 | logger.warn(format(formatString, values)); 76 | } 77 | } 78 | 79 | public void warn(final Throwable t, final String formatString, final Object ... values) 80 | { 81 | checkNotNull(formatString, "format is null"); 82 | if (values.length == 0) { 83 | logger.warn(formatString, t); 84 | } 85 | else { 86 | logger.warn(format(formatString, values), t); 87 | } 88 | } 89 | 90 | public void error(final String formatString, final Object ... values) 91 | { 92 | checkNotNull(formatString, "format is null"); 93 | if (values.length == 0) { 94 | logger.error(formatString); 95 | } 96 | else { 97 | logger.error(format(formatString, values)); 98 | } 99 | } 100 | 101 | public void error(final Throwable t, final String formatString, final Object ... values) 102 | { 103 | checkNotNull(formatString, "format is null"); 104 | if (values.length == 0) { 105 | logger.error(formatString, t); 106 | } 107 | else { 108 | logger.error(format(formatString, values), t); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/util/ManagedEventBus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | import static com.google.common.base.Preconditions.checkState; 18 | 19 | import java.io.Closeable; 20 | import java.io.IOException; 21 | import java.util.concurrent.ExecutionException; 22 | import java.util.concurrent.ExecutorService; 23 | import java.util.concurrent.Executors; 24 | import java.util.concurrent.TimeUnit; 25 | import java.util.concurrent.TimeoutException; 26 | import java.util.concurrent.atomic.AtomicBoolean; 27 | import java.util.concurrent.atomic.AtomicReference; 28 | 29 | import com.google.common.eventbus.AsyncEventBus; 30 | import com.google.common.eventbus.Subscribe; 31 | import com.google.common.eventbus.SubscriberExceptionContext; 32 | import com.google.common.eventbus.SubscriberExceptionHandler; 33 | import com.google.common.util.concurrent.SettableFuture; 34 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 35 | 36 | /** 37 | * As the event bus does not allow controlled shutdown, add the ability to "poison" the event bus and 38 | * wait for the pill to pass through it. It is assumed that the pill is the last event that the bus 39 | * processes and therefore when it is received, no additional events can be processed. 40 | */ 41 | public class ManagedEventBus implements Closeable 42 | { 43 | private final AsyncEventBus eventBus; 44 | private final AtomicBoolean finished = new AtomicBoolean(false); 45 | private final AtomicReference pillHolder = new AtomicReference<>(new PoisonPill()); 46 | 47 | private final ExecutorService executor; 48 | 49 | public ManagedEventBus(final String name) 50 | { 51 | checkNotNull(name, "name is null"); 52 | this.executor = Executors.newScheduledThreadPool(10, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("eventbus-" + name + "-%d").build()); 53 | this.eventBus = new AsyncEventBus(executor, new EventBusExceptionHandler(name)); 54 | } 55 | 56 | public void register(final Object listener) 57 | { 58 | checkState(!finished.get(), "event bus is shut down"); 59 | eventBus.register(listener); 60 | } 61 | 62 | public void post(final Object event) 63 | { 64 | checkState(!finished.get(), "event bus is shut down"); 65 | eventBus.post(event); 66 | } 67 | 68 | @Override 69 | public void close() throws IOException 70 | { 71 | if (!finished.getAndSet(true)) { 72 | eventBus.register(this); 73 | 74 | final PoisonPill pill = pillHolder.getAndSet(null); 75 | if (pill != null) { 76 | eventBus.post(pill); 77 | try { 78 | pill.awaitTermination(1, TimeUnit.DAYS); 79 | 80 | // The poison pill made it through the event bus, so 81 | // all events that were present before are either delivered 82 | // or in flight. Shut down the executor now. 83 | executor.shutdown(); 84 | executor.awaitTermination(1, TimeUnit.SECONDS); 85 | } 86 | catch (final InterruptedException e) { 87 | Thread.currentThread().interrupt(); 88 | return; 89 | } 90 | } 91 | } 92 | } 93 | 94 | @Subscribe 95 | public void receivePoisonPill(final PoisonPill poisonPill) 96 | { 97 | poisonPill.trigger(); 98 | } 99 | 100 | public static class PoisonPill 101 | { 102 | private final SettableFuture future = SettableFuture.create(); 103 | 104 | public void trigger() 105 | { 106 | future.set(null); 107 | } 108 | 109 | public void awaitTermination(final long timeout, final TimeUnit unit) 110 | throws InterruptedException 111 | { 112 | try { 113 | future.get(timeout, unit); 114 | } 115 | catch (TimeoutException | ExecutionException e) { 116 | return; // do nothing. 117 | } 118 | } 119 | } 120 | 121 | /** 122 | * Simple exception handler that, unlike the default handler, does not swallow 123 | * the exception causing the error. 124 | */ 125 | public static class EventBusExceptionHandler implements SubscriberExceptionHandler 126 | { 127 | public static final Log LOG = Log.getLog(EventBusExceptionHandler.class); 128 | 129 | private final String name; 130 | 131 | public EventBusExceptionHandler(final String name) 132 | { 133 | this.name = checkNotNull(name, "name is null"); 134 | } 135 | 136 | @Override 137 | public void handleException(final Throwable e, final SubscriberExceptionContext context) 138 | { 139 | LOG.error(e, "Could not call %s/%s on bus %s", context.getSubscriber().getClass().getSimpleName(), context.getSubscriberMethod().getName(), name); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/util/NetworkUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | import java.io.IOException; 17 | import java.net.InetAddress; 18 | import java.net.InetSocketAddress; 19 | import java.net.NetworkInterface; 20 | import java.net.ServerSocket; 21 | import java.net.SocketException; 22 | import java.net.UnknownHostException; 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | import com.google.common.collect.ImmutableList; 27 | 28 | public final class NetworkUtil 29 | { 30 | private NetworkUtil() 31 | { 32 | throw new AssertionError("do not instantiate"); 33 | } 34 | 35 | /** 36 | * Find an unused port. 37 | */ 38 | public static int findUnusedPort() throws IOException 39 | { 40 | int port; 41 | 42 | try (ServerSocket socket = new ServerSocket()) { 43 | socket.bind(new InetSocketAddress(0)); 44 | port = socket.getLocalPort(); 45 | } 46 | 47 | return port; 48 | } 49 | 50 | // 51 | // ======================================================================== 52 | // 53 | // This code was taken from https://github.com/airlift/airlift/blob/master/node/src/main/java/io/airlift/node/NodeInfo.java 54 | // 55 | // ======================================================================== 56 | // 57 | 58 | public static String findPublicIp() 59 | throws UnknownHostException 60 | { 61 | // Check if local host address is a good v4 address 62 | final InetAddress localAddress = InetAddress.getLocalHost(); 63 | if (isV4Address(localAddress) && getGoodAddresses().contains(localAddress)) { 64 | return localAddress.getHostAddress(); 65 | } 66 | 67 | // check all up network interfaces for a good v4 address 68 | for (final InetAddress address : getGoodAddresses()) { 69 | if (isV4Address(address)) { 70 | return address.getHostAddress(); 71 | } 72 | } 73 | 74 | // just return the local host address 75 | // it is most likely that this is a disconnected developer machine 76 | return localAddress.getHostAddress(); 77 | } 78 | 79 | private static List getGoodAddresses() 80 | { 81 | final ImmutableList.Builder list = ImmutableList.builder(); 82 | for (final NetworkInterface networkInterface : getGoodNetworkInterfaces()) { 83 | for (final InetAddress address : Collections.list(networkInterface.getInetAddresses())) { 84 | if (isGoodAddress(address)) { 85 | list.add(address); 86 | } 87 | } 88 | } 89 | return list.build(); 90 | } 91 | 92 | private static List getGoodNetworkInterfaces() 93 | { 94 | try { 95 | final ImmutableList.Builder builder = ImmutableList.builder(); 96 | for (final NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) { 97 | try { 98 | if (!networkInterface.isLoopback() && networkInterface.isUp()) { 99 | builder.add(networkInterface); 100 | } 101 | } 102 | catch (final SocketException se) { 103 | continue; // Ignore that network interface. 104 | } 105 | } 106 | return builder.build(); 107 | } 108 | catch (final SocketException se) { 109 | // Return empty list. 110 | return ImmutableList.of(); 111 | } 112 | } 113 | 114 | private static boolean isV4Address(final InetAddress address) 115 | { 116 | return address.getAddress().length == 4; 117 | } 118 | 119 | private static boolean isGoodAddress(final InetAddress address) 120 | { 121 | return !address.isAnyLocalAddress() && 122 | !address.isLoopbackAddress() && 123 | !address.isMulticastAddress(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/util/ProtobufRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | import org.apache.mesos.Protos; 17 | 18 | import com.google.common.annotations.VisibleForTesting; 19 | import com.google.protobuf.ExtensionRegistry; 20 | 21 | import mesos.internal.Messages; 22 | 23 | public enum ProtobufRegistry 24 | { 25 | INSTANCE; 26 | 27 | private final ExtensionRegistry extensionRegistry; 28 | 29 | private ProtobufRegistry() { 30 | final ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); 31 | Protos.registerAllExtensions(extensionRegistry); 32 | Messages.registerAllExtensions(extensionRegistry); 33 | 34 | this.extensionRegistry = extensionRegistry; 35 | } 36 | 37 | public ExtensionRegistry getExtensionRegistry() { 38 | return extensionRegistry.getUnmodifiable(); 39 | } 40 | 41 | @VisibleForTesting 42 | ExtensionRegistry mutableExtensionRegistry() { 43 | return extensionRegistry; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/util/TimeUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | public final class TimeUtil 17 | { 18 | private TimeUtil() 19 | { 20 | throw new AssertionError("do not instantiate"); 21 | } 22 | 23 | /** 24 | * Mesos wants second granularity. 25 | */ 26 | public static long currentTime() 27 | { 28 | return System.currentTimeMillis() / 1000L; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/util/UPID.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | import static java.lang.String.format; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | import static com.google.common.base.Preconditions.checkState; 20 | 21 | import java.io.IOException; 22 | import java.net.InetAddress; 23 | import java.nio.ByteBuffer; 24 | import java.nio.ByteOrder; 25 | import java.util.List; 26 | 27 | import javax.annotation.Nonnull; 28 | 29 | import com.google.common.base.CharMatcher; 30 | import com.google.common.base.Function; 31 | import com.google.common.base.Objects; 32 | import com.google.common.base.Splitter; 33 | import com.google.common.base.Throwables; 34 | import com.google.common.collect.ImmutableList; 35 | import com.google.common.net.HostAndPort; 36 | 37 | /** 38 | * Represents a mesos internal process address. Consists of an id, the host and a port. 39 | */ 40 | public final class UPID 41 | { 42 | private static final Log LOG = Log.getLog(UPID.class); 43 | 44 | private final String id; 45 | private final HostAndPort hostAndPort; 46 | private final Integer ip; 47 | 48 | private final String pidAsString; 49 | 50 | public static UPID create(final String master) 51 | { 52 | checkNotNull(master, "master is null"); 53 | final List parts = ImmutableList.copyOf(Splitter.on(CharMatcher.anyOf(":@")).limit(3).split(master)); 54 | checkState(parts.size() == 3, "%s is not a valid master definition", master); 55 | 56 | Integer ip = null; 57 | try { 58 | ip = resolveIp(parts.get(1)); 59 | } 60 | catch (final IOException e) { 61 | LOG.warn("Could not resolve %s: %s", parts.get(1), e.getMessage()); 62 | } 63 | 64 | return new UPID(parts.get(0), HostAndPort.fromParts(parts.get(1), Integer.parseInt(parts.get(2))), ip); 65 | } 66 | 67 | public static UPID fromParts(final String id, final HostAndPort hostAndPort) 68 | throws IOException 69 | { 70 | checkNotNull(id, "id is null"); 71 | checkNotNull(hostAndPort, "hostAndPort is null"); 72 | 73 | return new UPID(id, 74 | hostAndPort, 75 | resolveIp(hostAndPort.getHostText())); 76 | } 77 | 78 | private static Integer resolveIp(final String hostname) 79 | throws IOException 80 | { 81 | final InetAddress addr = InetAddress.getByName(hostname); 82 | // Gah. Mesos can only deal with IPv4. 83 | 84 | if (addr == null) { 85 | return null; 86 | } 87 | 88 | if (addr.getAddress().length != 4) { 89 | LOG.warn("Received non-IPv4 address (%s), see https://issues.apache.org/jira/browse/MESOS-1562", hostname); 90 | return null; 91 | } 92 | 93 | final ByteBuffer b = ByteBuffer.wrap(addr.getAddress()).order(ByteOrder.BIG_ENDIAN); 94 | return b.getInt(); 95 | } 96 | 97 | private UPID(final String id, final HostAndPort hostAndPort, final Integer ip) 98 | { 99 | this.id = id; 100 | this.hostAndPort = hostAndPort; 101 | this.ip = ip; 102 | 103 | this.pidAsString = format("%s@%s:%d", id, hostAndPort.getHostText(), hostAndPort.getPort()); 104 | } 105 | 106 | public String getId() 107 | { 108 | return id; 109 | } 110 | 111 | public String getHost() 112 | { 113 | return hostAndPort.getHostText(); 114 | } 115 | 116 | public int getPort() 117 | { 118 | return hostAndPort.getPort(); 119 | } 120 | 121 | public HostAndPort getHostAndPort() 122 | { 123 | return hostAndPort; 124 | } 125 | 126 | public Integer getIp() 127 | { 128 | return ip; 129 | } 130 | 131 | public String asString() 132 | { 133 | return pidAsString; 134 | } 135 | 136 | @Override 137 | public int hashCode() 138 | { 139 | return Objects.hashCode(id, hostAndPort, ip); 140 | } 141 | 142 | @Override 143 | public boolean equals(final Object other) 144 | { 145 | if (other == this) { 146 | return true; 147 | } 148 | if (other == null || other.getClass() != this.getClass()) { 149 | return false; 150 | } 151 | final UPID that = (UPID) other; 152 | 153 | return Objects.equal(this.id, that.id) 154 | && Objects.equal(this.hostAndPort, that.hostAndPort) 155 | && Objects.equal(this.ip, that.ip); 156 | } 157 | 158 | @Override 159 | public String toString() 160 | { 161 | return Objects.toStringHelper(this.getClass()) 162 | .add("id", id) 163 | .add("hostAndPort", hostAndPort) 164 | .add("ip", ip) 165 | .toString(); 166 | } 167 | 168 | public static Function getCreateFunction() 169 | { 170 | return new Function() { 171 | @Override 172 | public UPID apply(@Nonnull final String value) 173 | { 174 | try { 175 | return UPID.create(value); 176 | } 177 | catch (final Throwable t) { 178 | throw Throwables.propagate(t); 179 | } 180 | } 181 | }; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/util/UUIDUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | import java.nio.ByteBuffer; 17 | import java.nio.ByteOrder; 18 | import java.util.UUID; 19 | 20 | import com.google.protobuf.ByteString; 21 | 22 | public final class UUIDUtil 23 | { 24 | private UUIDUtil() 25 | { 26 | throw new AssertionError("do not instantiate"); 27 | } 28 | 29 | public static ByteString uuidBytes(final UUID uuid) 30 | { 31 | final ByteBuffer longBuffer = ByteBuffer.allocate(16).order(ByteOrder.BIG_ENDIAN); 32 | longBuffer.mark(); 33 | longBuffer.putLong(uuid.getMostSignificantBits()); 34 | longBuffer.putLong(uuid.getLeastSignificantBits()); 35 | longBuffer.reset(); 36 | return ByteString.copyFrom(longBuffer); 37 | } 38 | 39 | public static UUID bytesUuid(final ByteString byteString) 40 | { 41 | final ByteBuffer uuidBuffer = byteString.asReadOnlyByteBuffer(); 42 | // This always works (JLS 15.7.4) 43 | return new UUID(uuidBuffer.getLong(), uuidBuffer.getLong()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/zookeeper/DetectMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.zookeeper; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | 18 | import com.google.common.util.concurrent.SettableFuture; 19 | 20 | import org.apache.mesos.Protos.MasterInfo; 21 | 22 | /** 23 | * Sent to the message bus when a client wants to register for detection notification. 24 | */ 25 | public class DetectMessage 26 | { 27 | private final SettableFuture future; 28 | private final MasterInfo previous; 29 | 30 | DetectMessage(final SettableFuture future, final MasterInfo previous) 31 | { 32 | this.future = checkNotNull(future, "future is null"); 33 | this.previous = previous; 34 | } 35 | 36 | public SettableFuture getFuture() 37 | { 38 | return future; 39 | } 40 | 41 | public MasterInfo getPrevious() 42 | { 43 | return previous; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/zookeeper/MasterUpdateMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.zookeeper; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | 18 | import java.util.Set; 19 | 20 | /** 21 | * Sent to the message bus when a new set of nodes should be processed (zookeeper detected a 22 | * node change). 23 | */ 24 | public class MasterUpdateMessage 25 | { 26 | private final Set nodes; 27 | 28 | public MasterUpdateMessage(final Set nodes) 29 | { 30 | this.nodes = checkNotNull(nodes, "nodes is null"); 31 | } 32 | 33 | public Set getNodes() 34 | { 35 | return nodes; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/mesos/zookeeper/ZookeeperMasterDetector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.zookeeper; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | import static com.google.common.base.Preconditions.checkState; 18 | 19 | import java.io.Closeable; 20 | import java.io.IOException; 21 | import java.net.URI; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.Set; 25 | import java.util.SortedMap; 26 | import java.util.TreeMap; 27 | import java.util.concurrent.BlockingQueue; 28 | import java.util.concurrent.LinkedBlockingQueue; 29 | import java.util.concurrent.atomic.AtomicBoolean; 30 | 31 | import com.google.common.base.Objects; 32 | import com.google.common.base.Predicates; 33 | import com.google.common.base.Splitter; 34 | import com.google.common.collect.ImmutableList; 35 | import com.google.common.collect.ImmutableSet; 36 | import com.google.common.collect.Iterables; 37 | import com.google.common.collect.Sets; 38 | import com.google.common.eventbus.Subscribe; 39 | import com.google.common.util.concurrent.ListenableFuture; 40 | import com.google.common.util.concurrent.SettableFuture; 41 | import com.google.protobuf.InvalidProtocolBufferException; 42 | import com.groupon.mesos.util.Log; 43 | import com.groupon.mesos.util.ManagedEventBus; 44 | import com.groupon.mesos.util.UPID; 45 | 46 | import org.I0Itec.zkclient.IZkChildListener; 47 | import org.I0Itec.zkclient.ZkClient; 48 | import org.I0Itec.zkclient.exception.ZkMarshallingError; 49 | import org.I0Itec.zkclient.serialize.ZkSerializer; 50 | import org.apache.mesos.Protos.MasterInfo; 51 | 52 | /** 53 | * Mesos master detector. Watches all the child nodes of the mesos directory in zookeeper, loads all the child nodes when anything 54 | * changes and uses the numerically lowest as master. 55 | * 56 | * Uses the event bus to decouple event submission from event execution, otherwise caller threads might end up running large swaths 57 | * of watcher specific codes (especially when the master changes). May be overkill but it seems to be the right thing. 58 | */ 59 | public class ZookeeperMasterDetector 60 | implements Closeable 61 | { 62 | private static final Log LOG = Log.getLog(ZookeeperMasterDetector.class); 63 | 64 | private final String zookeeperPath; 65 | private final String user; 66 | private final String password; 67 | 68 | private final ZkClient client; 69 | private final ManagedEventBus eventBus; 70 | 71 | private final SortedMap nodeCache = new TreeMap<>(); 72 | private final BlockingQueue futures = new LinkedBlockingQueue<>(); 73 | private final AtomicBoolean running = new AtomicBoolean(false); 74 | 75 | public ZookeeperMasterDetector(final String master, final ManagedEventBus eventBus) throws IOException 76 | { 77 | checkNotNull(master, "master is null"); 78 | this.eventBus = checkNotNull(eventBus, "eventBus is null"); 79 | 80 | final URI zookeeperUri = URI.create(master); 81 | checkState(zookeeperUri.getScheme().equals("zk"), "Only zk:// URIs are supported (%s)", master); 82 | 83 | String authority = zookeeperUri.getAuthority(); 84 | final int atIndex = authority.indexOf('@'); 85 | if (atIndex != -1) { 86 | final List userPass = ImmutableList.copyOf(Splitter.on(':').trimResults().split(authority.substring(0, atIndex))); 87 | checkState(userPass.size() == 2, "found %s for user name and password", userPass); 88 | user = userPass.get(0); 89 | password = userPass.get(1); 90 | authority = authority.substring(atIndex + 1); 91 | } 92 | else { 93 | user = null; 94 | password = null; 95 | } 96 | 97 | String zookeeperPath = zookeeperUri.getPath(); 98 | while (zookeeperPath.endsWith("/")) { 99 | zookeeperPath = zookeeperPath.substring(0, zookeeperPath.length() - 1); 100 | } 101 | this.zookeeperPath = zookeeperPath; 102 | 103 | checkState(!zookeeperPath.equals(""), "A zookeeper path must be given! (%s)", zookeeperPath); 104 | 105 | checkState(user == null && password == null, "Current version of Zkclient does not support authentication!"); 106 | 107 | this.client = new ZkClient(authority); 108 | this.client.setZkSerializer(new MasterInfoZkSerializer()); 109 | } 110 | 111 | @Override 112 | public void close() throws IOException 113 | { 114 | if (running.getAndSet(false)) { 115 | this.client.close(); 116 | 117 | final List detectMessages = new ArrayList<>(futures.size()); 118 | futures.drainTo(detectMessages); 119 | 120 | for (final DetectMessage detectMessage : detectMessages) { 121 | detectMessage.getFuture().cancel(false); 122 | } 123 | } 124 | } 125 | 126 | public void start() 127 | { 128 | if (!running.getAndSet(true)) { 129 | processChildList(client.getChildren(zookeeperPath)); 130 | 131 | client.subscribeChildChanges(zookeeperPath, new IZkChildListener() { 132 | @Override 133 | public void handleChildChange(final String parentPath, final List currentChildren) throws Exception 134 | { 135 | checkState(zookeeperPath.equals(parentPath), "Received Event for %s (expected %s)", parentPath, zookeeperPath); 136 | processChildList(currentChildren); 137 | } 138 | }); 139 | } 140 | } 141 | 142 | public ListenableFuture detect(final MasterInfo previous) 143 | { 144 | checkState(running.get(), "not running"); 145 | 146 | final SettableFuture result = SettableFuture.create(); 147 | eventBus.post(new DetectMessage(result, previous)); 148 | return result; 149 | } 150 | 151 | @Subscribe 152 | public void processMasterUpdate(final MasterUpdateMessage message) 153 | { 154 | final Set currentNodes = message.getNodes(); 155 | final Set nodesToRemove = ImmutableSet.copyOf(Sets.difference(nodeCache.keySet(), currentNodes)); 156 | final Set nodesToAdd = ImmutableSet.copyOf(Sets.difference(currentNodes, nodeCache.keySet())); 157 | 158 | for (final String node : nodesToAdd) { 159 | final String path = zookeeperPath + "/" + node; 160 | final MasterInfo masterInfo = client.readData(path); 161 | nodeCache.put(node, masterInfo); 162 | } 163 | 164 | for (final String node : nodesToRemove) { 165 | nodeCache.remove(node); 166 | } 167 | 168 | LOG.debug("Processed event, active nodes are %s", nodeCache.entrySet()); 169 | 170 | final MasterInfo masterInfo = getMaster(); 171 | 172 | if (masterInfo == null) { 173 | LOG.debug("No current master exists!"); 174 | } 175 | else { 176 | LOG.debug("Current master is %s", UPID.create(masterInfo.getPid()).asString()); 177 | } 178 | 179 | final List detectMessages = new ArrayList<>(futures.size()); 180 | futures.drainTo(detectMessages); 181 | 182 | for (final DetectMessage detectMessage : detectMessages) { 183 | processDetect(detectMessage); 184 | } 185 | } 186 | 187 | @Subscribe 188 | public void processDetect(final DetectMessage message) 189 | { 190 | final SettableFuture future = message.getFuture(); 191 | final MasterInfo previous = message.getPrevious(); 192 | final MasterInfo currentLeader = getMaster(); 193 | 194 | if (!Objects.equal(currentLeader, previous)) { 195 | LOG.debug("Master has changed: %s -> %s", previous, currentLeader); 196 | future.set(currentLeader); 197 | } 198 | else { 199 | LOG.debug("Master unchanged, queueing"); 200 | futures.add(message); 201 | } 202 | } 203 | 204 | private void processChildList(final List children) 205 | { 206 | final Set masterNodes = ImmutableSet.copyOf(Iterables.filter(children, Predicates.containsPattern("^info_"))); 207 | eventBus.post(new MasterUpdateMessage(masterNodes)); 208 | } 209 | 210 | private MasterInfo getMaster() 211 | { 212 | if (nodeCache.isEmpty()) { 213 | return null; 214 | } 215 | final String key = nodeCache.firstKey(); 216 | return nodeCache.get(key); 217 | } 218 | 219 | private static class MasterInfoZkSerializer implements ZkSerializer 220 | { 221 | @Override 222 | public byte[] serialize(final Object data) throws ZkMarshallingError 223 | { 224 | checkState(data instanceof MasterInfo, "%s is not a MasterInfo!", data.getClass().getSimpleName()); 225 | return ((MasterInfo) data).toByteArray(); 226 | } 227 | 228 | @Override 229 | public Object deserialize(final byte[] bytes) throws ZkMarshallingError 230 | { 231 | checkNotNull(bytes, "bytes is null"); 232 | try { 233 | return MasterInfo.parseFrom(bytes); 234 | } 235 | catch (final InvalidProtocolBufferException e) { 236 | return new ZkMarshallingError(e); 237 | } 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/main/protobuf/messages.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import "mesos/mesos.proto"; 20 | 21 | package mesos.internal; 22 | 23 | // TODO(benh): Provide comments for each of these messages. Also, 24 | // consider splitting these messages into different "packages" which 25 | // represent which messages get handled by which components (e.g., the 26 | // "mesos.internal.executor" package includes messages that the 27 | // executor handles). 28 | 29 | 30 | // TODO(benh): It would be great if this could just be a 31 | // TaskInfo wherever it gets used! However, doing so would 32 | // require adding the framework_id field, the executor_id field, and 33 | // the state field into TaskInfo though (or send them another 34 | // way). Also, one performance reason why we don't do that now is 35 | // because storing whatever data is coupled with a TaskInfo 36 | // could be large and unnecessary. 37 | // TODO(bmahler): Add executor_uuid here, and send it to the master. This will 38 | // allow us to expose executor work directories for tasks in the webui when 39 | // looking from the master level. Currently only the slave knows which run the 40 | // task belongs to. 41 | message Task { 42 | required string name = 1; 43 | required TaskID task_id = 2; 44 | required FrameworkID framework_id = 3; 45 | optional ExecutorID executor_id = 4; 46 | required SlaveID slave_id = 5; 47 | required TaskState state = 6; // Latest state of the task. 48 | repeated Resource resources = 7; 49 | repeated TaskStatus statuses = 8; 50 | 51 | // These fields correspond to the state and uuid of the latest 52 | // status update forwarded to the master. 53 | // NOTE: Either both the fields must be set or both must be unset. 54 | optional TaskState status_update_state = 9; 55 | optional bytes status_update_uuid = 10; 56 | } 57 | 58 | 59 | // Describes a role, which are used to group frameworks for allocation 60 | // decisions, depending on the allocation policy being used. 61 | // The weight field can be used to indicate forms of priority. 62 | message RoleInfo { 63 | required string name = 1; 64 | optional double weight = 2 [default = 1]; 65 | } 66 | 67 | 68 | // TODO(vinod): Create a new UUID message type. 69 | message StatusUpdate { 70 | required FrameworkID framework_id = 1; 71 | optional ExecutorID executor_id = 2; 72 | optional SlaveID slave_id = 3; 73 | required TaskStatus status = 4; 74 | required double timestamp = 5; 75 | required bytes uuid = 6; 76 | 77 | // This corresponds to the latest state of the task according to the 78 | // slave. Note that this state might be different than the state in 79 | // 'status' because status update manager queues updates. In other 80 | // words, 'status' corresponds to the update at top of the queue and 81 | // 'latest_state' corresponds to the update at bottom of the queue. 82 | optional TaskState latest_state = 7; 83 | } 84 | 85 | 86 | // This message encapsulates how we checkpoint a status update to disk. 87 | // NOTE: If type == UPDATE, the 'update' field is required. 88 | // NOTE: If type == ACK, the 'uuid' field is required. 89 | message StatusUpdateRecord { 90 | enum Type { 91 | UPDATE = 0; 92 | ACK = 1; 93 | } 94 | required Type type = 1; 95 | optional StatusUpdate update = 2; 96 | optional bytes uuid = 3; 97 | } 98 | 99 | 100 | message SubmitSchedulerRequest 101 | { 102 | required string name = 1; 103 | } 104 | 105 | 106 | message SubmitSchedulerResponse 107 | { 108 | required bool okay = 1; 109 | } 110 | 111 | 112 | message ExecutorToFrameworkMessage { 113 | required SlaveID slave_id = 1; 114 | required FrameworkID framework_id = 2; 115 | required ExecutorID executor_id = 3; 116 | required bytes data = 4; 117 | } 118 | 119 | 120 | message FrameworkToExecutorMessage { 121 | required SlaveID slave_id = 1; 122 | required FrameworkID framework_id = 2; 123 | required ExecutorID executor_id = 3; 124 | required bytes data = 4; 125 | } 126 | 127 | 128 | message RegisterFrameworkMessage { 129 | required FrameworkInfo framework = 1; 130 | } 131 | 132 | 133 | message ReregisterFrameworkMessage { 134 | required FrameworkInfo framework = 2; 135 | required bool failover = 3; 136 | } 137 | 138 | 139 | message FrameworkRegisteredMessage { 140 | required FrameworkID framework_id = 1; 141 | required MasterInfo master_info = 2; 142 | } 143 | 144 | message FrameworkReregisteredMessage { 145 | required FrameworkID framework_id = 1; 146 | required MasterInfo master_info = 2; 147 | } 148 | 149 | message UnregisterFrameworkMessage { 150 | required FrameworkID framework_id = 1; 151 | } 152 | 153 | 154 | message DeactivateFrameworkMessage { 155 | required FrameworkID framework_id = 1; 156 | } 157 | 158 | 159 | message ResourceRequestMessage { 160 | required FrameworkID framework_id = 1; 161 | repeated Request requests = 2; 162 | } 163 | 164 | 165 | message ResourceOffersMessage { 166 | repeated Offer offers = 1; 167 | repeated string pids = 2; 168 | } 169 | 170 | 171 | message LaunchTasksMessage { 172 | required FrameworkID framework_id = 1; 173 | repeated TaskInfo tasks = 3; 174 | required Filters filters = 5; 175 | repeated OfferID offer_ids = 6; 176 | } 177 | 178 | 179 | message RescindResourceOfferMessage { 180 | required OfferID offer_id = 1; 181 | } 182 | 183 | 184 | message ReviveOffersMessage { 185 | required FrameworkID framework_id = 1; 186 | } 187 | 188 | 189 | message RunTaskMessage { 190 | required FrameworkID framework_id = 1; 191 | required FrameworkInfo framework = 2; 192 | required string pid = 3; 193 | required TaskInfo task = 4; 194 | } 195 | 196 | 197 | message KillTaskMessage { 198 | // TODO(bmahler): Include the SlaveID here to improve the Master's 199 | // ability to respond for non-activated slaves. 200 | required FrameworkID framework_id = 1; 201 | required TaskID task_id = 2; 202 | } 203 | 204 | 205 | // NOTE: If 'pid' is present, scheduler driver sends an 206 | // acknowledgement to the pid. 207 | message StatusUpdateMessage { 208 | required StatusUpdate update = 1; 209 | optional string pid = 2; 210 | } 211 | 212 | 213 | message StatusUpdateAcknowledgementMessage { 214 | required SlaveID slave_id = 1; 215 | required FrameworkID framework_id = 2; 216 | required TaskID task_id = 3; 217 | required bytes uuid = 4; 218 | } 219 | 220 | 221 | message LostSlaveMessage { 222 | required SlaveID slave_id = 1; 223 | } 224 | 225 | 226 | // Allows the framework to query the status for non-terminal tasks. 227 | // This causes the master to send back the latest task status for 228 | // each task in 'statuses', if possible. Tasks that are no longer 229 | // known will result in a TASK_LOST update. If statuses is empty, 230 | // then the master will send the latest status for each task 231 | // currently known. 232 | message ReconcileTasksMessage { 233 | required FrameworkID framework_id = 1; 234 | repeated TaskStatus statuses = 2; // Should be non-terminal only. 235 | } 236 | 237 | 238 | message FrameworkErrorMessage { 239 | required string message = 2; 240 | } 241 | 242 | 243 | message RegisterSlaveMessage { 244 | required SlaveInfo slave = 1; 245 | 246 | // NOTE: This is a hack for the master to detect the slave's 247 | // version. If unset the slave is < 0.21.0. 248 | // TODO(bmahler): Do proper versioning: MESOS-986. 249 | optional string version = 2; 250 | } 251 | 252 | 253 | message ReregisterSlaveMessage { 254 | // TODO(bmahler): slave_id is deprecated. 255 | // 0.21.0: Now an optional field. Always written, never read. 256 | // 0.22.0: Remove this field. 257 | optional SlaveID slave_id = 1; 258 | required SlaveInfo slave = 2; 259 | repeated ExecutorInfo executor_infos = 4; 260 | repeated Task tasks = 3; 261 | repeated Archive.Framework completed_frameworks = 5; 262 | 263 | // NOTE: This is a hack for the master to detect the slave's 264 | // version. If unset the slave is < 0.21.0. 265 | // TODO(bmahler): Do proper versioning: MESOS-986. 266 | optional string version = 6; 267 | } 268 | 269 | 270 | message SlaveRegisteredMessage { 271 | required SlaveID slave_id = 1; 272 | } 273 | 274 | 275 | message SlaveReregisteredMessage { 276 | required SlaveID slave_id = 1; 277 | 278 | repeated ReconcileTasksMessage reconciliations = 2; 279 | } 280 | 281 | 282 | message UnregisterSlaveMessage { 283 | required SlaveID slave_id = 1; 284 | } 285 | 286 | 287 | // This message is periodically sent by the master to the slave. 288 | // If the slave is connected to the master, "connected" is true. 289 | message PingSlaveMessage { 290 | required bool connected = 1; 291 | } 292 | 293 | 294 | // This message is sent by the slave to the master in response to the 295 | // PingSlaveMessage. 296 | message PongSlaveMessage {} 297 | 298 | 299 | // Tells a slave to shut down all executors of the given framework. 300 | message ShutdownFrameworkMessage { 301 | required FrameworkID framework_id = 1; 302 | } 303 | 304 | 305 | // Tells the executor to initiate a shut down by invoking 306 | // Executor::shutdown. 307 | message ShutdownExecutorMessage {} 308 | 309 | 310 | message UpdateFrameworkMessage { 311 | required FrameworkID framework_id = 1; 312 | required string pid = 2; 313 | } 314 | 315 | 316 | message RegisterExecutorMessage { 317 | required FrameworkID framework_id = 1; 318 | required ExecutorID executor_id = 2; 319 | } 320 | 321 | 322 | message ExecutorRegisteredMessage { 323 | required ExecutorInfo executor_info = 2; 324 | required FrameworkID framework_id = 3; 325 | required FrameworkInfo framework_info = 4; 326 | required SlaveID slave_id = 5; 327 | required SlaveInfo slave_info = 6; 328 | } 329 | 330 | 331 | message ExecutorReregisteredMessage { 332 | required SlaveID slave_id = 1; 333 | required SlaveInfo slave_info = 2; 334 | } 335 | 336 | 337 | message ExitedExecutorMessage { 338 | required SlaveID slave_id = 1; 339 | required FrameworkID framework_id = 2; 340 | required ExecutorID executor_id = 3; 341 | required int32 status = 4; 342 | } 343 | 344 | 345 | message ReconnectExecutorMessage { 346 | required SlaveID slave_id = 1; 347 | } 348 | 349 | 350 | message ReregisterExecutorMessage { 351 | required ExecutorID executor_id = 1; 352 | required FrameworkID framework_id = 2; 353 | repeated TaskInfo tasks = 3; 354 | repeated StatusUpdate updates = 4; 355 | } 356 | 357 | 358 | message ShutdownMessage { 359 | optional string message = 1; 360 | } 361 | 362 | 363 | message AuthenticateMessage { 364 | required string pid = 1; // PID that needs to be authenticated. 365 | } 366 | 367 | 368 | message AuthenticationMechanismsMessage { 369 | repeated string mechanisms = 1; // List of available SASL mechanisms. 370 | } 371 | 372 | 373 | message AuthenticationStartMessage { 374 | required string mechanism = 1; 375 | optional string data = 2; 376 | } 377 | 378 | 379 | message AuthenticationStepMessage { 380 | required bytes data = 1; 381 | } 382 | 383 | 384 | message AuthenticationCompletedMessage {} 385 | 386 | 387 | message AuthenticationFailedMessage {} 388 | 389 | 390 | message AuthenticationErrorMessage { 391 | optional string error = 1; 392 | } 393 | 394 | 395 | // TODO(adam-mesos): Move this to an 'archive' package. 396 | /** 397 | * Describes Completed Frameworks, etc. for archival. 398 | */ 399 | message Archive { 400 | message Framework { 401 | required FrameworkInfo framework_info = 1; 402 | optional string pid = 2; 403 | repeated Task tasks = 3; 404 | } 405 | repeated Framework frameworks = 1; 406 | } 407 | 408 | // Message describing task current health status that is sent by 409 | // the task health checker to the command executor. 410 | // The command executor reports the task status back to the 411 | // on each receive. If the health checker configured faiure 412 | // condition meets, then kill_task flag will be set to true which 413 | // the executor on message receive will kill the task. 414 | message TaskHealthStatus { 415 | required TaskID task_id = 1; 416 | 417 | required bool healthy = 2; 418 | 419 | // Flag to initiate task kill. 420 | optional bool kill_task = 3 [default = false]; 421 | 422 | // Number of consecutive counts in current status. 423 | // This will not be populated if task is healthy. 424 | optional int32 consecutive_failures = 4; 425 | } 426 | 427 | 428 | // Collection of Modules. 429 | message Modules { 430 | message Library { 431 | // If "file" contains a slash ("/"), then it is interpreted as a 432 | // (relative or absolute) pathname. Otherwise a standard library 433 | // search is performed. 434 | optional string file = 1; 435 | 436 | // We will add the proper prefix ("lib") and suffix (".so" for 437 | // Linux and ".dylib" for OS X) to the "name". 438 | optional string name = 2; 439 | 440 | message Module { 441 | // Module name. 442 | optional string name = 1; 443 | 444 | // Module-specific parameters. 445 | repeated Parameter parameters = 2; 446 | } 447 | 448 | repeated Module modules = 3; 449 | } 450 | 451 | repeated Library libraries = 1; 452 | } 453 | -------------------------------------------------------------------------------- /src/main/protobuf/state.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mesos.internal.state; 20 | 21 | // Describes a state entry, a versioned (via a UUID) key/value pair. 22 | message Entry { 23 | required string name = 1; 24 | required bytes uuid = 2; 25 | required bytes value = 3; 26 | } 27 | 28 | 29 | // Describes an operation used in the log storage implementation. 30 | message Operation { 31 | enum Type { 32 | SNAPSHOT = 1; 33 | DIFF = 3; 34 | EXPUNGE = 2; 35 | } 36 | 37 | // Describes a "snapshot" operation. 38 | message Snapshot { 39 | required Entry entry = 1; 40 | } 41 | 42 | // Describes a "diff" operation where the 'value' of the entry is 43 | // just the diff itself, but the 'uuid' represents the UUID of the 44 | // entry after applying this diff. 45 | message Diff { 46 | required Entry entry = 1; 47 | } 48 | 49 | // Describes an "expunge" operation. 50 | message Expunge { 51 | required string name = 1; 52 | } 53 | 54 | required Type type = 1; 55 | optional Snapshot snapshot = 2; 56 | optional Diff diff = 4; 57 | optional Expunge expunge = 3; 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/mesos/state/AbstractTestState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.state; 15 | 16 | import static org.junit.Assert.assertArrayEquals; 17 | import static org.junit.Assert.assertEquals; 18 | import static org.junit.Assert.assertFalse; 19 | import static org.junit.Assert.assertNotNull; 20 | import static org.junit.Assert.assertNull; 21 | import static org.junit.Assert.assertTrue; 22 | 23 | import java.nio.charset.StandardCharsets; 24 | import java.util.ArrayList; 25 | import java.util.Collections; 26 | import java.util.Iterator; 27 | import java.util.List; 28 | import java.util.Set; 29 | import java.util.SortedSet; 30 | import java.util.UUID; 31 | import java.util.concurrent.Callable; 32 | import java.util.concurrent.Executors; 33 | import java.util.concurrent.TimeUnit; 34 | 35 | import com.google.common.collect.ImmutableList; 36 | import com.google.common.collect.ImmutableSet; 37 | import com.google.common.collect.ImmutableSortedSet; 38 | import com.google.common.util.concurrent.Futures; 39 | import com.google.common.util.concurrent.ListenableFuture; 40 | import com.google.common.util.concurrent.ListeningExecutorService; 41 | import com.google.common.util.concurrent.MoreExecutors; 42 | 43 | import org.apache.mesos.state.State; 44 | import org.apache.mesos.state.Variable; 45 | import org.junit.Test; 46 | 47 | public abstract class AbstractTestState 48 | { 49 | protected abstract State getState(); 50 | 51 | @Test 52 | public void testNonExistentValue() 53 | throws Exception 54 | { 55 | final State state = getState(); 56 | 57 | final Variable empty = state.fetch("does-not-exist").get(); 58 | assertNotNull(empty); 59 | assertTrue(empty.value().length == 0); 60 | } 61 | 62 | @Test 63 | public void testSetAndGet() 64 | throws Exception 65 | { 66 | final State state = getState(); 67 | 68 | final byte[] value = "The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.UTF_8); 69 | 70 | final Variable var = state.fetch("someValue").get(); 71 | assertTrue(var.value().length == 0); 72 | final Variable newVar = var.mutate(value); 73 | 74 | final JVariable storedVar = (JVariable) state.store(newVar).get(); 75 | assertNotNull(storedVar); 76 | 77 | final JVariable retrievedVar = (JVariable) state.fetch("someValue").get(); 78 | 79 | assertNotNull(retrievedVar); 80 | assertArrayEquals(newVar.value(), retrievedVar.value()); 81 | 82 | assertArrayEquals(storedVar.value(), retrievedVar.value()); 83 | assertEquals(storedVar.getName(), retrievedVar.getName()); 84 | assertEquals(storedVar.getUuid(), retrievedVar.getUuid()); 85 | } 86 | 87 | @Test 88 | public void testNames() 89 | throws Exception 90 | { 91 | final State state = getState(); 92 | 93 | final byte[] value = "The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.UTF_8); 94 | 95 | final ImmutableSortedSet.Builder builder = ImmutableSortedSet.naturalOrder(); 96 | 97 | for (int i = 0; i < 10; i++) { 98 | final String key = "name-" + UUID.randomUUID().toString(); 99 | builder.add(key); 100 | 101 | final Variable var = state.fetch(key).get(); 102 | assertTrue(var.value().length == 0); 103 | state.store(var.mutate(value)).get(); 104 | } 105 | 106 | final SortedSet keys = builder.build(); 107 | 108 | final Iterator it = state.names().get(); 109 | 110 | final SortedSet entries = ImmutableSortedSet.copyOf(it); 111 | 112 | assertEquals(keys, entries); 113 | } 114 | 115 | @Test 116 | public void testUpdateOk() 117 | throws Exception 118 | { 119 | final State state = getState(); 120 | 121 | final byte[] value = "The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.UTF_8); 122 | final byte[] newValue = "Ich esse Autos zum Abendbrot und mache Kopfsprung ins Sandbecken. Gruen. Rot. Pferderennen.".getBytes(StandardCharsets.UTF_8); 123 | 124 | final Variable var = state.fetch("someValue").get(); 125 | assertTrue(var.value().length == 0); 126 | 127 | JVariable storedVar = (JVariable) state.store(var.mutate(value)).get(); 128 | 129 | storedVar = (JVariable) state.store(storedVar.mutate(newValue)).get(); 130 | assertNotNull(storedVar); 131 | 132 | final JVariable retrievedVar = (JVariable) state.fetch("someValue").get(); 133 | assertNotNull(retrievedVar); 134 | 135 | assertArrayEquals(storedVar.value(), retrievedVar.value()); 136 | assertEquals(storedVar.getName(), retrievedVar.getName()); 137 | assertEquals(storedVar.getUuid(), retrievedVar.getUuid()); 138 | } 139 | 140 | @Test 141 | public void testOldUpdateRefused() 142 | throws Exception 143 | { 144 | final State state = getState(); 145 | 146 | final byte[] value = "The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.UTF_8); 147 | final byte[] newValue = "Ich esse Autos zum Abendbrot und mache Kopfsprung ins Sandbecken. Gruen. Rot. Pferderennen.".getBytes(StandardCharsets.UTF_8); 148 | 149 | final Variable var = state.fetch("someValue").get(); 150 | assertTrue(var.value().length == 0); 151 | 152 | JVariable storedVar = (JVariable) state.store(var.mutate(value)).get(); 153 | 154 | storedVar = (JVariable) state.store(var.mutate(newValue)).get(); 155 | assertNull(storedVar); 156 | } 157 | 158 | @Test 159 | public void testExpungeOk() 160 | throws Exception 161 | { 162 | final State state = getState(); 163 | 164 | final byte[] value = "The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.UTF_8); 165 | 166 | final Variable var = state.fetch("someValue").get(); 167 | assertTrue(var.value().length == 0); 168 | 169 | final JVariable storedVar = (JVariable) state.store(var.mutate(value)).get(); 170 | 171 | final boolean expunged = state.expunge(storedVar).get(); 172 | assertTrue(expunged); 173 | 174 | final JVariable retrievedVar = (JVariable) state.fetch("someValue").get(); 175 | 176 | assertNotNull(retrievedVar); 177 | assertEquals(0, retrievedVar.value().length); 178 | } 179 | 180 | @Test 181 | public void testOldExpungeRefused() 182 | throws Exception 183 | { 184 | final State state = getState(); 185 | 186 | final byte[] value = "The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.UTF_8); 187 | 188 | final Variable var = state.fetch("someValue").get(); 189 | assertTrue(var.value().length == 0); 190 | 191 | final JVariable storedVar = (JVariable) state.store(var.mutate(value)).get(); 192 | 193 | final boolean expunged = state.expunge(var).get(); 194 | assertFalse(expunged); 195 | 196 | final JVariable retrievedVar = (JVariable) state.fetch("someValue").get(); 197 | assertNotNull(retrievedVar); 198 | 199 | assertArrayEquals(storedVar.value(), retrievedVar.value()); 200 | assertEquals(storedVar.getName(), retrievedVar.getName()); 201 | assertEquals(storedVar.getUuid(), retrievedVar.getUuid()); 202 | } 203 | 204 | @Test 205 | public void testExpungeNonExistentValue() 206 | throws Exception 207 | { 208 | final State state = getState(); 209 | 210 | final Variable empty = state.fetch("does-not-exist").get(); 211 | assertNotNull(empty); 212 | assertTrue(empty.value().length == 0); 213 | 214 | final boolean expunged = state.expunge(empty).get(); 215 | assertFalse(expunged); 216 | } 217 | 218 | @Test 219 | public void testTenMonkeysPressTenKeys() 220 | throws Exception 221 | { 222 | final State state = getState(); 223 | 224 | final byte[] value = "The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.UTF_8); 225 | final byte[] newValue = "Ich esse Autos zum Abendbrot und mache Kopfsprung ins Sandbecken. Gruen. Rot. Pferderennen.".getBytes(StandardCharsets.UTF_8); 226 | 227 | final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); 228 | 229 | final ImmutableList.Builder> builder = ImmutableList.builder(); 230 | 231 | for (int i = 0; i < 10; i++) { 232 | builder.add(executor.submit(new Callable() { 233 | 234 | @Override 235 | public String call() throws Exception 236 | { 237 | final String key = "key-" + UUID.randomUUID().toString(); 238 | final Variable var = state.fetch(key).get(); 239 | assertTrue(var.value().length == 0); 240 | 241 | JVariable storedVar = (JVariable) state.store(var.mutate(value)).get(); 242 | 243 | storedVar = (JVariable) state.store(storedVar.mutate(newValue)).get(); 244 | assertNotNull(storedVar); 245 | 246 | final JVariable retrievedVar = (JVariable) state.fetch(key).get(); 247 | assertNotNull(retrievedVar); 248 | 249 | assertArrayEquals(storedVar.value(), retrievedVar.value()); 250 | assertEquals(storedVar.getName(), retrievedVar.getName()); 251 | assertEquals(storedVar.getUuid(), retrievedVar.getUuid()); 252 | 253 | return key; 254 | } 255 | })); 256 | } 257 | 258 | final List keys = Futures.allAsList(builder.build()).get(); 259 | 260 | for (final String key : keys) { 261 | final JVariable retrievedVar = (JVariable) state.fetch(key).get(); 262 | assertNotNull(retrievedVar); 263 | 264 | assertArrayEquals(newValue, retrievedVar.value()); 265 | } 266 | 267 | executor.shutdown(); 268 | executor.awaitTermination(1, TimeUnit.DAYS); 269 | } 270 | 271 | @Test 272 | public void testTenMonkeysHammerOnTenKeys() 273 | throws Exception 274 | { 275 | final State state = getState(); 276 | 277 | final byte[] value = "The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.UTF_8); 278 | final byte[] newValue = "Ich esse Autos zum Abendbrot und mache Kopfsprung ins Sandbecken. Gruen. Rot. Pferderennen.".getBytes(StandardCharsets.UTF_8); 279 | 280 | final ImmutableSet.Builder builder = ImmutableSet.builder(); 281 | final ImmutableList.Builder varBuilder = ImmutableList.builder(); 282 | 283 | for (int i = 0; i < 10; i++) { 284 | final String key = "key-" + UUID.randomUUID().toString(); 285 | final Variable var = state.fetch(key).get(); 286 | assertTrue(var.value().length == 0); 287 | 288 | final Variable storedVar = state.store(var.mutate(value)).get(); 289 | assertNotNull(storedVar); 290 | builder.add(key); 291 | varBuilder.add(storedVar); 292 | } 293 | 294 | final Set keys = builder.build(); 295 | final List variables = varBuilder.build(); 296 | 297 | final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); 298 | 299 | final ImmutableList.Builder> resultBuilder = ImmutableList.builder(); 300 | 301 | for (int i = 0; i < 10; i++) { 302 | resultBuilder.add(executor.submit(new Callable() { 303 | 304 | @Override 305 | public Integer call() throws Exception 306 | { 307 | final ArrayList vars = new ArrayList<>(variables); 308 | Collections.shuffle(vars); 309 | 310 | int updateCount = 0; 311 | for (final Variable var : vars) { 312 | final Variable storedVar = state.store(var.mutate(newValue)).get(); 313 | if (storedVar != null) { 314 | updateCount++; 315 | Thread.sleep(2L); 316 | } 317 | } 318 | 319 | return updateCount; 320 | } 321 | })); 322 | } 323 | 324 | final List results = Futures.allAsList(resultBuilder.build()).get(); 325 | 326 | int finalTally = 0; 327 | for (final Integer result : results) { 328 | finalTally += result; 329 | } 330 | 331 | assertEquals(10, finalTally); 332 | 333 | for (final String key : keys) { 334 | final Variable retrievedVar = state.fetch(key).get(); 335 | assertNotNull(retrievedVar); 336 | 337 | assertArrayEquals(newValue, retrievedVar.value()); 338 | } 339 | 340 | executor.shutdown(); 341 | executor.awaitTermination(1, TimeUnit.DAYS); 342 | } 343 | 344 | } 345 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/mesos/state/TestJLevelDBState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.state; 15 | 16 | import java.io.File; 17 | 18 | import com.google.common.io.Files; 19 | 20 | import org.apache.mesos.state.State; 21 | import org.junit.After; 22 | import org.junit.Before; 23 | 24 | public class TestJLevelDBState 25 | extends AbstractTestState 26 | { 27 | private File levelDbFolder; 28 | private JLevelDBState state; 29 | 30 | @Before 31 | public void setUp() 32 | throws Exception 33 | { 34 | levelDbFolder = Files.createTempDir(); 35 | state = new JLevelDBState(levelDbFolder.getAbsolutePath()); 36 | } 37 | 38 | @After 39 | public void tearDown() 40 | throws Exception 41 | { 42 | state.close(); 43 | 44 | for (final File file : Files.fileTreeTraverser().postOrderTraversal(levelDbFolder)) { 45 | file.delete(); 46 | } 47 | } 48 | 49 | @Override 50 | protected State getState() 51 | { 52 | return state; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/mesos/state/TestJVariable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.state; 15 | 16 | import static com.groupon.mesos.util.UUIDUtil.uuidBytes; 17 | 18 | import static org.junit.Assert.assertArrayEquals; 19 | import static org.junit.Assert.assertEquals; 20 | 21 | import java.nio.charset.StandardCharsets; 22 | import java.util.UUID; 23 | 24 | import com.google.protobuf.ByteString; 25 | 26 | import org.apache.mesos.state.Variable; 27 | import org.junit.Test; 28 | 29 | import mesos.internal.state.State.Entry; 30 | 31 | public class TestJVariable 32 | { 33 | @Test 34 | public void testEntry() 35 | { 36 | final UUID uuid = UUID.randomUUID(); 37 | final String name = "test-name"; 38 | final byte[] value = "The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.UTF_8); 39 | 40 | final Entry entry = Entry.newBuilder() 41 | .setName(name) 42 | .setValue(ByteString.copyFrom(value)) 43 | .setUuid(uuidBytes(uuid)) 44 | .build(); 45 | 46 | final JVariable v = new JVariable(entry); 47 | 48 | assertEquals(name, v.getName()); 49 | assertEquals(uuid, v.getUuid()); 50 | assertArrayEquals(value, v.value()); 51 | } 52 | 53 | @Test 54 | public void testNameValue() 55 | { 56 | final String name = "test-name"; 57 | final byte[] value = "The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.UTF_8); 58 | 59 | final JVariable v = new JVariable(name, value); 60 | 61 | assertEquals(name, v.getName()); 62 | assertArrayEquals(value, v.value()); 63 | } 64 | 65 | @Test 66 | public void testMutate() 67 | { 68 | final String name = "test-name"; 69 | final byte[] value = "The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.UTF_8); 70 | final byte[] newValue = "Ich esse Autos zum Abendbrot und mache Kopfsprung ins Sandbecken. Gruen. Rot. Pferderennen.".getBytes(StandardCharsets.UTF_8); 71 | 72 | final JVariable v = new JVariable(name, value); 73 | 74 | final Variable w = v.mutate(newValue); 75 | 76 | assertEquals(JVariable.class, w.getClass()); 77 | assertArrayEquals(newValue, w.value()); 78 | 79 | assertEquals(name, v.getName()); 80 | assertArrayEquals(value, v.value()); 81 | 82 | final JVariable v2 = (JVariable) w; 83 | 84 | assertEquals(v.getName(), v2.getName()); 85 | assertEquals(v.getUuid(), v2.getUuid()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/mesos/state/TestJZookeeperState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.state; 15 | 16 | import java.io.IOException; 17 | import java.util.UUID; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | import com.groupon.mesos.testutil.EmbeddedZookeeper; 21 | 22 | import org.apache.mesos.state.State; 23 | import org.junit.After; 24 | import org.junit.AfterClass; 25 | import org.junit.Before; 26 | import org.junit.BeforeClass; 27 | 28 | public class TestJZookeeperState 29 | extends AbstractTestState 30 | { 31 | private static EmbeddedZookeeper ZOOKEEPER; 32 | 33 | private JZookeeperState state; 34 | 35 | @BeforeClass 36 | public static void setUpZookeeper() 37 | throws Exception 38 | { 39 | ZOOKEEPER = new EmbeddedZookeeper(); 40 | ZOOKEEPER.start(); 41 | } 42 | 43 | @Before 44 | public void setUp() 45 | throws IOException 46 | { 47 | state = new JZookeeperState(ZOOKEEPER.getConnectString(), 30, TimeUnit.SECONDS, "test-" + UUID.randomUUID().toString()); 48 | } 49 | 50 | @After 51 | public void tearDown() 52 | throws Exception 53 | { 54 | state.close(); 55 | } 56 | 57 | @AfterClass 58 | public static void tearDownZookeeper() 59 | { 60 | ZOOKEEPER.close(); 61 | ZOOKEEPER.cleanup(); 62 | } 63 | 64 | @Override 65 | protected State getState() 66 | { 67 | return state; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/mesos/testutil/EmbeddedZookeeper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.testutil; 15 | 16 | import static com.google.common.base.Preconditions.checkState; 17 | 18 | import java.io.Closeable; 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.net.InetSocketAddress; 22 | import java.util.concurrent.atomic.AtomicBoolean; 23 | 24 | import com.google.common.io.Files; 25 | import com.groupon.mesos.util.NetworkUtil; 26 | 27 | import org.apache.zookeeper.server.NIOServerCnxn; 28 | import org.apache.zookeeper.server.ZooKeeperServer; 29 | import org.apache.zookeeper.server.persistence.FileTxnSnapLog; 30 | 31 | public class EmbeddedZookeeper 32 | implements Closeable 33 | { 34 | private final int port; 35 | private final File zkDataDir; 36 | private final ZooKeeperServer zkServer; 37 | private final NIOServerCnxn.Factory cnxnFactory; 38 | 39 | private final AtomicBoolean started = new AtomicBoolean(); 40 | private final AtomicBoolean stopped = new AtomicBoolean(); 41 | 42 | public EmbeddedZookeeper() 43 | throws IOException 44 | { 45 | this(NetworkUtil.findUnusedPort()); 46 | } 47 | 48 | public EmbeddedZookeeper(final int port) 49 | throws IOException 50 | { 51 | this.port = port; 52 | zkDataDir = Files.createTempDir(); 53 | zkServer = new ZooKeeperServer(); 54 | 55 | final FileTxnSnapLog ftxn = new FileTxnSnapLog(zkDataDir, zkDataDir); 56 | zkServer.setTxnLogFactory(ftxn); 57 | 58 | cnxnFactory = new NIOServerCnxn.Factory(new InetSocketAddress(this.port), 0); 59 | } 60 | 61 | public void start() 62 | throws InterruptedException, IOException 63 | { 64 | if (!started.getAndSet(true)) { 65 | cnxnFactory.startup(zkServer); 66 | } 67 | } 68 | 69 | @Override 70 | public void close() 71 | { 72 | if (started.get() && !stopped.getAndSet(true)) { 73 | cnxnFactory.shutdown(); 74 | try { 75 | cnxnFactory.join(); 76 | } 77 | catch (final InterruptedException e) { 78 | Thread.currentThread().interrupt(); 79 | } 80 | 81 | if (zkServer.isRunning()) { 82 | zkServer.shutdown(); 83 | } 84 | } 85 | } 86 | 87 | public String getConnectString() 88 | { 89 | return "127.0.0.1:" + Integer.toString(port); 90 | } 91 | 92 | public int getPort() 93 | { 94 | return port; 95 | } 96 | 97 | public void cleanup() 98 | { 99 | checkState(stopped.get(), "not stopped"); 100 | 101 | for (final File file : Files.fileTreeTraverser().postOrderTraversal(zkDataDir)) { 102 | file.delete(); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/mesos/util/TestProtobufRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | 18 | import java.io.ByteArrayInputStream; 19 | 20 | import org.junit.Test; 21 | 22 | import com.google.protobuf.ExtensionRegistry; 23 | import com.groupon.jesos.TestProtos; 24 | import com.groupon.jesos.TestProtos.Base; 25 | import com.groupon.jesos.TestProtos.ExtBase; 26 | 27 | public class TestProtobufRegistry 28 | { 29 | @Test 30 | public void testRoundtrip() throws Exception { 31 | ExtensionRegistry e = ProtobufRegistry.INSTANCE.mutableExtensionRegistry(); 32 | 33 | TestProtos.registerAllExtensions(e); 34 | 35 | ExtBase extBase = 36 | ExtBase.newBuilder() 37 | .setA("172.42.1.2") 38 | .setB("br0") 39 | .setD(24) 40 | .setC("172.42.1.1") 41 | .setE("00:11:22:33:44:55") 42 | .build(); 43 | 44 | Base base = Base.newBuilder() 45 | .setExtension(ExtBase.type, extBase) 46 | .setType(Base.Type.EXT_BASE) 47 | .build(); 48 | 49 | byte [] bytes = base.toByteArray(); 50 | 51 | Base generated = Base.parseFrom(new ByteArrayInputStream(bytes), e); 52 | 53 | ExtBase extGenerated = generated.getExtension(ExtBase.type); 54 | 55 | assertEquals(extBase.getA(), extGenerated.getA()); 56 | assertEquals(extBase.getB(), extGenerated.getB()); 57 | assertEquals(extBase.getC(), extGenerated.getC()); 58 | assertEquals(extBase.getD(), extGenerated.getD()); 59 | assertEquals(extBase.getE(), extGenerated.getE()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/mesos/util/TestUUIDUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package com.groupon.mesos.util; 15 | 16 | import static com.groupon.mesos.util.UUIDUtil.bytesUuid; 17 | import static com.groupon.mesos.util.UUIDUtil.uuidBytes; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | 21 | import java.util.UUID; 22 | 23 | import com.google.protobuf.ByteString; 24 | 25 | import org.junit.Test; 26 | 27 | public class TestUUIDUtil 28 | { 29 | @Test 30 | public void testUuid() 31 | { 32 | final UUID uuid = UUID.randomUUID(); 33 | final ByteString str = uuidBytes(uuid); 34 | final UUID newUuid = bytesUuid(str); 35 | assertEquals(uuid, newUuid); 36 | 37 | final ByteString newStr = uuidBytes(newUuid); 38 | assertEquals(str, newStr); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/protobuf/test.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package test; 15 | 16 | option java_package = "com.groupon.jesos"; 17 | option java_outer_classname = "TestProtos"; 18 | 19 | message Base { 20 | extensions 100 to max; 21 | 22 | enum Type { 23 | EXT_BASE = 1; 24 | } 25 | 26 | required Type type = 1; 27 | } 28 | 29 | message ExtBase { 30 | extend Base { 31 | required ExtBase type = 100; 32 | } 33 | 34 | optional string a = 1; 35 | optional string b = 2; 36 | optional string c = 3; 37 | optional uint32 d = 4; 38 | optional string e = 5; 39 | } 40 | 41 | --------------------------------------------------------------------------------