├── docs ├── requirements.txt ├── docutils.conf ├── conf.py ├── versions.cfg ├── limitations.rst ├── index.rst ├── buildout.cfg ├── installation.rst ├── api.rst ├── bootstrap.py └── usage.rst ├── settings.gradle ├── bin ├── zk ├── run ├── deploy-auth ├── deploy-marathon └── deploy ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── vagrant ├── mesos-dns.json ├── systemd │ └── mesos-dns.service ├── provision-common.sh ├── provision-common-multi-master.sh └── VagrantfileMultiMaster ├── CONTRIBUTING.rst ├── .gitignore ├── .travis.yml ├── src ├── main │ └── java │ │ └── io │ │ └── crate │ │ └── frameworks │ │ └── mesos │ │ ├── Observer.java │ │ ├── api │ │ ├── GenericAPIResponse.java │ │ ├── ClusterResizeRequest.java │ │ └── CrateHttpService.java │ │ ├── StreamRedirect.java │ │ ├── MessageMissingResource.java │ │ ├── SaneProtos.java │ │ ├── Observable.java │ │ ├── CrateMessage.java │ │ ├── config │ │ ├── Resources.java │ │ └── Configuration.java │ │ ├── CrateInstance.java │ │ ├── Version.java │ │ ├── PersistentStateStore.java │ │ ├── CrateInstances.java │ │ ├── CrateState.java │ │ ├── CrateExecutableInfo.java │ │ └── Main.java └── test │ ├── resources │ ├── app.json │ └── minimesosConf │ └── java │ └── io │ └── crate │ └── frameworks │ └── mesos │ ├── config │ └── ResourcesTest.java │ ├── integration │ ├── ScaleIntegrationTest.java │ └── BaseIntegrationTest.java │ ├── VersionTest.java │ ├── CrateStateTest.java │ ├── api │ ├── CrateHttpServiceTest.java │ └── CrateRestResourceTest.java │ ├── MainTest.java │ ├── CrateInstancesTest.java │ ├── CrateExecutableInfoTest.java │ └── CrateSchedulerTest.java ├── copyright.xml ├── CHANGES.txt ├── marathon ├── local.json └── marathon.json.template ├── DEVELOP.rst ├── gradlew.bat ├── devtools └── create_tag.sh ├── Vagrantfile ├── README.rst ├── gradlew └── LICENSE /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | crate-docs-theme 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = 'crate-mesos' 3 | -------------------------------------------------------------------------------- /docs/docutils.conf: -------------------------------------------------------------------------------- 1 | [html4css1 writer] 2 | field_name_limit: 40 3 | 4 | -------------------------------------------------------------------------------- /bin/zk: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | vagrant ssh mesos-master -c '/opt/mesosphere/zookeeper/bin/zkCli.sh' 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crate/crate-mesos-framework/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /bin/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | vagrant ssh mesos-master -c "LIBPROCESS_IP=192.168.10.100 java -Djava.library.path=/usr/local/lib -jar /vagrant/build/libs/crate-mesos-*.jar $*" 3 | -------------------------------------------------------------------------------- /vagrant/mesos-dns.json: -------------------------------------------------------------------------------- 1 | { 2 | "zk": "zk://localhost:2181/mesos", 3 | "masters": ["localhost:5050"], 4 | "domain": "mesos", 5 | "resolvers": ["10.0.2.3"] 6 | } 7 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | from crate.theme.rtd.conf import * 2 | project = u'Crate Mesos Framework' 3 | primary_domain = u'Crate.Mesos' 4 | source_suffix = '.rst' 5 | exclude_patterns = ['out/**', 'eggs/**', 'requirements.txt'] 6 | html_favicon = None 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed May 11 09:57:46 CEST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip 7 | -------------------------------------------------------------------------------- /bin/deploy-auth: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm build/libs/crate-mesos-*.jar 3 | ./gradlew fatJar && vagrant ssh -c "LIBPROCESS_IP=192.168.10.100 MESOS_AUTHENTICATE=true DEFAULT_PRINCIPAL=crate DEFAULT_SECRET=foo java -Djava.library.path=/usr/local/lib -jar /vagrant/build/libs/crate-mesos-*.jar $* --zookeeper 192.168.10.100:2181 -Des.network.publish_host=_enp0s8:ipv4_" 4 | -------------------------------------------------------------------------------- /bin/deploy-marathon: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PWD=$(pwd) 3 | MESOS_MASTER="192.168.10.100" 4 | echo "Working directory: $PWD" 5 | ./gradlew clean snapshotRelease 6 | cd ./build/libs && tar -zcf crate-mesos.tar.gz crate-mesos-*.jar && cd $PWD 7 | curl -sXPOST "http://$MESOS_MASTER:8080/v2/apps" -d@marathon/local.json -H "Content-Type: application/json" | jq . 8 | 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Thank you for your interest in contributing. 6 | 7 | Please see the CrateDB `contribution guide`_ for more information. Everything in 8 | the CrateDB contribution guide applies to this repository. 9 | 10 | .. _contribution guide: https://github.com/crate/crate/blob/master/CONTRIBUTING.rst -------------------------------------------------------------------------------- /bin/deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./gradlew clean && \ 3 | ./gradlew snapshotRelease && \ 4 | vagrant ssh mesos-master -c "LIBPROCESS_IP=192.168.10.100 java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -Djava.library.path=/usr/local/lib -jar /vagrant/build/libs/crate-mesos-*.jar $* --zookeeper 192.168.10.100:2181 -Des.network.publish_host=_enp0s8:ipv4_" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | 15 | .gradle 16 | /build 17 | *.iml 18 | *.ipr 19 | *.iws 20 | .idea/ 21 | 22 | .vagrant/ 23 | .installed.cfg 24 | docs/out/ 25 | docs/bin/ 26 | 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk7 4 | - oraclejdk8 5 | 6 | sudo: false 7 | env: 8 | global: 9 | - JAVA7_HOME="/usr/lib/jvm/java-7-oracle" 10 | 11 | cache: 12 | directories: 13 | - $HOME/.m2 14 | 15 | notifications: 16 | email: false 17 | 18 | before_install: 19 | - ulimit -u 65535 20 | 21 | script: 22 | - ./gradlew test 23 | 24 | branches: 25 | except: 26 | - /.*\/.*/ 27 | 28 | -------------------------------------------------------------------------------- /vagrant/systemd/mesos-dns.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Mesos DNS 3 | After=network.target zookeeper.service 4 | Wants=zookeeper.service 5 | 6 | [Service] 7 | Restart=on-failure 8 | ExecStartPre=/usr/bin/sh -c "echo 'nameserver 127.0.0.1' > /etc/resolv.conf" 9 | ExecStart=/home/vagrant/go/src/github.com/mesosphere/mesos-dns/mesos-dns -config=/etc/mesos-dns.json 10 | ExecStopPost=/usr/bin/sh -c "echo 'nameserver 10.0.2.3' > /etc/resolv.conf" 11 | User=root 12 | 13 | [Install] 14 | WantedBy=default.target 15 | -------------------------------------------------------------------------------- /docs/versions.cfg: -------------------------------------------------------------------------------- 1 | [versions] 2 | Babel = 2.3.4 3 | Jinja2 = 2.8 4 | Pygments = 2.1.3 5 | Sphinx = 1.4.2 6 | alabaster = 0.7.8 7 | collective.recipe.template = 1.13 8 | crate-docs-theme = 0.5.13 9 | docutils = 0.12 10 | pytz = 2016.4 11 | setuptools = 21.2.2 12 | six = 1.10.0 13 | snowballstemmer = 1.2.1 14 | zc.buildout = 2.5.1 15 | zc.recipe.egg = 2.0.3 16 | 17 | # Required by: 18 | # Jinja2==2.8 19 | MarkupSafe = 0.23 20 | 21 | # Required by: 22 | # Sphinx==1.4.2 23 | imagesize = 0.7.1 24 | 25 | # Required by: 26 | # crate-docs-theme==0.5.13 27 | sphinxcontrib-plantuml = 0.8.1 28 | 29 | -------------------------------------------------------------------------------- /docs/limitations.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Limitations 3 | =========== 4 | 5 | The Crate Mesos Framework still has the following limitations and known issues: 6 | 7 | * No automatic handling of cluster failures. 8 | * Overall cluster health needs to be monitored separately, using the Crate Admin 9 | UI (running on port ``4200`` at path ``/admin``) or other third party tools. 10 | * The cluster does not automatically resize depending on used resources. 11 | * Although Crate requires a minimum disk size to start, the disk usage is not 12 | monitored inside the framework. This can be done using the Admin UI or plain 13 | SQL. 14 | * A zero-downtime upgrade is not possible. 15 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Crate Mesos Framework 3 | ===================== 4 | 5 | This is an integration framework which allows running and managing the Crate_ 6 | database through Mesos_. 7 | 8 | .. warning:: 9 | 10 | The Crate Mesos Framework is intended to be used for quickly launching 11 | Crate on a Mesos cluster for testing purposes. 12 | It's not recommended for production use yet and changes in the API might 13 | break older installations! 14 | 15 | 16 | .. rubric:: Table of Contents 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | 21 | installation 22 | usage 23 | api 24 | limitations 25 | 26 | .. _Crate: https://crate.io 27 | .. _Mesos: http://mesos.apache.org 28 | -------------------------------------------------------------------------------- /docs/buildout.cfg: -------------------------------------------------------------------------------- 1 | [buildout] 2 | versions = versions 3 | extends = versions.cfg 4 | show-picked-versions = true 5 | parts = sphinx 6 | sphinx-cmd 7 | 8 | [sphinx] 9 | recipe = zc.recipe.egg:script 10 | eggs = sphinx 11 | crate-docs-theme 12 | relative-paths=true 13 | 14 | [sphinx-cmd] 15 | recipe = collective.recipe.template 16 | output = ${buildout:bin-directory}/sphinx 17 | mode = 0755 18 | input = inline: 19 | #!/bin/sh 20 | declare -i RESULT=0 21 | echo "\033[1mCleaning output folder ...\033[0m" 22 | rm -rf out/ && rm -rf clients/out/ 23 | RESULT+=$? 24 | echo "\033[1;44mBuilding docs (html) ...\033[0m" 25 | ${buildout:bin-directory}/sphinx-build -b html -E . ${buildout:directory}/out/html 26 | RESULT+=$? 27 | echo "\033[1;44mBuilding docs (text) ...\033[0m" 28 | ${buildout:bin-directory}/sphinx-build -b text -E . ${buildout:directory}/out/text 29 | RESULT+=$? 30 | exit $RESULT 31 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/Observer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | public interface Observer { 25 | 26 | public void update(ObservedType data); 27 | } 28 | -------------------------------------------------------------------------------- /copyright.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | ================================= 2 | CHANGES for Crate Mesos Framework 3 | ================================= 4 | 5 | Unreleased 6 | ========== 7 | 8 | 2016/06/22 0.2.1 9 | ================ 10 | 11 | - Fixed leading master selection method 12 | 13 | 2016/06/02 0.2.0 14 | ================ 15 | 16 | - Fixed bug where the same task got killed multiple times 17 | 18 | - Do not allow to resize a Crate cluster greater than the amount of available 19 | slaves in the Mesos cluster 20 | 21 | - Set ``gateway`` and ``minimum_master_nodes`` settings when resizing the 22 | Crate cluster 23 | 24 | - Fix: ``--crate-node-count`` command line argument was ignored 25 | 26 | - Crate executable now uses correct Java executable provided by fetch URI 27 | 28 | - Updated Mesos to ``0.28.1`` and Crate client to ``0.54.8`` 29 | 30 | 2015/08/08 0.1.0 31 | ================ 32 | 33 | - Added shutdown hook to terminate crate if the executor goes down 34 | 35 | - Stop Crate process when executor is shutting down 36 | 37 | - Make framework name configurable 38 | 39 | - Use JRE from OpenJDK from 40 | `https://github.com/alexkasko/openjdk-unofficial-builds#openjdk-unofficial-installers-for-windows-linux-and-mac-os-x`_ 41 | 42 | - Use custom version of OpenJDK as dependency 43 | 44 | - initial framework implementation 45 | see README.rst for usage and limitations 46 | 47 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/api/GenericAPIResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos.api; 23 | 24 | 25 | abstract class GenericAPIResponse { 26 | 27 | private int status = 200; 28 | private String message = "SUCCESS"; 29 | 30 | public int getStatus() { 31 | return this.status; 32 | } 33 | 34 | public Object getMessage() { 35 | return this.message; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/resources/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "crate-dev", 3 | "instances": 1, 4 | "cpus": 0.25, 5 | "mem": 128, 6 | "portDefinitions": [ 7 | { 8 | "port": ${API_PORT}, 9 | "protocol": "tcp", 10 | "name": "api" 11 | } 12 | ], 13 | "requirePorts": true, 14 | "env": { 15 | "CRATE_CLUSTER_NAME": "crate-dev", 16 | "CRATE_HTTP_PORT": "${HTTP_PORT}", 17 | "CRATE_TRANSPORT_PORT": "${TRANSPORT_PORT}" 18 | }, 19 | "fetch": [ 20 | { 21 | "uri": "https://cdn.crate.io/downloads/openjdk/jre-7u80-linux.tar.gz", 22 | "extract": true, 23 | "executable": false, 24 | "cache": false 25 | }, 26 | { 27 | "uri": "file://${CRATE_MESOS_FRAMEWORK_PATH}", 28 | "extract": false, 29 | "executable": true, 30 | "cache": false 31 | } 32 | ], 33 | "cmd": "env && $(pwd)/jre/bin/java $JAVA_OPTS -jar $(pwd)/${CRATE_MESOS_FRAMEWORK} --zookeeper ${ZOOKEEPER}:2181 --api-port $PORT0 --crate-cluster-name $CRATE_CLUSTER_NAME --crate-version ${CRATE_VERSION} --crate-http-port $CRATE_HTTP_PORT --crate-transport-port $CRATE_TRANSPORT_PORT --resource-cpus 0.25 --resource-memory 512 --framework-user root", 34 | "healthChecks": [ 35 | { 36 | "protocol": "HTTP", 37 | "path": "/cluster", 38 | "gracePeriodSeconds": 3, 39 | "intervalSeconds": 10, 40 | "portIndex": 0, 41 | "timeoutSeconds": 10, 42 | "maxConsecutiveFailures": 3 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /marathon/local.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "crate-framework", 3 | "instances": 1, 4 | "cpus": 0.25, 5 | "mem": 128, 6 | "portDefinitions": [ 7 | { 8 | "port": 4040, 9 | "protocol": "tcp", 10 | "name": "api" 11 | } 12 | ], 13 | "requirePorts": true, 14 | "env": { 15 | "CRATE_CLUSTER_NAME": "dev-local", 16 | "CRATE_VERSION": "0.54.9", 17 | "CRATE_HTTP_PORT": "4200", 18 | "CRATE_TRANSPORT_PORT": "4300", 19 | "MESOS_MASTER": "192.168.10.100" 20 | }, 21 | "fetch": [ 22 | { 23 | "uri": "file:///vagrant/build/libs/crate-mesos.tar.gz", 24 | "extract": true, 25 | "executable": false, 26 | "cache": false 27 | }, 28 | { 29 | "uri": "https://cdn.crate.io/downloads/openjdk/jre-7u80-linux.tar.gz", 30 | "extract": true, 31 | "executable": false, 32 | "cache": false 33 | } 34 | ], 35 | "cmd": "env && $(pwd)/jre/bin/java $JAVA_OPTS -jar $(pwd)/crate-mesos-*.jar --crate-cluster-name $CRATE_CLUSTER_NAME --crate-version $CRATE_VERSION --api-port $PORT0 --crate-http-port $CRATE_HTTP_PORT --crate-transport-port $CRATE_TRANSPORT_PORT --zookeeper $MESOS_MASTER:2181 -Des.network.publish_host=_enp0s8:ipv4_", 36 | "healthChecks": [ 37 | { 38 | "protocol": "HTTP", 39 | "path": "/cluster", 40 | "gracePeriodSeconds": 3, 41 | "intervalSeconds": 10, 42 | "portIndex": 0, 43 | "timeoutSeconds": 10, 44 | "maxConsecutiveFailures": 3 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/api/ClusterResizeRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos.api; 23 | 24 | /** 25 | * A model that represents a cluster resize action. 26 | */ 27 | public class ClusterResizeRequest { 28 | 29 | private int instances; 30 | 31 | public ClusterResizeRequest() {} 32 | 33 | public ClusterResizeRequest(int instances) { 34 | this.instances = instances; 35 | } 36 | 37 | public int getInstances() { 38 | return instances; 39 | } 40 | 41 | public void setInstances(int instances) { 42 | this.instances = instances; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /marathon/marathon.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "id": "crate-framework", 3 | "instances": 1, 4 | "cpus": 0.25, 5 | "mem": 128, 6 | "portDefinitions": [ 7 | { 8 | "port": 4242, 9 | "protocol": "tcp", 10 | "name": "api" 11 | } 12 | ], 13 | "requirePorts": true, 14 | "env": { 15 | "CRATE_CLUSTER_NAME": "dev-local", 16 | "CRATE_VERSION": "0.54.8", 17 | "CRATE_HTTP_PORT": "4200", 18 | "CRATE_TRANSPORT_PORT": "4300", 19 | "MESOS_MASTER": "zk://{zookeeper}/mesos", 20 | "ZOOKEEPER": "mesos-master-1:2181, mesos-master-2:2181, mesos-master-3:2181", 21 | "RESOURCE_HEAP": "256", 22 | "RESOURCE_DISK": "1024", 23 | "FRAMEWORK_NAME": "crate-mesos", 24 | "FRAMEWORK_USER": "crate", 25 | "FRAMEWORK_ROLE": "*" 26 | }, 27 | "fetch": [ 28 | { 29 | "uri": "https://cdn.crate.io/downloads/releases/crate-mesos-0.2.0-SNAPSHOT-db6471b.tar.gz", 30 | "extract": true, 31 | "executable": false, 32 | "cache": false 33 | }, 34 | { 35 | "uri": "https://cdn.crate.io/downloads/openjdk/jre-7u80-linux.tar.gz", 36 | "extract": true, 37 | "executable": false, 38 | "cache": false 39 | } 40 | ], 41 | "cmd": "env && $(pwd)/jre/bin/java $JAVA_OPTS -jar $(pwd)/crate-mesos-*.jar --crate-cluster-name $CRATE_CLUSTER_NAME --crate-version $CRATE_VERSION --zookeeper $ZOOKEEPER --api-port $PORT0 --crate-http-port $CRATE_HTTP_PORT --crate-transport-port $CRATE_TRANSPORT_PORT --mesos-master $MESOS_MASTER --resource-heap $RESOURCE_HEAP --resource-disk $RESOURCE_DISK --framework-name $FRAMEWORK_NAME --framework-user $FRAMEWORK_USER --framework-role $FRAMEWORK_ROLE", 42 | "healthChecks": [ 43 | { 44 | "protocol": "HTTP", 45 | "path": "/cluster", 46 | "gracePeriodSeconds": 3, 47 | "intervalSeconds": 10, 48 | "portIndex": 0, 49 | "timeoutSeconds": 10, 50 | "maxConsecutiveFailures": 3 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/io/crate/frameworks/mesos/config/ResourcesTest.java: -------------------------------------------------------------------------------- 1 | package io.crate.frameworks.mesos.config; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.Arrays; 7 | 8 | import static io.crate.frameworks.mesos.SaneProtos.cpus; 9 | import static io.crate.frameworks.mesos.SaneProtos.mem; 10 | import static io.crate.frameworks.mesos.SaneProtos.ports; 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | import static org.hamcrest.core.Is.is; 13 | 14 | public class ResourcesTest { 15 | 16 | private Configuration configuration; 17 | 18 | @Before 19 | public void setUp() throws Exception { 20 | configuration = new Configuration(); 21 | configuration.resCpus = 2.0d; 22 | configuration.resMemory = 1024d * 8; 23 | configuration.resHeap = 1024d * 4; 24 | configuration.resDisk = 1024d * 20; 25 | configuration.httpPort = 4200; 26 | } 27 | 28 | @Test 29 | public void testMatchesWithOfferThatHasToFewCpus() throws Exception { 30 | assertThat(Resources.matches(Arrays.asList(cpus(1), mem(20_000)), configuration), is(false)); 31 | } 32 | 33 | @Test 34 | public void testMatchesWithOfferThatHasNotEnoughMemory() throws Exception { 35 | assertThat(Resources.matches(Arrays.asList(cpus(4), mem(512)), configuration), is(false)); 36 | } 37 | 38 | @Test 39 | public void testMatchesWithOfferThatHasNoRequestedPorts() throws Exception { 40 | assertThat(Resources.matches(Arrays.asList(cpus(4), mem(512), ports(3000, 4000)), configuration), is(false)); 41 | } 42 | 43 | @Test 44 | public void testMatchesWithOfferThatHasEnough() throws Exception { 45 | assertThat(Resources.matches(Arrays.asList(cpus(4), mem(20_000), ports(4000, 5000)), configuration), is(true)); 46 | } 47 | 48 | @Test 49 | public void testMatchesWithOfferThatHasEnoughEq() throws Exception { 50 | assertThat(Resources.matches(Arrays.asList( 51 | cpus(4), 52 | mem(20_000), 53 | ports(4200, 4200), 54 | ports(4300, 4300)), configuration), is(true)); 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/StreamRedirect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import org.apache.commons.io.IOUtils; 25 | 26 | import java.io.*; 27 | 28 | public class StreamRedirect extends Thread { 29 | 30 | InputStream inputStream; 31 | PrintStream printStream; 32 | 33 | StreamRedirect(InputStream is, PrintStream type) { 34 | this.inputStream = is; 35 | this.printStream = type; 36 | } 37 | 38 | public void run() { 39 | InputStreamReader streamReader = null; 40 | BufferedReader bufferedReader = null; 41 | try { 42 | streamReader = new InputStreamReader(inputStream); 43 | bufferedReader = new BufferedReader(streamReader); 44 | String line; 45 | while ((line = bufferedReader.readLine()) != null) 46 | printStream.println(line); 47 | } 48 | catch (IOException ioe) { 49 | ioe.printStackTrace(); 50 | } finally { 51 | IOUtils.closeQuietly(bufferedReader); 52 | IOUtils.closeQuietly(streamReader); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /vagrant/provision-common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MESOS_VERSION="0.28.1" 4 | ZOOKEEPER_VERSION="3.4.6" 5 | MARATHON_VERSION="1.1.1" 6 | 7 | rpm -Uvh http://repos.mesosphere.io/el/7/noarch/RPMS/mesosphere-el-repo-7-1.noarch.rpm 8 | yum -y install "mesos-$MESOS_VERSION" \ 9 | "mesosphere-zookeeper-$ZOOKEEPER_VERSION" \ 10 | "marathon-$MARATHON_VERSION" 11 | yum -y install golang git bind-utils cyrus-sasl cyrus-sasl-md5 12 | 13 | # Clear old config 14 | rm -rf /tmp/mesos 15 | rm -rf /etc/mesos-master/* 16 | rm -rf /etc/mesos-slave/* 17 | 18 | echo "zk://mesos-master:2181/mesos" > /etc/mesos/zk 19 | 20 | if [[ $(grep "# crate-mesos-framework" /etc/hosts 2> /dev/null) == "" ]] ; then 21 | echo "Add /etc/hosts entries ..." 22 | echo "# crate-mesos-framework 23 | # Since we do not have DHCP/DNS we need to set the hostnames manually 24 | 192.168.10.100 mesos-master 25 | 192.168.10.101 mesos-slave-1 26 | 192.168.10.102 mesos-slave-2 27 | 192.168.10.103 mesos-slave-3" >> /etc/hosts 28 | fi 29 | 30 | # Authentication 31 | # Uncomment and re-run provisioning if you want to test master/slave/framework authentication. 32 | # echo '/etc/mesos/passwd' > /etc/mesos-master/credentials 33 | # echo '/etc/mesos/passwd' > /etc/mesos-slave/credential 34 | # echo 'crammd5' > /etc/mesos-master/authenticators 35 | # echo 'crate foo' > /etc/mesos/passwd 36 | 37 | if [ $(id -u crate 2> /dev/null || echo "0") -gt 0 ] ; then 38 | echo "User crate already exists" 39 | else 40 | useradd crate -s /bin/bash -m 41 | fi 42 | rm -rf /tmp/crate && mkdir -pv /tmp/crate && chown -R crate.crate /tmp/crate 43 | 44 | # Mesos DNS 45 | # Uncomment and re-run provisioning if you want to use Mesos DNS. 46 | # mkdir /home/vagrant/go 47 | # chown vagrant.vagrant /home/vagrant/go 48 | # echo 'export GOPATH=$HOME/go' >> /home/vagrant/.bash_profile 49 | # su - vagrant -c "go get github.com/miekg/dns" 50 | # su - vagrant -c "go get github.com/mesosphere/mesos-dns" 51 | # su - vagrant -c "cd /home/vagrant/go/src/github.com/mesosphere/mesos-dns && go build -o mesos-dns" 52 | # cp /vagrant/vagrant/mesos-dns.json /etc/ 53 | # cp /vagrant/vagrant/systemd/mesos-dns.service /etc/systemd/system/ 54 | # systemctl enable mesos-dns 55 | # systemctl restart mesos-dns 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/MessageMissingResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import java.io.Serializable; 25 | 26 | public class MessageMissingResource implements Serializable { 27 | private Reason reason; 28 | 29 | public final static MessageMissingResource MISSING_DATA_PATH = new MessageMissingResource(Reason.MISSING_DATA_PATH); 30 | public final static MessageMissingResource MISSING_BLOB_PATH = new MessageMissingResource(Reason.MISSING_BLOB_PATH); 31 | 32 | private final static String MISSING_DATA_PATH_VALUE = "MISSING_DATA_PATH"; 33 | private final static String MISSING_BLOB_PATH_VALUE = "MISSING_BLOB_PATH"; 34 | 35 | private static final long serialVersionUID = 1L; 36 | 37 | public enum Reason { 38 | 39 | MISSING_DATA_PATH(MISSING_DATA_PATH_VALUE), 40 | MISSING_BLOB_PATH(MISSING_BLOB_PATH_VALUE); 41 | 42 | String name; 43 | 44 | Reason(String name) { 45 | this.name = name; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return name; 51 | } 52 | } 53 | 54 | public MessageMissingResource(Reason reason) { 55 | this.reason = reason; 56 | } 57 | 58 | public Reason reason() { 59 | return reason; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/io/crate/frameworks/mesos/integration/ScaleIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos.integration; 23 | 24 | import com.mashape.unirest.http.exceptions.UnirestException; 25 | import io.crate.frameworks.mesos.CrateInstances; 26 | import org.json.JSONArray; 27 | import org.junit.After; 28 | import org.junit.Test; 29 | 30 | import static org.hamcrest.Matchers.is; 31 | import static org.junit.Assert.assertThat; 32 | 33 | public class ScaleIntegrationTest extends BaseIntegrationTest { 34 | 35 | @Test 36 | public void testScaleWithMinimumMasterNodes() throws UnirestException { 37 | // starting with 0 instances 38 | assertThat(crateNodesCount(), is(0)); 39 | // then scale to 3 .. 2 .. 1 instances 40 | for (int i = 3; i > 0; i--) { 41 | scaleCrate(i); 42 | assertThat(CrateInstances.calculateQuorum(i), is(getMinMasterNodes())); 43 | assertThat(crateNodesCount(), is(i)); 44 | } 45 | 46 | } 47 | 48 | private int getMinMasterNodes() throws UnirestException { 49 | JSONArray rows = execute("SELECT settings['discovery']['zen']['minimum_master_nodes'] FROM sys.cluster") 50 | .getBody().getObject().getJSONArray("rows"); 51 | return rows.getJSONArray(0).getInt(0); 52 | } 53 | 54 | @After 55 | public void tearDown() throws UnirestException { 56 | shutdown(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/SaneProtos.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import org.apache.mesos.Protos; 25 | 26 | public class SaneProtos { 27 | 28 | public static Protos.Resource cpus(double value) { 29 | return scalarResource("cpus", value); 30 | } 31 | 32 | public static Protos.Resource mem(double value) { 33 | return scalarResource("mem", value); 34 | } 35 | 36 | public static Protos.TaskID taskID(String taskId) { 37 | return Protos.TaskID.newBuilder().setValue(taskId).build(); 38 | } 39 | 40 | public static Protos.Resource scalarResource(String name, double value) { 41 | Protos.Resource.Builder builder = Protos.Resource.newBuilder() 42 | .setName(name) 43 | .setRole("*") 44 | .setType(Protos.Value.Type.SCALAR) 45 | .setScalar(Protos.Value.Scalar.newBuilder().setValue(value).build()); 46 | return builder.build(); 47 | } 48 | 49 | 50 | public static Protos.Resource ports(int from, int to) { 51 | return Protos.Resource.newBuilder() 52 | .setName("ports") 53 | .setRole("*") 54 | .setType(Protos.Value.Type.RANGES) 55 | .setRanges(Protos.Value.Ranges.newBuilder().addRange( 56 | Protos.Value.Range.newBuilder().setBegin(from).setEnd(to).build())) 57 | .build(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /vagrant/provision-common-multi-master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MESOS_VERSION="0.28.1" 4 | ZOOKEEPER_VERSION="3.4.6" 5 | MARATHON_VERSION="1.1.1" 6 | 7 | rpm -Uvh http://repos.mesosphere.io/el/7/noarch/RPMS/mesosphere-el-repo-7-1.noarch.rpm 8 | yum -y install "mesos-$MESOS_VERSION" \ 9 | "mesosphere-zookeeper-$ZOOKEEPER_VERSION" \ 10 | "marathon-$MARATHON_VERSION" 11 | yum -y install golang git bind-utils cyrus-sasl cyrus-sasl-md5 12 | 13 | # Clear old config 14 | rm -rf /tmp/mesos 15 | rm -rf /etc/mesos-master/* 16 | rm -rf /etc/mesos-slave/* 17 | 18 | echo "zk://mesos-master-1:2181,mesos-master-2:2181,mesos-master-3:2181/mesos" > /etc/mesos/zk 19 | 20 | echo "# zk servers 21 | server.1=mesos-master-1:2888:3888 22 | server.2=mesos-master-2:2888:3888 23 | server.3=mesos-master-3:2888:3888" >> /etc/zookeeper/conf/zoo.cfg 24 | 25 | if [[ $(grep "# crate-mesos-framework" /etc/hosts 2> /dev/null) == "" ]] ; then 26 | echo "Add /etc/hosts entries ..." 27 | echo "# crate-mesos-framework 28 | # Since we do not have DHCP/DNS we need to set the hostnames manually 29 | 192.168.10.100 mesos-master-1 30 | 192.168.10.99 mesos-master-2 31 | 192.168.10.98 mesos-master-3 32 | 192.168.10.101 mesos-slave-1 33 | 192.168.10.102 mesos-slave-2 34 | 192.168.10.103 mesos-slave-3" >> /etc/hosts 35 | fi 36 | 37 | # Authentication 38 | # Uncomment and re-run provisioning if you want to test master/slave/framework authentication. 39 | # echo '/etc/mesos/passwd' > /etc/mesos-master/credentials 40 | # echo '/etc/mesos/passwd' > /etc/mesos-slave/credential 41 | # echo 'crammd5' > /etc/mesos-master/authenticators 42 | # echo 'crate foo' > /etc/mesos/passwd 43 | 44 | if [ $(id -u crate 2> /dev/null || echo "0") -gt 0 ] ; then 45 | echo "User crate already exists" 46 | else 47 | useradd crate -s /bin/bash -m 48 | fi 49 | rm -rf /tmp/crate && mkdir -pv /tmp/crate && chown -R crate.crate /tmp/crate 50 | 51 | # Mesos DNS 52 | # Uncomment and re-run provisioning if you want to use Mesos DNS. 53 | # mkdir /home/vagrant/go 54 | # chown vagrant.vagrant /home/vagrant/go 55 | # echo 'export GOPATH=$HOME/go' >> /home/vagrant/.bash_profile 56 | # su - vagrant -c "go get github.com/miekg/dns" 57 | # su - vagrant -c "go get github.com/mesosphere/mesos-dns" 58 | # su - vagrant -c "cd /home/vagrant/go/src/github.com/mesosphere/mesos-dns && go build -o mesos-dns" 59 | # cp /vagrant/vagrant/mesos-dns.json /etc/ 60 | # cp /vagrant/vagrant/systemd/mesos-dns.service /etc/systemd/system/ 61 | # systemctl enable mesos-dns 62 | # systemctl restart mesos-dns 63 | 64 | -------------------------------------------------------------------------------- /DEVELOP.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Developer Guide 3 | =============== 4 | 5 | IDE 6 | --- 7 | 8 | We recommend that you use `IntelliJ IDEA`_ to develop this project. 9 | 10 | Gradle can be used to generate project files that can be opened in IntelliJ:: 11 | 12 | $ ./gradlew idea 13 | 14 | Running tests 15 | ============= 16 | 17 | You can run tests directly from within IntelliJ. 18 | 19 | You can also run them using Gradle:: 20 | 21 | $ ./gradlew test 22 | 23 | Integrations Tests 24 | ------------------ 25 | 26 | The integration tests can be run like so:: 27 | 28 | $ ./gradlew itest 29 | 30 | The integration tests use the Minimesos_ testing framework which requires a 31 | working local Docker_ environment. 32 | 33 | You can set up a local Docker environment like so:: 34 | 35 | $ docker-machine create -d virtualbox \ 36 | --virtualbox-memory 8192 \ 37 | --virtualbox-cpu-count 1 \ 38 | minimesos 39 | $ eval $(docker-machine env minimesos) 40 | $ sudo route delete 172.17.0.0/16 41 | $ sudo route -n add 172.17.0.0/16 $(docker-machine ip minimesos) 42 | 43 | Debugging 44 | ========= 45 | 46 | It is not easy to debug the framework from within IntelliJ. 47 | 48 | The best way to debug is to use loggers and then watch the log files from 49 | Mesos:: 50 | 51 | $ vagrant ssh -c "tail -f /var/log/mesos/mesos-{slave,master}.{INFO,WARNING,ERROR}" 52 | 53 | Zookeeper 54 | ========= 55 | 56 | If you need to reset the state in Zookeeper you can use the CLI client:: 57 | 58 | $ bin/zk 59 | 60 | To delete all CrateDB Mesos state run:: 61 | 62 | $ rmr /crate-mesos 63 | 64 | Preparing a Release 65 | =================== 66 | 67 | To create a new release, you must: 68 | 69 | - Update the ``CURRENT`` version in ``io.crate.frameworks.mesos.Version`` 70 | 71 | - Add a section for the new version in the ``CHANGES.txt`` file 72 | 73 | - Commit your changes with a message like "prepare release x.y.z" 74 | 75 | - Push to origin 76 | 77 | - Create a tag by running ``./devtools/create_tag.sh`` 78 | 79 | At this point, Jenkins will take care of the rest. 80 | 81 | However, if you'd like to do things manually, you can run:: 82 | 83 | $ ./gradlew release 84 | 85 | This Gradle task runs the ``fatJar`` task, but additionally checks that the 86 | output of ``git describe --tag`` matches the current version. 87 | 88 | The resulting JAR file will reside in the ``build/libs`` directory. 89 | 90 | .. _Docker: https://www.docker.com/ 91 | .. _Gradle: http://www.gradle.org/ 92 | .. _IntelliJ IDEA: https://www.jetbrains.com/idea/ 93 | .. _Minimesos: https://minimesos.org/ 94 | -------------------------------------------------------------------------------- /src/test/java/io/crate/frameworks/mesos/VersionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import org.junit.Test; 25 | 26 | import static org.junit.Assert.*; 27 | 28 | public class VersionTest { 29 | 30 | static final Version VERSION_2_0_0 = new Version(20000, false); 31 | static final Version VERSION_1_0_0 = new Version(10000, false); 32 | 33 | @Test 34 | public void testAfter() throws Exception { 35 | assertTrue(VERSION_2_0_0.after(VERSION_1_0_0)); 36 | assertFalse(VERSION_1_0_0.after(VERSION_2_0_0)); 37 | } 38 | 39 | @Test 40 | public void testBefore() throws Exception { 41 | assertTrue(VERSION_1_0_0.before(VERSION_2_0_0)); 42 | assertFalse(VERSION_2_0_0.before(VERSION_1_0_0)); 43 | } 44 | 45 | @Test 46 | public void testNumber() throws Exception { 47 | assertEquals("0.0.1", new Version(1, false).number()); 48 | assertEquals("0.1.0", new Version(100, false).number()); 49 | assertEquals("0.1.1", new Version(101, false).number()); 50 | assertEquals("1.0.0", new Version(10000, false).number()); 51 | assertEquals("1.0.0", new Version(10000, false).number()); 52 | assertEquals("1.0.1", new Version(10001, false).number()); 53 | assertEquals("1.1.0", new Version(10100, false).number()); 54 | assertEquals("1.1.1", new Version(10101, false).number()); 55 | } 56 | 57 | @Test 58 | public void testToString() throws Exception { 59 | assertEquals("1.0.0", new Version(10000, false).toString()); 60 | assertEquals("1.0.0-SNAPSHOT", new Version(10000, true).toString()); 61 | 62 | } 63 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/Observable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import java.io.IOException; 25 | import java.io.Serializable; 26 | import java.util.LinkedList; 27 | import java.util.List; 28 | 29 | 30 | public class Observable implements Serializable { 31 | 32 | private List> observers = new LinkedList<>(); 33 | private ObservedType value; 34 | 35 | public Observable(ObservedType value) { 36 | this.value = value; 37 | } 38 | 39 | public ObservedType getValue(){ 40 | return this.value; 41 | } 42 | 43 | public void setValue(ObservedType value){ 44 | this.value = value; 45 | this.notifyObservers(this.value); 46 | } 47 | 48 | public void addObserver(Observer observer) { 49 | if (observer != null && !observers.contains(observer)) { 50 | observers.add(observer); 51 | } 52 | } 53 | 54 | public void notifyObservers(ObservedType value) { 55 | for (Observer observer : observers) { 56 | observer.update(value); 57 | } 58 | } 59 | 60 | public void clearObservers() { 61 | observers.clear(); 62 | } 63 | 64 | private void writeObject(java.io.ObjectOutputStream stream) 65 | throws IOException { 66 | stream.writeObject(value); 67 | } 68 | 69 | @SuppressWarnings("unchecked") 70 | private void readObject(java.io.ObjectInputStream stream) 71 | throws IOException, ClassNotFoundException { 72 | value = (ObservedType) stream.readObject(); 73 | observers = new LinkedList<>(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/resources/minimesosConf: -------------------------------------------------------------------------------- 1 | minimesos { 2 | clusterName = "crate-mesos-cluster" 3 | loggingLevel = "INFO" 4 | mapAgentSandboxVolume = false 5 | mesosVersion = "0.27" 6 | timeout = 60 7 | 8 | agent { 9 | imageName = "containersol/mesos-agent" 10 | imageTag = "# derive from mesos version" 11 | loggingLevel = "# INHERIT FROM CLUSTER" 12 | portNumber = 5051 13 | 14 | resources { 15 | 16 | cpu { 17 | role = "*" 18 | value = 1 19 | } 20 | 21 | disk { 22 | role = "*" 23 | value = 200 24 | } 25 | 26 | mem { 27 | role = "*" 28 | value = 1024 29 | } 30 | 31 | ports { 32 | role = "*" 33 | value = "[4200-4399]" 34 | } 35 | } 36 | } 37 | 38 | agent { 39 | imageName = "containersol/mesos-agent" 40 | imageTag = "# derive from mesos version" 41 | loggingLevel = "# INHERIT FROM CLUSTER" 42 | portNumber = 5051 43 | 44 | resources { 45 | 46 | cpu { 47 | role = "*" 48 | value = 1 49 | } 50 | 51 | disk { 52 | role = "*" 53 | value = 200 54 | } 55 | 56 | mem { 57 | role = "*" 58 | value = 1024 59 | } 60 | 61 | ports { 62 | role = "*" 63 | value = "[4200-4399]" 64 | } 65 | } 66 | } 67 | 68 | agent { 69 | imageName = "containersol/mesos-agent" 70 | imageTag = "# derive from mesos version" 71 | loggingLevel = "# INHERIT FROM CLUSTER" 72 | portNumber = 5051 73 | 74 | resources { 75 | 76 | cpu { 77 | role = "*" 78 | value = 1 79 | } 80 | 81 | disk { 82 | role = "*" 83 | value = 200 84 | } 85 | 86 | mem { 87 | role = "*" 88 | value = 1024 89 | } 90 | 91 | ports { 92 | role = "*" 93 | value = "[4200-4399]" 94 | } 95 | } 96 | } 97 | 98 | master { 99 | imageName = "containersol/mesos-master" 100 | imageTag = "# derive from mesos version" 101 | loggingLevel = "# INHERIT FROM CLUSTER" 102 | } 103 | 104 | marathon { 105 | imageName = "mesosphere/marathon" 106 | imageTag = "v1.1.1" 107 | } 108 | 109 | zookeeper { 110 | imageName = "jplock/zookeeper" 111 | imageTag = "3.4.8" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /devtools/create_tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 4 | # license agreements. See the NOTICE file distributed with this work for 5 | # additional information regarding copyright ownership. Crate licenses 6 | # this file to you under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. You may 8 | # 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, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | # 18 | # However, if you have executed another commercial license agreement 19 | # with Crate these terms will supersede the license and you may use the 20 | # software solely pursuant to the terms of the relevant commercial agreement. 21 | 22 | # Info: Be aware from where you are invoking this script due to relative paths. 23 | # For example, 'gradlew' has to be in the same directory as where you are 24 | # invoking this script from. 25 | # E.g.: ../crate-mesos-framework> ./devtools/create_tag.sh 26 | 27 | # check if everything is committed 28 | CLEAN=`git status -s` 29 | if [ ! -z "$CLEAN" ] 30 | then 31 | echo "Working directory not clean. Please commit all changes before tagging" 32 | echo "Aborting." 33 | exit -1 34 | fi 35 | 36 | echo "Fetching origin..." 37 | git fetch origin > /dev/null 38 | 39 | # get current branc 40 | BRANCH=`git branch | grep "^*" | cut -d " " -f 2` 41 | echo "Current branch is $BRANCH." 42 | 43 | # check if BRANCH == origin/BRANCH 44 | LOCAL_COMMIT=`git show --format="%H" $BRANCH` 45 | ORIGIN_COMMIT=`git show --format="%H" origin/$BRANCH` 46 | 47 | if [ "$LOCAL_COMMIT" != "$ORIGIN_COMMIT" ] 48 | then 49 | echo "Local $BRANCH is not up to date. " 50 | echo "Aborting." 51 | exit -1 52 | fi 53 | 54 | # get the version 55 | echo "Getting version ..." 56 | VERSION=`./gradlew getVersion | grep version | cut -d ":" -f 2 | tr -d ' '` 57 | echo "$VERSION" 58 | 59 | # check if tag to create has already been created 60 | EXISTS=`git tag | grep $VERSION` 61 | 62 | if [ "$VERSION" == "$EXISTS" ] 63 | then 64 | echo "Revision $VERSION already tagged." 65 | echo "Aborting." 66 | exit -1 67 | fi 68 | 69 | # check if VERSION is in head of CHANGES.txt 70 | VERSION_NUMBER=`echo $VERSION | cut -d '-' -f 1` 71 | REV_NOTE=`grep "[0-9/]\{10\} $VERSION_NUMBER" CHANGES.txt` 72 | if [ -z "$REV_NOTE" ] 73 | then 74 | echo "No notes for revision $VERSION found in CHANGES.txt" 75 | echo "Aborting." 76 | exit -1 77 | fi 78 | 79 | echo "Creating tag $VERSION_NUMBER ..." 80 | git tag -a "$VERSION_NUMBER" -m "Tag release for revision $VERSION" 81 | git push --tags 82 | echo "Done." 83 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/CrateMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | import java.io.*; 28 | 29 | public final class CrateMessage implements Serializable { 30 | 31 | private static final long serialVersionUID = 1L; 32 | private static final Logger LOGGER = LoggerFactory.getLogger(CrateMessage.class); 33 | private final Type type; 34 | private final T data; 35 | 36 | public CrateMessage(Type type, T data) { 37 | this.type = type; 38 | this.data = data; 39 | } 40 | 41 | public Type type() { 42 | return type; 43 | } 44 | 45 | public T data() { 46 | return data; 47 | } 48 | 49 | public enum Type { 50 | MESSAGE_MISSING_RESOURCE, 51 | MESSAGE_CLUSTER_SHUTDOWN 52 | } 53 | 54 | @SuppressWarnings("unchecked") 55 | public static CrateMessage fromStream(byte[] value) throws IOException { 56 | if (value.length == 0) { 57 | return null; 58 | } 59 | ByteArrayInputStream in = new ByteArrayInputStream(value); 60 | try (ObjectInputStream objectInputStream = new ObjectInputStream(in)) { 61 | return (CrateMessage) objectInputStream.readObject(); 62 | } catch (ClassNotFoundException e) { 63 | LOGGER.error("Could not deserialize CrateMessage:", e); 64 | } 65 | return null; 66 | } 67 | 68 | public byte[] toStream() { 69 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 70 | try (ObjectOutputStream objOut = new ObjectOutputStream(out)) { 71 | objOut.writeObject(this); 72 | } catch (IOException e){ 73 | LOGGER.error("Could not serialize CrateMessage:", e); 74 | } 75 | return out.toByteArray(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | 6 | # shared configuration 7 | config.vm.box = "bento/centos-7.2" 8 | 9 | # mesos master with zookeeper 10 | config.vm.define "mesos-master" do |host| 11 | host.vm.provider "virtualbox" do |v| 12 | v.memory = 1024 13 | v.cpus = 1 14 | end 15 | host.vm.hostname = "mesos-master" 16 | host.vm.network :private_network, ip: "192.168.10.100" 17 | host.vm.provision "shell", path: "vagrant/provision-common.sh" 18 | host.vm.provision :shell, :inline => <<-SCRIPT 19 | echo "$(ifconfig enp0s8 | grep 'inet ' | awk '{print $2}')" | sudo tee /etc/mesos-master/ip 20 | echo "$(hostname)" | sudo tee /etc/mesos-master/hostname 21 | echo "1" | sudo tee /var/lib/zookeeper/myid 22 | echo "1" | sudo tee /etc/mesos-master/quorum 23 | echo "/var/lib/mesos/master" | sudo tee /etc/mesos-master/work_dir 24 | echo "crate-mesos-demo" | sudo tee /etc/mesos-master/cluster 25 | sudo systemctl disable mesos-slave 26 | sudo systemctl stop mesos-slave 27 | sudo systemctl enable zookeeper 28 | sudo systemctl restart zookeeper 29 | sudo systemctl enable mesos-master 30 | sudo systemctl restart mesos-master 31 | sudo systemctl enable marathon 32 | sudo systemctl restart marathon 33 | SCRIPT 34 | end 35 | 36 | # mesos slaves 37 | (1..3).each do |i| 38 | config.vm.define "mesos-slave-#{i}" do |host| 39 | host.vm.provider "virtualbox" do |v| 40 | v.memory = 2048 41 | v.cpus = 2 42 | end 43 | host.vm.hostname = "mesos-slave-#{i}" 44 | host.vm.network :private_network, ip: "192.168.10.#{100+i}" 45 | host.vm.provision "shell", path: "vagrant/provision-common.sh" 46 | host.vm.provision :shell, :inline => <<-SCRIPT 47 | echo "$(hostname)" | sudo tee /etc/mesos-slave/hostname 48 | echo "$(ifconfig enp0s8 | grep 'inet ' | awk '{print $2}')" | sudo tee /etc/mesos-slave/ip 49 | echo "mesos" | sudo tee /etc/mesos-slave/containerizers 50 | echo "10mins" | sudo tee /etc/mesos-slave/executor_registration_timeout 51 | echo "ports(*):[31000-31099, 31101-32000, 4000-4999]" | sudo tee /etc/mesos-slave/resources 52 | sudo systemctl disable mesos-master 53 | sudo systemctl stop mesos-master 54 | sudo systemctl disable marathon 55 | sudo systemctl stop marathon 56 | sudo systemctl disable zookeeper 57 | sudo systemctl stop zookeeper 58 | sudo systemctl enable mesos-slave 59 | sudo systemctl restart mesos-slave 60 | SCRIPT 61 | end 62 | end 63 | 64 | config.ssh.forward_agent = true 65 | end 66 | -------------------------------------------------------------------------------- /vagrant/VagrantfileMultiMaster: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | 6 | # shared configuration 7 | config.vm.box = "bento/centos-7.2" 8 | 9 | # mesos master with zookeeper 10 | (1..3).each do |i| 11 | config.vm.define "mesos-master-#{i}" do |host| 12 | host.vm.provider "virtualbox" do |v| 13 | v.memory = 1024 14 | v.cpus = 1 15 | end 16 | host.vm.hostname = "mesos-master-#{i}" 17 | host.vm.network :private_network, ip: "192.168.10.#{101-i}" 18 | host.vm.provision "shell", path: "vagrant/provision-common-multi-master.sh" 19 | host.vm.provision :shell, :inline => <<-SCRIPT 20 | echo "$(ifconfig enp0s8 | grep 'inet ' | awk '{print $2}')" | sudo tee /etc/mesos-master/ip 21 | echo "$(hostname)" | sudo tee /etc/mesos-master/hostname 22 | echo "#{i}" | sudo tee /var/lib/zookeeper/myid 23 | echo "2" | sudo tee /etc/mesos-master/quorum 24 | echo "/var/lib/mesos/master" | sudo tee /etc/mesos-master/work_dir 25 | echo "crate-mesos-demo" | sudo tee /etc/mesos-master/cluster 26 | sudo systemctl disable mesos-slave 27 | sudo systemctl stop mesos-slave 28 | sudo systemctl enable zookeeper 29 | sudo systemctl restart zookeeper 30 | sudo systemctl enable mesos-master 31 | sudo systemctl restart mesos-master 32 | sudo systemctl enable marathon 33 | sudo systemctl restart marathon 34 | SCRIPT 35 | end 36 | end 37 | # mesos slaves 38 | (1..3).each do |i| 39 | config.vm.define "mesos-slave-#{i}" do |host| 40 | host.vm.provider "virtualbox" do |v| 41 | v.memory = 2048 42 | v.cpus = 2 43 | end 44 | host.vm.hostname = "mesos-slave-#{i}" 45 | host.vm.network :private_network, ip: "192.168.10.#{100+i}" 46 | host.vm.provision "shell", path: "vagrant/provision-common.sh" 47 | host.vm.provision :shell, :inline => <<-SCRIPT 48 | echo "$(hostname)" | sudo tee /etc/mesos-slave/hostname 49 | echo "$(ifconfig enp0s8 | grep 'inet ' | awk '{print $2}')" | sudo tee /etc/mesos-slave/ip 50 | echo "mesos" | sudo tee /etc/mesos-slave/containerizers 51 | echo "10mins" | sudo tee /etc/mesos-slave/executor_registration_timeout 52 | echo "ports(*):[31000-31099, 31101-32000, 4000-4999]" | sudo tee /etc/mesos-slave/resources 53 | sudo systemctl disable mesos-master 54 | sudo systemctl stop mesos-master 55 | sudo systemctl disable marathon 56 | sudo systemctl stop marathon 57 | sudo systemctl disable zookeeper 58 | sudo systemctl stop zookeeper 59 | sudo systemctl enable mesos-slave 60 | sudo systemctl restart mesos-slave 61 | SCRIPT 62 | end 63 | end 64 | 65 | config.ssh.forward_agent = true 66 | end 67 | -------------------------------------------------------------------------------- /src/test/java/io/crate/frameworks/mesos/CrateStateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import com.google.common.base.Optional; 25 | import org.junit.Test; 26 | 27 | import java.util.HashSet; 28 | 29 | import static org.junit.Assert.*; 30 | 31 | public class CrateStateTest { 32 | 33 | @Test 34 | public void testSerializeCrateState() throws Throwable { 35 | CrateState state = new CrateState(); 36 | state.desiredInstances(42); 37 | state.frameworkId("crate-mesos"); 38 | byte[] bytes = state.toStream(); 39 | 40 | CrateState stateDeserialized = CrateState.fromStream(bytes); 41 | assertEquals(42, (int) stateDeserialized.desiredInstances().getValue()); 42 | assertTrue(stateDeserialized.frameworkId().isPresent()); 43 | assertEquals(Optional.of("crate-mesos"), stateDeserialized.frameworkId()); 44 | assertEquals(0, stateDeserialized.crateInstances().size()); 45 | 46 | } 47 | 48 | @Test 49 | public void testSerializeCrateStateWithInstances() throws Throwable { 50 | CrateState state = new CrateState(); 51 | CrateInstances cluster = new CrateInstances(); 52 | cluster.addInstance(new CrateInstance("127.0.0.1", "1", "0.47.0", 4300, "exec-1", "slave-1")); 53 | cluster.addInstance(new CrateInstance("127.0.0.2", "2", "0.47.0", 4300, "exec-1", "slave-1")); 54 | cluster.setToRunning("1", "id-1"); 55 | state.instances(cluster); 56 | 57 | byte[] bytes = state.toStream(); 58 | 59 | CrateState stateDeserialized = CrateState.fromStream(bytes); 60 | assertEquals(2, stateDeserialized.crateInstances().size()); 61 | 62 | HashSet states = new HashSet<>(2); 63 | for (CrateInstance instance : stateDeserialized.crateInstances()) { 64 | states.add(instance.state()); 65 | } 66 | assertEquals(new HashSet(){ 67 | { 68 | add(CrateInstance.State.RUNNING); 69 | add(CrateInstance.State.PENDING); 70 | } 71 | }, states); 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/config/Resources.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos.config; 23 | 24 | import com.google.common.base.Function; 25 | import com.google.common.collect.*; 26 | import org.apache.mesos.Protos; 27 | 28 | import java.util.List; 29 | 30 | import static com.google.common.collect.Iterables.getOnlyElement; 31 | 32 | public class Resources { 33 | 34 | private static final Function RESOURCE_NAME = new Function() { 35 | @Override 36 | public String apply (Protos.Resource input) { 37 | return input != null ? input.getName() : "Unnamed"; 38 | } 39 | }; 40 | 41 | @SuppressWarnings("RedundantIfStatement") 42 | public static boolean matches(List offeredResources, Configuration configuration) { 43 | ImmutableListMultimap resourceMap = Multimaps.index(offeredResources, RESOURCE_NAME); 44 | 45 | Protos.Resource cpus1 = getOnlyElement(resourceMap.get("cpus")); 46 | if (cpus1.getScalar().getValue() < configuration.resCpus) { 47 | return false; 48 | } 49 | 50 | Protos.Resource mem = getOnlyElement(resourceMap.get("mem")); 51 | 52 | if (mem.getScalar().getValue() < configuration.resMemory) { 53 | return false; 54 | } 55 | 56 | ImmutableList ports = resourceMap.get("ports"); 57 | if(!isPortInRange(configuration.httpPort, ports)) { 58 | return false; 59 | } 60 | if(!isPortInRange(configuration.transportPort, ports)) { 61 | return false; 62 | } 63 | 64 | return true; 65 | } 66 | 67 | private static boolean isPortInRange(int port, List portResources) { 68 | for (Protos.Resource portResource : portResources) { 69 | for (final Protos.Value.Range range : portResource.getRanges().getRangeList()) { 70 | final long begin = range.getBegin(); 71 | final long end = range.getEnd(); 72 | if(port >= begin && port <= end) { 73 | return true; 74 | } 75 | } 76 | } 77 | return false; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/CrateInstance.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import java.io.Serializable; 25 | 26 | public class CrateInstance implements Serializable { 27 | 28 | private final String taskId; 29 | private final String hostname; 30 | private final String version; 31 | private final int transportPort; 32 | private final String executorID; 33 | private final String slaveID; 34 | private String nodeId; // todo: this is never read 35 | private State state; 36 | 37 | public enum State implements Serializable { 38 | PENDING, 39 | RUNNING 40 | } 41 | 42 | public CrateInstance(String hostname, String taskId, String version, int transportPort, 43 | String executorID, String slaveID) { 44 | this.taskId = taskId; 45 | this.hostname = hostname; 46 | this.version = version; 47 | this.transportPort = transportPort; 48 | this.executorID = executorID; 49 | this.slaveID = slaveID; 50 | nodeId = null; 51 | state = State.PENDING; 52 | } 53 | 54 | public String taskId() { 55 | return taskId; 56 | } 57 | 58 | public void nodeId(String nodeId) { 59 | this.nodeId = nodeId; 60 | } 61 | 62 | public State state() { 63 | return state; 64 | } 65 | 66 | public void state(State newState) { 67 | this.state = newState; 68 | } 69 | 70 | public String hostname() { 71 | return hostname; 72 | } 73 | 74 | public String version() { 75 | return version; 76 | } 77 | 78 | public int transportPort() { 79 | return transportPort; 80 | } 81 | 82 | public String executorID() { return executorID; } 83 | 84 | public String slaveID() { return slaveID; } 85 | 86 | public String connectionString() { 87 | return String.format("%s:%d", hostname, transportPort); 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return "CrateInstance{" + 93 | "taskId='" + taskId + '\'' + 94 | ", hostname='" + hostname + '\'' + 95 | ", version='" + version + '\'' + 96 | ", transportPort=" + transportPort + 97 | ", state=" + state + 98 | '}'; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/api/CrateHttpService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos.api; 23 | 24 | import io.crate.frameworks.mesos.PersistentStateStore; 25 | import io.crate.frameworks.mesos.config.Configuration; 26 | import org.glassfish.grizzly.http.server.*; 27 | import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; 28 | import org.glassfish.jersey.server.ResourceConfig; 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | 32 | import javax.ws.rs.core.UriBuilder; 33 | import java.io.File; 34 | import java.io.IOException; 35 | import java.io.UnsupportedEncodingException; 36 | import java.net.URI; 37 | import java.net.URL; 38 | import java.net.URLDecoder; 39 | import java.util.concurrent.TimeUnit; 40 | 41 | public class CrateHttpService { 42 | 43 | private final HttpServer server; 44 | private final static String PACKAGE_NAMESPACE = "io.crate.frameworks.mesos.api"; 45 | private static final Logger LOGGER = LoggerFactory.getLogger(CrateHttpService.class); 46 | 47 | public CrateHttpService(PersistentStateStore crateState, Configuration conf) { 48 | ResourceConfig httpConf = new ResourceConfig() 49 | .register(new CrateRestResource(crateState, conf)) 50 | .packages(PACKAGE_NAMESPACE); 51 | URI httpUri = UriBuilder.fromPath("/").scheme("http").host("0.0.0.0").port(conf.apiPort).build(); 52 | server = GrizzlyHttpServerFactory.createHttpServer(httpUri, httpConf); 53 | server.getServerConfiguration().addHttpHandler( 54 | new StaticHttpHandler(getRoot()), 55 | "/static" 56 | ); 57 | } 58 | 59 | public static URL jarLocation() { 60 | return io.crate.frameworks.mesos.Main.class.getProtectionDomain().getCodeSource().getLocation(); 61 | } 62 | 63 | private static String getRoot() { 64 | String jarPath = null; 65 | try { 66 | jarPath = URLDecoder.decode(jarLocation().getFile(), "UTF-8"); 67 | } catch (UnsupportedEncodingException e) { 68 | LOGGER.error("Could not read root directory path for jar file.", e); 69 | System.exit(2); 70 | } 71 | 72 | return new File(jarPath).getParentFile().getPath(); 73 | } 74 | 75 | public void start() throws IOException { 76 | server.start(); 77 | } 78 | 79 | public void stop() { 80 | server.shutdown(30_000, TimeUnit.MILLISECONDS); 81 | } 82 | 83 | } 84 | 85 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/Version.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | 25 | import io.crate.shade.org.elasticsearch.monitor.jvm.JvmInfo; 26 | 27 | public class Version { 28 | 29 | /** 30 | * The logic for the Version ID is: XXYYZZ, where XX is major version, 31 | * YY is minor version, and ZZ is the revision. 32 | */ 33 | 34 | public static final boolean SNAPSHOT = true; 35 | public static final Version CURRENT = new Version(300, SNAPSHOT); 36 | 37 | public final int id; 38 | public final byte major; 39 | public final byte minor; 40 | public final byte revision; 41 | public final boolean snapshot; 42 | 43 | Version(int id, boolean snapshot) { 44 | this.id = id; 45 | this.major = (byte) ((id / 10000) % 100); 46 | this.minor = (byte) ((id / 100) % 100); 47 | this.revision = (byte) (id % 100); 48 | this.snapshot = snapshot; 49 | } 50 | 51 | public boolean snapshot() { 52 | return snapshot; 53 | } 54 | 55 | public boolean after(Version version) { 56 | return version.id < id; 57 | } 58 | 59 | public boolean before(Version version) { 60 | return version.id > id; 61 | } 62 | 63 | /** 64 | * Just the version number (without -SNAPSHOT if snapshot). 65 | */ 66 | public String number() { 67 | StringBuilder sb = new StringBuilder() 68 | .append(major).append('.') 69 | .append(minor).append('.') 70 | .append(revision); 71 | return sb.toString(); 72 | } 73 | 74 | public static void main(String[] args) { 75 | System.out.println("Version: " + Version.CURRENT + ", JVM: " + JvmInfo.jvmInfo().version() ); 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | StringBuilder sb = new StringBuilder().append(number()); 81 | if (snapshot) { 82 | sb.append("-SNAPSHOT"); 83 | } 84 | return sb.toString(); 85 | } 86 | 87 | @Override 88 | public boolean equals(Object o) { 89 | if (this == o) return true; 90 | if (o == null || getClass() != o.getClass()) return false; 91 | 92 | Version version = (Version) o; 93 | 94 | if (id != version.id) return false; 95 | 96 | return true; 97 | } 98 | 99 | @Override 100 | public int hashCode() { 101 | return id; 102 | } 103 | 104 | } 105 | 106 | -------------------------------------------------------------------------------- /src/test/java/io/crate/frameworks/mesos/api/CrateHttpServiceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos.api; 23 | 24 | import io.crate.frameworks.mesos.CrateState; 25 | import io.crate.frameworks.mesos.PersistentStateStore; 26 | import io.crate.frameworks.mesos.config.Configuration; 27 | import org.junit.After; 28 | import org.junit.Before; 29 | import org.junit.Test; 30 | 31 | import javax.ws.rs.client.Client; 32 | import javax.ws.rs.client.ClientBuilder; 33 | import javax.ws.rs.client.Entity; 34 | import javax.ws.rs.core.Response; 35 | import javax.ws.rs.core.UriBuilder; 36 | 37 | import java.net.URI; 38 | 39 | import static org.junit.Assert.*; 40 | import static org.mockito.Mockito.mock; 41 | import static org.mockito.Mockito.when; 42 | 43 | public class CrateHttpServiceTest { 44 | 45 | public static final int TEST_SERVER_PORT = 40401; 46 | 47 | private CrateHttpService service; 48 | private Client client; 49 | 50 | @Before 51 | public void setUp() throws Exception { 52 | PersistentStateStore store = mock(PersistentStateStore.class); 53 | CrateState state = new CrateState(); 54 | when(store.state()).thenReturn(state); 55 | Configuration conf = new Configuration(); 56 | conf.apiPort = TEST_SERVER_PORT; 57 | service = new CrateHttpService(store, conf); 58 | service.start(); 59 | client = ClientBuilder.newClient(); 60 | } 61 | 62 | @After 63 | public void tearDown() throws Exception { 64 | service.stop(); 65 | } 66 | 67 | private UriBuilder newRequestBuilder(String path) { 68 | return UriBuilder.fromPath(path).host("localhost").port(TEST_SERVER_PORT).scheme("http"); 69 | 70 | } 71 | 72 | @Test 73 | public void testClusterInfo() throws Exception { 74 | URI uri = newRequestBuilder("/cluster").build(); 75 | Response res = client.target(uri).request().get(); 76 | assertEquals(200, res.getStatus()); 77 | } 78 | 79 | @Test 80 | public void testClusterResize() throws Exception { 81 | URI uri = newRequestBuilder("/cluster/resize").build(); 82 | Response res = client.target(uri).request().post(Entity.text("{\"instances\":1}")); 83 | assertEquals(415, res.getStatus()); 84 | res = client.target(uri).request().post(Entity.json("{\"instances\":1}")); 85 | assertEquals(200, res.getStatus()); 86 | } 87 | 88 | @Test 89 | public void testStaticHandler() throws Throwable { 90 | URI uri = newRequestBuilder("/static/").build(); 91 | Response res = client.target(uri).request().get(); 92 | assertEquals(404, res.getStatus()); 93 | // download path cannot be tested because tests are not running from jar file 94 | } 95 | } -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/PersistentStateStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import org.apache.mesos.state.Variable; 25 | import org.apache.mesos.state.ZooKeeperState; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | import java.io.IOException; 30 | import java.util.concurrent.ExecutionException; 31 | import java.util.concurrent.Future; 32 | 33 | public class PersistentStateStore { 34 | 35 | private static final Logger LOGGER = LoggerFactory.getLogger(PersistentStateStore.class); 36 | private static final String CRATE_STATE = "crate"; 37 | 38 | private final ZooKeeperState zk; 39 | private final CrateState state; 40 | 41 | private final Future zkFuture; 42 | private Variable stateVariable = null; 43 | 44 | 45 | public PersistentStateStore(ZooKeeperState zk, int desiredInstances) { 46 | this.zk = zk; 47 | this.zkFuture = zk.fetch(CRATE_STATE); 48 | this.state = restore(); 49 | int nodeCount = state.desiredInstances().getValue() == CrateState.UNDEFINED_DESIRED_INSTANCES ? 50 | desiredInstances : 51 | state.desiredInstances().getValue(); 52 | desiredInstances(nodeCount); 53 | } 54 | 55 | public CrateState state() { 56 | return state; 57 | } 58 | 59 | public synchronized void save() { 60 | stateVariable = stateVariable.mutate(state.toStream()); 61 | try { 62 | stateVariable = zk.store(stateVariable).get(); 63 | if (stateVariable == null) { 64 | LOGGER.error("Couldn't save state in Zookeeper"); 65 | } 66 | } catch (InterruptedException | ExecutionException e) { 67 | e.printStackTrace(); 68 | } 69 | } 70 | 71 | private CrateState restore() { 72 | try { 73 | stateVariable = zkFuture.get(); 74 | } catch (ExecutionException | InterruptedException e) { 75 | e.printStackTrace(); 76 | LOGGER.error(e.getMessage()); 77 | } 78 | if (stateVariable != null) { 79 | try { 80 | CrateState st = CrateState.fromStream(stateVariable.value()); 81 | LOGGER.info("Restored state from Zookeeper: {}", st); 82 | LOGGER.debug(st.toString()); 83 | return st; 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | LOGGER.error(e.getMessage()); 87 | } 88 | } 89 | return new CrateState(); 90 | } 91 | 92 | public synchronized void desiredInstances(int instances) { 93 | // set state 94 | state.desiredInstances(instances); 95 | save(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/io/crate/frameworks/mesos/MainTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import com.beust.jcommander.ParameterException; 25 | import io.crate.frameworks.mesos.config.Configuration; 26 | import org.junit.Rule; 27 | import org.junit.Test; 28 | import org.junit.rules.ExpectedException; 29 | 30 | import static org.hamcrest.MatcherAssert.assertThat; 31 | import static org.hamcrest.Matchers.is; 32 | 33 | public class MainTest { 34 | 35 | @Rule 36 | public ExpectedException expectedException = ExpectedException.none(); 37 | 38 | @Test 39 | public void testParseConfigurationWithProtectedArg() throws Exception { 40 | expectedException.expect(IllegalArgumentException.class); 41 | expectedException.expectMessage( 42 | "Argument \"-Des.cluster.name\" is protected and managed by the framework. It cannot be set by the user"); 43 | Main.parseConfiguration(new String[]{"--crate-version", "0.47.0", "-Des.cluster.name=foo"}); 44 | } 45 | 46 | @Test 47 | public void testVersionByVersionNumber() throws Exception { 48 | Configuration configuration = Main.parseConfiguration(new String[]{ 49 | "--crate-version", "0.48.2" 50 | }); 51 | assertThat(configuration.version, is("0.48.2")); 52 | } 53 | 54 | @Test 55 | public void testVersionByInvalidVersionNumber() throws Exception { 56 | expectedException.expect(ParameterException.class); 57 | expectedException.expectMessage("The specified Crate version \"0.48.x\" isn't a valid version or download location"); 58 | Main.parseConfiguration(new String[]{ 59 | "--crate-version", "0.48.x" 60 | }); 61 | } 62 | 63 | @Test 64 | public void testVersionByURL() throws Exception { 65 | String downloadLocation = "https://cdn.crate.io/downloads/releases/nightly/crate-0.39.0-201406050908-74848fd.tar.gz"; 66 | Configuration configuration = Main.parseConfiguration(new String[]{ 67 | "--crate-version", downloadLocation 68 | }); 69 | assertThat(configuration.version, is(downloadLocation)); 70 | downloadLocation = "https://cdn.crate.io/downloads/releases/dev/crate-0.49.0-SNAPSHOT.tar.gz"; 71 | configuration = Main.parseConfiguration(new String[]{ 72 | "--crate-version", downloadLocation 73 | }); 74 | assertThat(configuration.version, is(downloadLocation)); 75 | } 76 | 77 | @Test 78 | public void testVersionByInvalidURL() throws Exception { 79 | expectedException.expect(ParameterException.class); 80 | expectedException.expectMessage("The specified Crate version \"http://cdn.crate.io/downloads/releases/download.tar.gz\" isn't a valid version or download location."); 81 | Main.parseConfiguration(new String[]{ 82 | "--crate-version", "http://cdn.crate.io/downloads/releases/download.tar.gz" 83 | }); 84 | } 85 | @Test 86 | public void testValidCrateArgsAreSet() throws Exception { 87 | Configuration configuration = Main.parseConfiguration(new String[]{ 88 | "--crate-version", "0.47.0", 89 | "-Des.cluster.routing.allocation.awareness.attributes=mesos_zone" 90 | }); 91 | assertThat(configuration.crateArgs().get(0), 92 | is("-Des.cluster.routing.allocation.awareness.attributes=mesos_zone")); 93 | } 94 | } -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/CrateInstances.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import com.google.common.base.Joiner; 25 | import com.google.common.base.Predicate; 26 | import com.google.common.collect.Iterables; 27 | 28 | import java.io.Serializable; 29 | import java.util.*; 30 | 31 | public class CrateInstances implements Serializable, Iterable { 32 | 33 | private final ArrayList instances = new ArrayList<>(); 34 | private HashSet hosts; 35 | 36 | public int size() { 37 | return instances.size(); 38 | } 39 | 40 | public boolean anyOnHost(final String hostname) { 41 | return hosts().contains(hostname); 42 | } 43 | 44 | public String unicastHosts() { 45 | List hosts = new ArrayList<>(instances.size()); 46 | for (CrateInstance instance : instances) { 47 | hosts.add(instance.connectionString()); 48 | } 49 | return Joiner.on(",").join(hosts); 50 | } 51 | 52 | public String[] connectionHosts() { 53 | List hosts = new ArrayList<>(); 54 | for (CrateInstance instance : runningInstances()) { 55 | hosts.add(instance.connectionString()); 56 | } 57 | return hosts.toArray(new String[hosts.size()]); 58 | } 59 | 60 | public static int calculateQuorum(int expectedNodes) { 61 | return (int) Math.ceil((expectedNodes + 1.0f) / 2.0f); 62 | } 63 | 64 | public Set runningInstances() { 65 | HashSet running = new HashSet<>(); 66 | for (CrateInstance instance : instances) { 67 | if (instance.state() == CrateInstance.State.RUNNING) { 68 | running.add(instance); 69 | } 70 | } 71 | return running; 72 | } 73 | 74 | public Set hosts() { 75 | // TODO: probably just expose hosts as iterable and avoid the internal set... 76 | if (hosts == null) { 77 | hosts = new HashSet<>(instances.size()); 78 | for (CrateInstance instance : instances) { 79 | hosts.add(instance.hostname()); 80 | } 81 | } 82 | return hosts; 83 | } 84 | 85 | public void addInstance(CrateInstance crateInstance) { 86 | instances.add(crateInstance); 87 | if (hosts != null) { 88 | hosts.add(crateInstance.hostname()); 89 | } 90 | } 91 | 92 | public void setToRunning(String taskId, String nodeId) { 93 | for (CrateInstance instance : instances) { 94 | if (instance.taskId().equals(taskId)) { 95 | instance.state(CrateInstance.State.RUNNING); 96 | instance.nodeId(nodeId); 97 | } 98 | } 99 | } 100 | 101 | public void removeTask(String taskId) { 102 | for (int i = instances.size() -1; i >= 0; i--) { 103 | CrateInstance crateInstance = instances.get(i); 104 | if (crateInstance.taskId().equals(taskId)) { 105 | instances.remove(i); 106 | if (hosts != null) { 107 | hosts.remove(crateInstance.hostname()); 108 | } 109 | } 110 | } 111 | } 112 | 113 | @Override 114 | public Iterator iterator() { 115 | return instances.iterator(); 116 | } 117 | 118 | public CrateInstance get(int index) { 119 | return instances.get(index); 120 | } 121 | 122 | public CrateInstance byTaskId(final String taskId) { 123 | return Iterables.find(this, new Predicate() { 124 | @Override 125 | public boolean apply(CrateInstance input) { 126 | return input.taskId().equals(taskId); 127 | } 128 | }); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/test/java/io/crate/frameworks/mesos/CrateInstancesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import org.junit.Test; 25 | 26 | import java.util.HashSet; 27 | 28 | import static org.junit.Assert.*; 29 | 30 | 31 | public class CrateInstancesTest { 32 | 33 | private static CrateInstance newInstance(String hostname, String taskId) { 34 | return new CrateInstance(hostname, taskId, "0.47.0", 4300, "exec-1", "slave-1"); 35 | } 36 | 37 | @Test 38 | public void testSize() throws Exception { 39 | CrateInstances cluster = new CrateInstances(); 40 | assertEquals(0, cluster.size()); 41 | 42 | cluster.addInstance(newInstance("localhost", "1")); 43 | assertEquals(1, cluster.size()); 44 | } 45 | 46 | @Test 47 | public void testAnyOnHost() throws Exception { 48 | CrateInstances cluster = new CrateInstances(); 49 | cluster.addInstance(newInstance("127.0.0.1", "1")); 50 | cluster.addInstance(newInstance("127.0.0.2", "2")); 51 | assertTrue(cluster.anyOnHost("127.0.0.1")); 52 | assertFalse(cluster.anyOnHost("127.0.0.3")); 53 | } 54 | 55 | @Test 56 | public void testHosts() throws Exception { 57 | CrateInstances cluster = new CrateInstances(); 58 | cluster.addInstance(newInstance("127.0.0.1", "1")); 59 | cluster.addInstance(newInstance("127.0.0.2", "2")); 60 | assertEquals(new HashSet(){ 61 | { 62 | add("127.0.0.1"); 63 | add("127.0.0.2"); 64 | } 65 | }, cluster.hosts()); 66 | } 67 | 68 | @Test 69 | public void testSetToRunning() throws Exception { 70 | CrateInstances cluster = new CrateInstances(); 71 | cluster.addInstance(newInstance("127.0.0.1", "1")); 72 | cluster.addInstance(newInstance("127.0.0.2", "2")); 73 | 74 | cluster.setToRunning("1", "id-1"); 75 | 76 | HashSet states = new HashSet<>(2); 77 | for (CrateInstance instance : cluster) { 78 | states.add(instance.state()); 79 | } 80 | assertEquals(new HashSet(){ 81 | { 82 | add(CrateInstance.State.RUNNING); 83 | add(CrateInstance.State.PENDING); 84 | } 85 | }, states); 86 | 87 | } 88 | 89 | @Test 90 | public void testRemoveTask() throws Exception { 91 | CrateInstances cluster = new CrateInstances(); 92 | cluster.addInstance(newInstance("127.0.0.1", "1")); 93 | cluster.addInstance(newInstance("127.0.0.2", "2")); 94 | cluster.removeTask("1"); 95 | assertEquals(1, cluster.size()); 96 | assertFalse(cluster.anyOnHost("127.0.0.1")); 97 | assertTrue(cluster.anyOnHost("127.0.0.2")); 98 | cluster.removeTask("2"); 99 | assertEquals(new HashSet(), cluster.hosts()); 100 | } 101 | 102 | @Test 103 | public void testCalculateQuorum() throws Exception { 104 | assertEquals(1, CrateInstances.calculateQuorum(1)); 105 | assertEquals(2, CrateInstances.calculateQuorum(2)); 106 | assertEquals(2, CrateInstances.calculateQuorum(3)); 107 | assertEquals(3, CrateInstances.calculateQuorum(4)); 108 | assertEquals(3, CrateInstances.calculateQuorum(5)); 109 | assertEquals(4, CrateInstances.calculateQuorum(6)); 110 | assertEquals(4, CrateInstances.calculateQuorum(7)); 111 | assertEquals(5, CrateInstances.calculateQuorum(8)); 112 | assertEquals(5, CrateInstances.calculateQuorum(9)); 113 | assertEquals(6, CrateInstances.calculateQuorum(10)); 114 | // the quorum must be greater than half of the expected cluster size 115 | for (int i = 0; i < 1000; i++) { 116 | assertTrue(CrateInstances.calculateQuorum(i) > i / 2.0f); 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | CrateDB Mesos Framework 3 | ======================= 4 | 5 | .. image:: https://travis-ci.org/crate/crate-mesos-framework.svg?branch=master 6 | :target: https://travis-ci.org/crate/crate-mesos-framework 7 | 8 | .. image:: https://img.shields.io/badge/license-Apache%202-blue.svg 9 | :target: https://raw.githubusercontent.com/crate/crate-mesos-framework/master/LICENSE 10 | 11 | | 12 | 13 | An integration framework that allows you to run and manage CrateDB_ via `Apache 14 | Mesos`_. 15 | 16 | **This project is currently unmaintained. Contributions welcome. See below.** 17 | 18 | *Note: this framework is experimental and not suitable for production use. 19 | Future changes in the API might break older installations!* 20 | 21 | Prerequisites 22 | ============= 23 | 24 | A JDK needs to be installed. 25 | 26 | On OS X, we recommend using `Oracle's Java`_. If you're using Linux, we 27 | recommend OpenJDK_. 28 | 29 | We recommend you use a recent Java 8 version. 30 | 31 | Vagrant_ is also used. 32 | 33 | Setup and Building 34 | ================== 35 | 36 | Clone the repository:: 37 | 38 | $ git clone https://github.com/crate/crate-mesos-framework.git 39 | 40 | Build the JAR file:: 41 | 42 | $ ./gradlew fatJar 43 | 44 | The JAR file can then be found in the ``build/libs/`` directory. 45 | 46 | This JAR file cannot be run directly as it requires a Mesos master instance and 47 | the Mesos native libraries. 48 | 49 | Launching 50 | ========= 51 | 52 | This project ships with a Vagrantfile that can be used with Vagrant to launch 53 | *virtual machines* (VM) with Mesos installed. 54 | 55 | Launch the VMs like so:: 56 | 57 | $ vagrant up 58 | 59 | This will create and provision four VMs: 60 | 61 | - ``mesos-master`` 62 | The Mesos master instance along with Zookeeper 63 | - ``mesos-slave-{1..3}`` 64 | The Mesos slaves 65 | 66 | If you have run ``vagrant up`` before, Vagrant boots the existing VMs. 67 | 68 | Once the VMs are up-and-running, the CrateDB Mesos framework can be started 69 | inside the master VM. You can do that like so:: 70 | 71 | $ vagrant ssh -c "java -Djava.library.path=/usr/local/lib -jar /vagrant/build/libs/crate-mesos-*.jar --crate-version 0.54.9 --zookeeper 192.168.10.100:2181" 72 | 73 | Inside the VM, ``/vagrant`` is mapped to the project root. This way, the JAR 74 | file can be accessed. 75 | 76 | Hosts Entries 77 | ------------- 78 | 79 | The static IPs of the Vagrant VMs are ``192.168.10.100`` for the master and 80 | ``192.168.10.{101..103}`` for the slaves. 81 | 82 | You can add them to your ``/etc/hosts`` file, like so:: 83 | 84 | 192.168.10.100 mesos-master 85 | 192.168.10.101 mesos-slave-1 86 | 192.168.10.102 mesos-slave-2 87 | 192.168.10.103 mesos-slave-3 88 | 89 | The Mesos WebUI should be available under http://mesos-master:5050 immediately 90 | after ``vagrant up`` is finished. 91 | 92 | Once the CrateDB Mesos framework has been launched, the framework API becomes 93 | available at http://mesos-master:4040/cluster (if an API port is not otherwise 94 | specified). 95 | 96 | Shortcut 97 | -------- 98 | 99 | You can re-build the JAR file and re-start the framework with this shortcut 100 | command:: 101 | 102 | $ bin/deploy --crate-version 0.47.7 --zookeeper 192.168.10.100:2181 103 | 104 | Running via Marathon 105 | ==================== 106 | 107 | One of the easiest ways to run CrateDB Mesos framework is via Marathon_, on 108 | something like a DCOS_ cluster. 109 | 110 | For installing Marathon, please refer to `Mesosphere install guide`_. The 111 | Marathon WebUI should be available under http://mesos-master:8080 after setting 112 | things up. 113 | 114 | Modify the template `marathon/local.json`_ file to suit your purposes and then 115 | submit the file to Marathon, like so:: 116 | 117 | $ curl -s -XPOST http://mesos-master:8080/v2/apps -d@marathon/local.json -H "Content-Type: application/json" 118 | 119 | Once deployed, you can use the framework API_ to launch a CrateDB cluster. To do 120 | so, execute the ``resize`` command:: 121 | 122 | $ curl -sXPOST -H "Content-Type: application/json" :4040/cluster/resize -d '{"instances": }' 123 | 124 | Here, ```` is the hostname or IP of the host the framework is 125 | scheduled on, and ```` is the desired number of CrateDB nodes. 126 | 127 | Contributing 128 | ============ 129 | 130 | This project is primarily maintained by Crate.io_, but we welcome community 131 | contributions! 132 | 133 | See the `developer docs`_ and the `contribution docs`_ for more information. 134 | 135 | Help 136 | ==== 137 | 138 | Looking for more help? 139 | 140 | - Read the `project docs`_ 141 | - Check out our `support channels`_ 142 | 143 | .. _`Mesosphere install guide`: http://mesosphere.com/docs/getting-started/datacenter/install/ 144 | .. _Apache Mesos: http://mesos.apache.org 145 | .. _API: https://crate.io/docs/reference/mesos-framework/en/latest/api.html 146 | .. _contribution docs: CONTRIBUTING.rst 147 | .. _Crate.io: http://crate.io/ 148 | .. _CrateDB: https://crate.io 149 | .. _DCOS: https://dcos.io 150 | .. _developer docs: DEVELOP.rst 151 | .. _Gradle: http://www.gradle.org/ 152 | .. _Marathon: https://mesosphere.github.io/marathon/ 153 | .. _marathon/local.json: marathon/local.json 154 | .. _OpenJDK: http://openjdk.java.net/projects/jdk8/ 155 | .. _Oracle's Java: http://www.java.com/en/download/help/mac_install.xml 156 | .. _project doc: https://github.com/crate/crate-mesos-framework/tree/master/docs 157 | .. _support channels: https://crate.io/support/ 158 | .. _Vagrant: https://www.vagrantup.com/ 159 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/CrateState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import com.google.common.base.Optional; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | import java.io.*; 29 | import java.util.*; 30 | 31 | public class CrateState implements Serializable { 32 | 33 | private static final Logger LOGGER = LoggerFactory.getLogger(CrateState.class); 34 | 35 | private Observable desiredInstances = new Observable<>(UNDEFINED_DESIRED_INSTANCES); 36 | private String frameworkId = null; 37 | private CrateInstances crateInstances = new CrateInstances(); 38 | private HashMap> excludedSlaves = new HashMap<>(); 39 | private Set slavesWithInstance = new HashSet<>(); 40 | 41 | private static final long serialVersionUID = 1L; 42 | 43 | public static final int UNDEFINED_DESIRED_INSTANCES = -1; 44 | 45 | 46 | public static CrateState fromStream(byte[] value) throws IOException { 47 | if (value.length == 0) { 48 | return new CrateState(); 49 | } 50 | ByteArrayInputStream in = new ByteArrayInputStream(value); 51 | try (ObjectInputStream objectInputStream = new ObjectInputStream(in)) { 52 | return (CrateState) objectInputStream.readObject(); 53 | } catch (ClassNotFoundException e) { 54 | LOGGER.error("Could not deserialize ClusterState:", e); 55 | } 56 | return null; 57 | } 58 | 59 | public byte[] toStream() { 60 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 61 | try (ObjectOutputStream objOut = new ObjectOutputStream(out)) { 62 | objOut.writeObject(this); 63 | } catch (IOException e){ 64 | LOGGER.error("Could not serialize ClusterState:", e); 65 | } 66 | return out.toByteArray(); 67 | } 68 | 69 | public CrateInstances crateInstances() { 70 | return crateInstances; 71 | } 72 | 73 | public void instances(CrateInstances activeOrPendingInstances) { 74 | this.crateInstances = activeOrPendingInstances; 75 | } 76 | 77 | public Observable desiredInstances() { 78 | return desiredInstances; 79 | } 80 | 81 | public void desiredInstances(int instances) { 82 | desiredInstances.setValue(instances); 83 | } 84 | 85 | public void frameworkId(String frameworkId) { 86 | this.frameworkId = frameworkId; 87 | } 88 | 89 | public Optional frameworkId() { 90 | return Optional.fromNullable(frameworkId); 91 | } 92 | 93 | public int missingInstances() { 94 | return desiredInstances().getValue() - crateInstances().size(); 95 | } 96 | 97 | public boolean addSlaveIdToExcludeList(String reason, String slaveId) { 98 | if (!excludedSlaves.containsKey(reason)) { 99 | excludedSlaves.put(reason, new ArrayList()); 100 | } 101 | return excludedSlaves.get(reason).add(slaveId); 102 | } 103 | 104 | public void removeSlaveIdFromExcludeList(String slaveId) { 105 | for( Map.Entry> slaves : excludedSlaves.entrySet()) { 106 | slaves.getValue().remove(slaveId); 107 | } 108 | } 109 | public boolean removeSlaveIdFromExcludeList(String reason, String slaveId) { 110 | if (!excludedSlaves.containsKey(reason)) { 111 | return false; 112 | } 113 | return excludedSlaves.get(reason).remove(slaveId); 114 | } 115 | 116 | public List excludedSlaveIds(String reason){ 117 | if (!excludedSlaves.containsKey(reason)) { 118 | return Collections.emptyList(); 119 | } 120 | return excludedSlaves.get(reason); 121 | } 122 | 123 | public List excludedSlaveIds() { 124 | if (excludedSlaves.size() == 0) return Collections.emptyList(); 125 | List allIds = new ArrayList<>(); 126 | 127 | for( Map.Entry> slaves : excludedSlaves.entrySet()) { 128 | allIds.addAll(slaves.getValue()); 129 | } 130 | return allIds; 131 | 132 | } 133 | 134 | public Set slavesWithInstances() { 135 | return slavesWithInstance; 136 | } 137 | 138 | public HashMap> excludedSlaves () { 139 | return excludedSlaves; 140 | } 141 | 142 | @Override 143 | public String toString() { 144 | return String.format("%s { frameworkId=%s, crateInstances=%d, desiredInstances=%d }", 145 | this.getClass().getName(), frameworkId, crateInstances.size(), desiredInstances.getValue()); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | .. rubric:: Table of Contents 6 | 7 | .. contents:: 8 | :local: 9 | 10 | Build 11 | ===== 12 | 13 | First, build the jar file from source:: 14 | 15 | ./gradlew fatJar 16 | 17 | You can deploy the framework in two ways: 18 | 19 | 1. Executing the jar file from the command line. 20 | 2. Launch it via Marathon. 21 | 22 | If you deploy the framework using Marathon, copy the jar file to the 23 | mesos-slaves. Otherwise copy it to the mesos-master from where it is launched 24 | using the command line. 25 | 26 | In both cases the ``Main`` method requires a ``--crate-version`` argument, 27 | which is the Crate version to use. The version number must be in the format 28 | ``x.y.z``. The cuurrent stable Crate version is ``0.54.9``. 29 | 30 | Alternatively you can specify a full download URL for Crate. In this case the 31 | URL needs to be in the format: 32 | 33 | ``http(s):///crate-...tar.gz`` 34 | 35 | 36 | Resources 37 | ========= 38 | 39 | After setting up a Mesos cluster, your Mesos slaves need to provide resources 40 | that are required by Crate and the Crate Framework, mainly paths and ports. 41 | 42 | User/Role 43 | --------- 44 | 45 | The Crate Framework runs as user ``crate`` and role ``*`` by default. This is 46 | configurable using ``--framework-user`` and ``--framework-role``. 47 | 48 | This means that user ``crate`` (or other specified user) is required to be 49 | present on all instances, both master and slaves. The user does not need to 50 | have any specific permissions. You can add a user with:: 51 | 52 | useradd crate -s /bin/bash 53 | 54 | If you specify a role different to the default ``"*"`` you need to add it the 55 | mesos-master configuration, e.g.:: 56 | 57 | echo "crate" > /etc/mesos-master/roles 58 | 59 | Data Path 60 | --------- 61 | 62 | If you are using :ref:`persistent_data_paths` (which is recommended), you need 63 | to make sure that the user ``crate`` has **write** permissions at these 64 | locations. For example:: 65 | 66 | chown crate:crate /path/to/persistent/disk 67 | 68 | Ports 69 | ----- 70 | 71 | Crate uses ports ``4200`` and ``4300`` by default. To receive offers you need to 72 | add the resource reservation for a port range that includes these ports by 73 | writing it into the resources file:: 74 | 75 | echo 'ports(*)[31000-31099, 31101-32000, 4000-4999]' > 76 | /etc/mesos-slave/resources 77 | 78 | or starting the slave with the option:: 79 | 80 | --resources=ports(*)[31000-31099, 31101-32000, 4000-4999] 81 | 82 | Then restart the slave and clean the old slave state if necessary 83 | (``rm -f /tmp/mesos/meta/slaves/latest``). 84 | 85 | The ports can be configured on startup of the Framework, which means that you 86 | need adapt the resource port range according to your configured ports. 87 | 88 | 89 | Open Files 90 | ========== 91 | 92 | Depending on the size of your installation, Crate can open a lot of files. 93 | You can check the number of open files with ``ulimit -n``, but it can depend 94 | on your host operating system. Increasing this limit is dependent on your 95 | operating system. 96 | 97 | For instance, in RHEL6.x, you can place a ``crate.conf`` file containing ``crate 98 | soft nofile 65535`` and ``crate hard nofile 65535`` under 99 | ``/etc/security/limit.d``. This will set limit of number of open files 100 | for user ``crate`` to 65535. 101 | 102 | It's recommended to set the memlock limit (the maximum locked-in-memory address 103 | space) to unlimited. You can do this by adding ``crate hard memlock unlimited`` 104 | and ``crate soft memlock unlimited`` to the ``crate.conf`` file mentioned 105 | above. 106 | 107 | 108 | Local Installation 109 | ================== 110 | 111 | This repository provides a ``Vagrantfile`` you can use for a simple Mesos 112 | cluster installation for local deployment and testing. 113 | 114 | The script will launch a 4 node Mesos cluster, 1 master and 3 slaves. For full 115 | instructions on how to set up the local cluster (including changes to your 116 | ``/etc/hosts`` file), refer to ``DEVELOP.rst`` in the root of the repository_. 117 | 118 | 119 | Execute via Command Line 120 | ------------------------ 121 | 122 | :: 123 | 124 | java -Djava.library.path=/usr/local/lib -jar /path/to/crate-mesos-0.x.x.jar 125 | --crate-version 0.x.x [OPTIONS]" 126 | 127 | 128 | Launch via Marathon 129 | ------------------- 130 | 131 | Create a Marathon configuration file:: 132 | 133 | { 134 | "id": "crate-dev", 135 | "instances": 1, 136 | "cpus": 0.5, 137 | "mem": 256, 138 | "ports": [0], 139 | "uris": [], 140 | "env": {}, 141 | "cmd": "java -Djava.library.path=/usr/local/lib -jar 142 | /path/to/crate-mesos-0.x.x.jar --crate-version 0.x.x [OPTIONS]" 143 | } 144 | 145 | For these options to work ``java`` needs to be available on the mesos-slave. If 146 | ``java`` isn't available it can be included as dependency in the Marathon 147 | configuration file by listing it in ``uris`` changing the ``cmd``:: 148 | 149 | "uris": [ 150 | "https://downloads.mesosphere.io/java/jre-7u76-linux-x64.tar.gz" 151 | ], 152 | "cmd": "$(pwd)/jre*/bin/java $JAVA_OPTS -jar /path/to/crate-mesos-0.x.x.jar 153 | --crate-version 0.47.7", 154 | 155 | 156 | And submit it to a running Marathon master:: 157 | 158 | curl -s -XPOST http://localhost:8080/v2/apps -d@CrateFramework.json -H 159 | "Content-Type: application/json" 160 | 161 | 162 | There's a template file for ``marathon.json`` under the marathon directory. You 163 | can copy it with ``cp marathon/marathon.json.template marathon/marathon.json`` 164 | and changing the necessary parameters 165 | 166 | 167 | .. _repository: https://github.com/crate/crate-mesos-framework 168 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /src/test/java/io/crate/frameworks/mesos/CrateExecutableInfoTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import com.google.common.collect.ImmutableList; 25 | import io.crate.frameworks.mesos.config.Configuration; 26 | import org.apache.mesos.Protos; 27 | import org.hamcrest.Matchers; 28 | import org.junit.Test; 29 | 30 | import java.util.Arrays; 31 | import java.util.List; 32 | 33 | import static org.hamcrest.MatcherAssert.assertThat; 34 | 35 | public class CrateExecutableInfoTest { 36 | 37 | @Test 38 | public void testSerializeCrateExecutableInfo() throws Throwable { 39 | Configuration configuration = new Configuration(); 40 | configuration.clusterName = "foo"; 41 | configuration.dataPath = "/mnt/ssd1/crate"; 42 | configuration.blobPath= "/mnt/data1/crate"; 43 | CrateInstances instances = new CrateInstances(); 44 | instances.addInstance(new CrateInstance("host1", "1", "0.48.0", 4300, "exec-1", "slave-1")); 45 | List attr = ImmutableList.of( 46 | Protos.Attribute.newBuilder() 47 | .setType(Protos.Value.Type.TEXT) 48 | .setName("zone") 49 | .setText(Protos.Value.Text.newBuilder().setValue("a").build()) 50 | .build() 51 | ); 52 | CrateExecutableInfo info = new CrateExecutableInfo(configuration, "host1", instances, attr, 1); 53 | byte[] serializedInfo = info.toStream(); 54 | CrateExecutableInfo newInfo = CrateExecutableInfo.fromStream(serializedInfo); 55 | Protos.Environment.Variable heap = Protos.Environment.Variable.newBuilder() 56 | .setName("CRATE_HEAP_SIZE") 57 | .setValue(String.format("%sm", configuration.resHeap.longValue())) 58 | .build(); 59 | assertThat(newInfo.environment(), Matchers.hasItem(heap)); 60 | assertThat(newInfo.arguments(), Matchers.hasItem("-Des.cluster.name=foo")); 61 | assertThat(newInfo.arguments(), Matchers.hasItem("-Des.path.data=/mnt/ssd1/crate")); 62 | assertThat(newInfo.arguments(), Matchers.hasItem("-Des.path.blobs=/mnt/data1/crate")); 63 | assertThat(newInfo.arguments(), Matchers.hasItem("-Des.node.mesos_zone=a")); 64 | } 65 | 66 | @Test 67 | public void testTransportPortIsSetCorrectly() throws Exception { 68 | Configuration configuration = new Configuration(); 69 | configuration.transportPort = 4250; 70 | 71 | CrateInstances instances = new CrateInstances(); 72 | instances.addInstance(new CrateInstance("runningHost", "1", "0.47.7", 4350, "exec-1", "slave-1")); 73 | CrateExecutableInfo host1 = new CrateExecutableInfo(configuration, "host1", instances, 74 | ImmutableList.of(), 1); 75 | List args = host1.arguments(); 76 | 77 | assertThat(args, Matchers.hasItem("-Des.transport.tcp.port=4250")); 78 | assertThat(args, Matchers.hasItem("-Des.discovery.zen.ping.unicast.hosts=runningHost:4350")); 79 | } 80 | 81 | @Test 82 | public void testClusterSettingsAreSetCorrectly() throws Exception { 83 | Configuration configuration = new Configuration(); 84 | 85 | CrateInstances instances = new CrateInstances(); 86 | instances.addInstance(new CrateInstance("runningHost", "1", "0.54.8", 4300, "exec-1", "slave-1")); 87 | CrateExecutableInfo host1 = new CrateExecutableInfo(configuration, "host1", instances, 88 | ImmutableList.of(), 3); 89 | List args = host1.arguments(); 90 | 91 | assertThat(args, Matchers.hasItem("-Des.discovery.zen.minimum_master_nodes=2")); 92 | assertThat(args, Matchers.hasItem("-Des.gateway.recover_after_nodes=2")); 93 | assertThat(args, Matchers.hasItem("-Des.gateway.expected_nodes=3")); 94 | } 95 | 96 | @Test 97 | public void testAttributeFromOffersAreSetAsNodeTags() throws Exception { 98 | Configuration configuration = new Configuration(); 99 | CrateInstances instances = new CrateInstances(); 100 | CrateExecutableInfo host1 = new CrateExecutableInfo(configuration, "host1", instances, 101 | ImmutableList.of( 102 | Protos.Attribute.newBuilder() 103 | .setType(Protos.Value.Type.TEXT) 104 | .setName("zone") 105 | .setText(Protos.Value.Text.newBuilder().setValue("a").build()) 106 | .build() 107 | ), 1); 108 | assertThat(host1.arguments(), Matchers.hasItem("-Des.node.mesos_zone=a")); 109 | } 110 | 111 | @Test 112 | public void testCrateArgsAreSet() throws Exception { 113 | Configuration configuration = new Configuration(); 114 | configuration.crateArgs(Arrays.asList("-Des.foo=x")); 115 | CrateInstances instances = new CrateInstances(); 116 | CrateExecutableInfo host1 = new CrateExecutableInfo(configuration, "host1", instances, 117 | ImmutableList.of(), 1); 118 | 119 | assertThat(host1.arguments(), Matchers.hasItem("-Des.foo=x")); 120 | } 121 | } -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/config/Configuration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos.config; 23 | 24 | import com.beust.jcommander.IParameterValidator; 25 | import com.beust.jcommander.Parameter; 26 | import com.beust.jcommander.ParameterException; 27 | import com.google.common.collect.ImmutableList; 28 | import io.crate.frameworks.mesos.SaneProtos; 29 | import org.apache.mesos.Protos; 30 | 31 | import java.io.Serializable; 32 | import java.util.Arrays; 33 | import java.util.List; 34 | import java.util.regex.Matcher; 35 | import java.util.regex.Pattern; 36 | 37 | public class Configuration implements Serializable { 38 | 39 | @Parameter(names = { "--framework-user" }) 40 | public String user = "crate"; 41 | 42 | @Parameter(names = { "--framework-role" }) 43 | public String role = "*"; 44 | 45 | @Parameter(names = { "--framework-name" }) 46 | public String frameworkName = "crate-mesos"; 47 | 48 | @Parameter(names = { "--mesos-master" }) 49 | private String mesosMaster = null; 50 | 51 | @Parameter(names = { "--zookeeper" }) 52 | public String zookeeper = "localhost:2181"; 53 | 54 | @Parameter(names = { "--crate-version" }, required = true, validateWith = VersionValidator.class) 55 | public String version; 56 | 57 | @Parameter(names = { "--crate-cluster-name" }) 58 | public String clusterName = "crate"; 59 | 60 | @Parameter(names = { "--crate-node-count" }) 61 | public Integer nodeCount = 0; 62 | 63 | @Parameter(names = { "--crate-http-port" }) 64 | public Integer httpPort = 4200; 65 | 66 | @Parameter(names = { "--crate-transport-port" }) 67 | public Integer transportPort = 4300; 68 | 69 | @Parameter(names = { "--crate-data-path" }) 70 | public String dataPath = null; 71 | 72 | @Parameter(names = { "--crate-blob-path" }) 73 | public String blobPath = null; 74 | 75 | @Parameter(names = { "--api-port" }) 76 | public Integer apiPort = 4040; 77 | 78 | @Parameter(names = { "--resource-cpus" }) 79 | public Double resCpus = 0.5d; 80 | 81 | @Parameter(names = { "--resource-memory" }) 82 | public Double resMemory = 512d; 83 | 84 | @Parameter(names = { "--resource-heap" }) 85 | public Double resHeap = 256d; 86 | 87 | @Parameter(names = { "--resource-disk" }) 88 | public Double resDisk = 1024d; 89 | 90 | private List crateArgs = ImmutableList.of(); 91 | 92 | public String mesosMaster() { 93 | if (mesosMaster == null) { 94 | return String.format("zk://%s/mesos", zookeeper); 95 | } 96 | return mesosMaster; 97 | } 98 | 99 | public Iterable getAllRequiredResources() { 100 | return Arrays.asList( 101 | SaneProtos.cpus(resCpus), 102 | SaneProtos.mem(resMemory), 103 | SaneProtos.ports(httpPort, httpPort), 104 | SaneProtos.ports(transportPort, transportPort) 105 | ); 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return "Configuration{" + 111 | "mesosMaster='" + mesosMaster + '\'' + 112 | ", zookeeper='" + zookeeper + '\'' + 113 | ", version='" + version + '\'' + 114 | ", frameworkName='" + frameworkName + '\'' + 115 | ", clusterName='" + clusterName + '\'' + 116 | ", nodeCount=" + nodeCount + 117 | ", httpPort=" + httpPort + 118 | ", transportPort=" + transportPort + 119 | ", dataPath="+ dataPath + 120 | ", blobPath="+ blobPath + 121 | ", apiPort=" + apiPort + 122 | ", resCpus=" + resCpus + 123 | ", resMemory=" + resMemory + 124 | ", resHeap=" + resHeap + 125 | ", resDisk=" + resDisk + 126 | '}'; 127 | } 128 | 129 | public void version(String version) { 130 | this.version = version; 131 | } 132 | 133 | public boolean versionIsDownloadURL() { 134 | return this.version != null && this.version.startsWith("http"); 135 | } 136 | 137 | public void crateArgs(List crateArgs) { 138 | this.crateArgs = crateArgs; 139 | } 140 | 141 | public List crateArgs() { 142 | return crateArgs; 143 | } 144 | 145 | public static class VersionValidator implements IParameterValidator { 146 | 147 | private static final Pattern VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); 148 | private static final Pattern URL_VERSION_PATTERN = Pattern.compile("^https?:\\/\\/.*crate-\\d+\\.\\d+\\.\\d+(.*)\\.tar\\.gz"); 149 | 150 | @Override 151 | public void validate(String name, String value) throws ParameterException { 152 | Matcher matcher; 153 | if (value.startsWith("http")) { 154 | matcher = URL_VERSION_PATTERN.matcher(value); 155 | } else { 156 | matcher = VERSION_PATTERN.matcher(value); 157 | } 158 | if (!matcher.matches()) { 159 | throw new ParameterException( 160 | String.format("The specified Crate version \"%s\" isn't a valid version or download location.", value)); 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/CrateExecutableInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | 25 | import io.crate.frameworks.mesos.config.Configuration; 26 | import org.apache.mesos.Protos.*; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | import java.io.*; 31 | import java.net.URI; 32 | import java.util.*; 33 | 34 | import static java.util.Arrays.asList; 35 | 36 | public class CrateExecutableInfo implements Serializable { 37 | 38 | private final static String CDN_URL = "https://cdn.crate.io/downloads/releases"; 39 | private static final Logger LOGGER = LoggerFactory.getLogger(CrateExecutableInfo.class); 40 | 41 | private final List downloadURIs; 42 | private final String nodeNode; 43 | private final String unicastHosts; 44 | private final String hostname; // todo: this is never used 45 | private final String execId; 46 | private final Configuration configuration; 47 | private final List attributes; 48 | private final int numExpectedNodes; 49 | 50 | public CrateExecutableInfo(Configuration configuration, 51 | String hostname, 52 | CrateInstances crateInstances, 53 | List attributes, 54 | int numExpectedNodes) { 55 | this.execId = UUID.randomUUID().toString(); 56 | this.hostname = hostname; 57 | this.configuration = configuration; 58 | this.attributes = attributes; 59 | this.downloadURIs = asList( 60 | URI.create(configuration.versionIsDownloadURL() ? 61 | configuration.version : 62 | String.format("%s/crate-%s.tar.gz", CDN_URL, configuration.version)) 63 | ); 64 | this.nodeNode = String.format("%s-%s", configuration.clusterName, execId); 65 | this.unicastHosts = crateInstances.unicastHosts(); 66 | this.numExpectedNodes = numExpectedNodes; 67 | } 68 | 69 | public String nodeName() { 70 | return nodeNode; 71 | } 72 | 73 | public int transportPort() { 74 | return configuration.transportPort; 75 | } 76 | 77 | public List arguments() { 78 | int minimumMasterNodes = CrateInstances.calculateQuorum(numExpectedNodes); 79 | List args = new ArrayList<>(asList( 80 | "crate-*/bin/crate", 81 | "-p", 82 | "crate.pid", 83 | String.format("-Des.cluster.name=%s", configuration.clusterName), 84 | String.format("-Des.http.port=%d", configuration.httpPort), 85 | String.format("-Des.transport.tcp.port=%d", configuration.transportPort), 86 | String.format("-Des.node.name=%s", nodeNode), 87 | String.format("-Des.discovery.zen.ping.multicast.enabled=%s", "false"), 88 | String.format("-Des.discovery.zen.ping.unicast.hosts=%s", unicastHosts), 89 | String.format("-Des.discovery.zen.minimum_master_nodes=%d", minimumMasterNodes), 90 | String.format("-Des.gateway.recover_after_nodes=%d", minimumMasterNodes), 91 | String.format("-Des.gateway.expected_nodes=%d", numExpectedNodes) 92 | )); 93 | if (configuration.dataPath != null) { 94 | args.add(String.format("-Des.path.data=%s", configuration.dataPath)); 95 | } 96 | if (configuration.blobPath != null) { 97 | args.add(String.format("-Des.path.blobs=%s", configuration.blobPath)); 98 | } 99 | for (Attribute attribute : attributes) { 100 | if (attribute.hasText()) { 101 | args.add(String.format("-Des.node.mesos_%s=%s", attribute.getName(), attribute.getText().getValue())); 102 | } 103 | } 104 | for (String crateArg : configuration.crateArgs()) { 105 | args.add(crateArg); 106 | } 107 | return args; 108 | } 109 | 110 | public List environment() { 111 | return asList( 112 | Environment.Variable.newBuilder() 113 | .setName("CRATE_HEAP_SIZE") 114 | .setValue(String.format("%sm", configuration.resHeap.longValue())) 115 | .build() 116 | ); 117 | } 118 | 119 | public Integer httpPort() { return configuration.httpPort; } 120 | 121 | public List uris() { 122 | return downloadURIs; 123 | } 124 | 125 | public File dataDir() { 126 | return configuration.dataPath == null ? null : new File(configuration.dataPath); 127 | } 128 | 129 | public File blobDir() { 130 | return configuration.blobPath == null ? null : new File(configuration.blobPath); 131 | } 132 | 133 | /** 134 | * Helper function for Serializable 135 | * @param value 136 | * @return 137 | * @throws IOException 138 | */ 139 | public static CrateExecutableInfo fromStream(byte[] value) throws IOException { 140 | if (value.length == 0) { 141 | return null; 142 | } 143 | ByteArrayInputStream in = new ByteArrayInputStream(value); 144 | try (ObjectInputStream objectInputStream = new ObjectInputStream(in)) { 145 | CrateExecutableInfo state = (CrateExecutableInfo) objectInputStream.readObject(); 146 | return state; 147 | } catch (ClassNotFoundException e) { 148 | LOGGER.error("Could not deserialize CrateExecutableInfo:", e); 149 | } 150 | return null; 151 | } 152 | 153 | /** 154 | * Helper function for Serializable 155 | * @return 156 | */ 157 | public byte[] toStream() { 158 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 159 | try (ObjectOutputStream objOut = new ObjectOutputStream(out)) { 160 | objOut.writeObject(this); 161 | } catch (IOException e){ 162 | LOGGER.error("Could not serialize CrateExecutableInfo:", e); 163 | } 164 | return out.toByteArray(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Framework API 3 | ============= 4 | 5 | .. rubric:: Table of Contents 6 | 7 | .. contents:: 8 | :local: 9 | 10 | API Usage 11 | ========= 12 | 13 | The API is availble on port ``4040`` by default, but can be set via the 14 | ``--api-port`` command line option. 15 | 16 | You can get information about the cluster from the ``/cluster`` endpoint:: 17 | 18 | curl -X GET http://localhost:4040/cluster 19 | 20 | You can resize the cluster by setting the number of desired instances:: 21 | 22 | curl -X POST -H "Content-Type: application/json" localhost:4040/cluster/resize -d '{"instances": 5}' 23 | 24 | You can force a shut down the cluster:: 25 | 26 | curl -X POST http://localhost:4040/cluster/shutdown 27 | 28 | 29 | Resizing a Cluster 30 | ================== 31 | 32 | A Crate cluster can be resized by changing the number of instances using the 33 | Framework API (see `API Usage`_). 34 | 35 | Increasing the number of instances is always possible, unless the number of 36 | desired instances is greater than the number of slaves. Each instance of the 37 | Crate Framework enforces the contraint that there is only one Crate instance 38 | running on each host. 39 | 40 | The Crate Framework shuts down Crate instances gracefully (see `Configuration`_ 41 | and `Zero Downtime Upgrade`_) when decreasing the number of instances in a 42 | cluster. 43 | 44 | If you want to ensure green health status (full data + replica availability), 45 | you need to change the ``cluster.graceful_stop.min_availability`` setting to 46 | ``full``. This option will cause the Crate node to try and move all shards off 47 | the node before shutting down. If this is not possible, the node will **not** 48 | shut down and timeout (``cluster.graceful_stop.timeout``), but the Crate 49 | Framework will continue trying to shut down the node. Such a state is indicated 50 | by the Framework API if the number of running instances does not approach the 51 | number of desired instances when scaling down. Please keep in mind that the 52 | cluster can not be resized to zero instances. 53 | 54 | To shut down the cluster you need to use the ``/cluster/shutdown`` API endpoint. 55 | 56 | Cluster Settings 57 | ---------------- 58 | 59 | When resizing a cluster the Crate Framework automatically applies certain 60 | important cluster settings. These settings are: 61 | 62 | * ``discovery.zen.minimum_master_nodes`` 63 | * ``gateway.recover_after_nodes`` 64 | * ``gateway.expected_nodes`` 65 | 66 | Since only ``minumum_master_nodes`` is a runtime setting, there are limitations 67 | on how these settings are applied. 68 | **These limitations should be carefully considered when running a Crate 69 | cluster!** 70 | 71 | 1. The ``minimum_master_nodes`` and ``gateway`` settings are applied when a 72 | node starts using the ``-Des.`` command line argument. This means that, for 73 | example, when you resize a cluster from 0 to 5 instances, the Crate Framework 74 | executor will launch Crate using these arguments:: 75 | 76 | -Des.discovery.zen.minimum_master_nodes=3 77 | -Des.gateway.recover_after_nodes=3 -Des.gateway.expected_nodes=5 78 | 79 | This will prevent the cluster from a "split brain" scenario and sets the 80 | correct gateway settings for recovery. 81 | 82 | 2. When resizing a cluster from 0 to 1 instance, the same rules apply and the 83 | executor launches Crate with the following arguments:: 84 | 85 | -Des.discovery.zen.minimum_master_nodes=1 86 | -Des.gateway.recover_after_nodes=1 -Des.gateway.expected_nodes=1 87 | 88 | If you now resize to 5 instances the Framework first tries to update the 89 | ``minimum_master_nodes`` setting to the new quorum of the 90 | expected nodes (in this case 5). Since it's not possible to set 91 | this setting to a value greater than the number of nodes in the (existing) 92 | cluster, the cluster settings update will fail (silently), and the newly 93 | added nodes started with the updated arguments:: 94 | 95 | -Des.discovery.zen.minimum_master_nodes=3 96 | -Des.gateway.recover_after_nodes=3 -Des.gateway.expected_nodes=5 97 | 98 | Note, that starting the instances with these settings will not update the 99 | cluster setting itself, you will have to update them manually using 100 | ``crash`` or via the Admin UI. 101 | 102 | 3. When scaling down a cluster, the Crate Framework also updates the 103 | ``minimum_master_nodes`` setting before shutting down the 104 | required nodes to reach the new amount of desired instances. This means that 105 | there's a small window where the setting is "incorrect". If you need to 106 | scale down your cluster significantly, you should do it in multiple smaller 107 | iterations. 108 | 109 | 4. Since the ``gateway.*`` settings are **not** settable at runtime, the value 110 | applied when first starting the cluster cannot be changed, even when 111 | scaling down. This means that, for example, the value of ``expected_nodes`` 112 | is still 5 (from the initial cluster start) even though you've already resized the cluster to 3 nodes. 113 | 114 | 115 | Cluster Upgrade 116 | =============== 117 | 118 | A zero downtime upgrade of a Crate cluster running on Mesos is currently not 119 | possible, but it's still possible to upgrade the cluster with downtime. 120 | 121 | .. warning:: 122 | 123 | A cluster upgrade/shutdown requires the ``--crate-data-path`` to be set 124 | so data is stored persistently outside of the sandboxed executor path. 125 | **Otherwise data will be lost** 126 | 127 | An upgrade requires a few steps: 128 | 129 | 1. Set graceful stop options 130 | ---------------------------- 131 | 132 | Assuming you've started the Crate Framework with version 0.47.7 and want to 133 | upgrade to version 0.47.8 (or any other newer version), you will first need 134 | to set the minimum availability to ``full`` (see `Resizing a Cluster`_) if 135 | not already done so. Also check the other options for a graceful shutdown. This 136 | will ensure that you are able to resize your cluster to the minimum amount 137 | of nodes. 138 | 139 | 2. Resize to minimum required nodes 140 | ----------------------------------- 141 | 142 | The minimum amount of nodes is equal to the highest number of replicas of a 143 | table plus 1:: 144 | 145 | min_nodes = max_replicas + 1 146 | 147 | E.g. If your cluster has 5 nodes and your table with the highest configured 148 | replicas has 2 replicas, you can resize your cluster down to 3 nodes. 149 | 150 | It's highly recommended to shut down Crate nodes one by one. In this way you 151 | are in better control if a node does not shut down gracefully, e.g. it timesout. 152 | 153 | 3. Restart framework with new Crate version number 154 | -------------------------------------------------- 155 | 156 | Now you can restart the Crate Framework with the newer Crate version, but the 157 | Crate instances with the old version are still running. If you scale your 158 | cluster now, the new Crate instances will still be using the old version, and 159 | that's not what you want. 160 | 161 | 4. Shut down remaining instances and scale up again 162 | --------------------------------------------------- 163 | 164 | To use the new version with the restarted framework, you need to kill the 165 | remaining instances using the ``/cluster/shutdown`` API endpoint. 166 | 167 | Once there are no more instances running the old version, you can resize the 168 | cluster and new Crate instances will use the new version from the framework. 169 | 170 | Because the framework stores details on which slaves Crate instances with data 171 | were running when you up-scale the cluster again, it will prefer offers from 172 | these slaves. 173 | 174 | .. note:: 175 | 176 | Please also read the instructions how to perform a `Zero Downtime Upgrade`_! 177 | 178 | .. note:: 179 | 180 | You can omit step 2, however recovery is faster if there are less instances 181 | and it's less likely that other frameworks 'capture' resources on slaves 182 | making it impossible to spawn Crate instances on these slaves again. 183 | 184 | 185 | .. _Configuration: https://crate.io/docs/en/stable/configuration.html#graceful-stop 186 | .. _`Zero Downtime Upgrade`: https://crate.io/docs/en/stable/best_practice/cluster_upgrade.html 187 | -------------------------------------------------------------------------------- /docs/bootstrap.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2006 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Bootstrap a buildout-based project 15 | 16 | Simply run this script in a directory containing a buildout.cfg. 17 | The script accepts buildout command-line options, so you can 18 | use the -c option to specify an alternate configuration file. 19 | """ 20 | 21 | import os 22 | import shutil 23 | import sys 24 | import tempfile 25 | 26 | from optparse import OptionParser 27 | 28 | __version__ = '2015-07-01' 29 | # See zc.buildout's changelog if this version is up to date. 30 | 31 | tmpeggs = tempfile.mkdtemp(prefix='bootstrap-') 32 | 33 | usage = '''\ 34 | [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] 35 | 36 | Bootstraps a buildout-based project. 37 | 38 | Simply run this script in a directory containing a buildout.cfg, using the 39 | Python that you want bin/buildout to use. 40 | 41 | Note that by using --find-links to point to local resources, you can keep 42 | this script from going over the network. 43 | ''' 44 | 45 | parser = OptionParser(usage=usage) 46 | parser.add_option("--version", 47 | action="store_true", default=False, 48 | help=("Return bootstrap.py version.")) 49 | parser.add_option("-t", "--accept-buildout-test-releases", 50 | dest='accept_buildout_test_releases', 51 | action="store_true", default=False, 52 | help=("Normally, if you do not specify a --version, the " 53 | "bootstrap script and buildout gets the newest " 54 | "*final* versions of zc.buildout and its recipes and " 55 | "extensions for you. If you use this flag, " 56 | "bootstrap and buildout will get the newest releases " 57 | "even if they are alphas or betas.")) 58 | parser.add_option("-c", "--config-file", 59 | help=("Specify the path to the buildout configuration " 60 | "file to be used.")) 61 | parser.add_option("-f", "--find-links", 62 | help=("Specify a URL to search for buildout releases")) 63 | parser.add_option("--allow-site-packages", 64 | action="store_true", default=False, 65 | help=("Let bootstrap.py use existing site packages")) 66 | parser.add_option("--buildout-version", 67 | help="Use a specific zc.buildout version") 68 | parser.add_option("--setuptools-version", 69 | help="Use a specific setuptools version") 70 | parser.add_option("--setuptools-to-dir", 71 | help=("Allow for re-use of existing directory of " 72 | "setuptools versions")) 73 | 74 | options, args = parser.parse_args() 75 | if options.version: 76 | print("bootstrap.py version %s" % __version__) 77 | sys.exit(0) 78 | 79 | 80 | ###################################################################### 81 | # load/install setuptools 82 | 83 | try: 84 | from urllib.request import urlopen 85 | except ImportError: 86 | from urllib2 import urlopen 87 | 88 | ez = {} 89 | if os.path.exists('ez_setup.py'): 90 | exec(open('ez_setup.py').read(), ez) 91 | else: 92 | exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) 93 | 94 | if not options.allow_site_packages: 95 | # ez_setup imports site, which adds site packages 96 | # this will remove them from the path to ensure that incompatible versions 97 | # of setuptools are not in the path 98 | import site 99 | # inside a virtualenv, there is no 'getsitepackages'. 100 | # We can't remove these reliably 101 | if hasattr(site, 'getsitepackages'): 102 | for sitepackage_path in site.getsitepackages(): 103 | # Strip all site-packages directories from sys.path that 104 | # are not sys.prefix; this is because on Windows 105 | # sys.prefix is a site-package directory. 106 | if sitepackage_path != sys.prefix: 107 | sys.path[:] = [x for x in sys.path 108 | if sitepackage_path not in x] 109 | 110 | setup_args = dict(to_dir=tmpeggs, download_delay=0) 111 | 112 | if options.setuptools_version is not None: 113 | setup_args['version'] = options.setuptools_version 114 | if options.setuptools_to_dir is not None: 115 | setup_args['to_dir'] = options.setuptools_to_dir 116 | 117 | ez['use_setuptools'](**setup_args) 118 | import setuptools 119 | import pkg_resources 120 | 121 | # This does not (always?) update the default working set. We will 122 | # do it. 123 | for path in sys.path: 124 | if path not in pkg_resources.working_set.entries: 125 | pkg_resources.working_set.add_entry(path) 126 | 127 | ###################################################################### 128 | # Install buildout 129 | 130 | ws = pkg_resources.working_set 131 | 132 | setuptools_path = ws.find( 133 | pkg_resources.Requirement.parse('setuptools')).location 134 | 135 | # Fix sys.path here as easy_install.pth added before PYTHONPATH 136 | cmd = [sys.executable, '-c', 137 | 'import sys; sys.path[0:0] = [%r]; ' % setuptools_path + 138 | 'from setuptools.command.easy_install import main; main()', 139 | '-mZqNxd', tmpeggs] 140 | 141 | find_links = os.environ.get( 142 | 'bootstrap-testing-find-links', 143 | options.find_links or 144 | ('http://downloads.buildout.org/' 145 | if options.accept_buildout_test_releases else None) 146 | ) 147 | if find_links: 148 | cmd.extend(['-f', find_links]) 149 | 150 | requirement = 'zc.buildout' 151 | version = options.buildout_version 152 | if version is None and not options.accept_buildout_test_releases: 153 | # Figure out the most recent final version of zc.buildout. 154 | import setuptools.package_index 155 | _final_parts = '*final-', '*final' 156 | 157 | def _final_version(parsed_version): 158 | try: 159 | return not parsed_version.is_prerelease 160 | except AttributeError: 161 | # Older setuptools 162 | for part in parsed_version: 163 | if (part[:1] == '*') and (part not in _final_parts): 164 | return False 165 | return True 166 | 167 | index = setuptools.package_index.PackageIndex( 168 | search_path=[setuptools_path]) 169 | if find_links: 170 | index.add_find_links((find_links,)) 171 | req = pkg_resources.Requirement.parse(requirement) 172 | if index.obtain(req) is not None: 173 | best = [] 174 | bestv = None 175 | for dist in index[req.project_name]: 176 | distv = dist.parsed_version 177 | if _final_version(distv): 178 | if bestv is None or distv > bestv: 179 | best = [dist] 180 | bestv = distv 181 | elif distv == bestv: 182 | best.append(dist) 183 | if best: 184 | best.sort() 185 | version = best[-1].version 186 | if version: 187 | requirement = '=='.join((requirement, version)) 188 | cmd.append(requirement) 189 | 190 | import subprocess 191 | if subprocess.call(cmd) != 0: 192 | raise Exception( 193 | "Failed to execute command:\n%s" % repr(cmd)[1:-1]) 194 | 195 | ###################################################################### 196 | # Import and run buildout 197 | 198 | ws.add_entry(tmpeggs) 199 | ws.require(requirement) 200 | import zc.buildout.buildout 201 | 202 | if not [a for a in args if '=' not in a]: 203 | args.append('bootstrap') 204 | 205 | # if -c was provided, we push it back into args for buildout' main function 206 | if options.config_file is not None: 207 | args[0:0] = ['-c', options.config_file] 208 | 209 | zc.buildout.buildout.main(args) 210 | shutil.rmtree(tmpeggs) 211 | -------------------------------------------------------------------------------- /src/main/java/io/crate/frameworks/mesos/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import com.beust.jcommander.JCommander; 25 | import com.google.common.base.Optional; 26 | import com.google.common.collect.Sets; 27 | import io.crate.frameworks.mesos.api.CrateHttpService; 28 | import io.crate.frameworks.mesos.config.Configuration; 29 | import io.crate.shade.org.apache.commons.lang3.ObjectUtils; 30 | import org.apache.log4j.BasicConfigurator; 31 | import org.apache.mesos.MesosSchedulerDriver; 32 | import org.apache.mesos.Protos; 33 | import org.apache.mesos.Scheduler; 34 | import org.apache.mesos.state.ZooKeeperState; 35 | import org.slf4j.Logger; 36 | import org.slf4j.LoggerFactory; 37 | 38 | import javax.ws.rs.core.UriBuilder; 39 | import java.net.InetAddress; 40 | import java.net.UnknownHostException; 41 | import java.util.ArrayList; 42 | import java.util.List; 43 | import java.util.Set; 44 | import java.util.concurrent.TimeUnit; 45 | 46 | 47 | public class Main { 48 | 49 | private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); 50 | public static final String JAVA_URL = "https://cdn.crate.io/downloads/openjdk/jre-7u80-linux.tar.gz"; 51 | 52 | private static final Set HELP_OPTIONS = Sets.newHashSet("-h", "--help", "help"); 53 | private static final Set PROTECTED_CRATE_ARGS = Sets.newHashSet( 54 | "-Des.cluster.name", 55 | "-Des.http.port", 56 | "-Des.transport.tcp.port", 57 | "-Des.node.name", 58 | "-Des.discovery.zen.ping.multicast.enabled", 59 | "-Des.discovery.zen.ping.unicast.hosts", 60 | "-Des.path.data", 61 | "-Des.path.blobs", 62 | "-Des.path.logs" 63 | ); 64 | 65 | static Configuration parseConfiguration(String[] args) { 66 | Configuration configuration = new Configuration(); 67 | JCommander jCommander; 68 | 69 | List crateArgs = new ArrayList<>(); 70 | List safeArgs = new ArrayList<>(args.length); 71 | for (String arg : args) { 72 | if (HELP_OPTIONS.contains(arg)) { 73 | jCommander = new JCommander(configuration); 74 | jCommander.usage(); 75 | System.exit(1); 76 | } 77 | if (arg.startsWith("-Des.")) { 78 | String argKey = arg.split("\\=")[0]; 79 | if (PROTECTED_CRATE_ARGS.contains(argKey)) { 80 | throw new IllegalArgumentException( 81 | String.format("Argument \"%s\" is protected and managed by the framework. " + 82 | "It cannot be set by the user", argKey)); 83 | } else { 84 | crateArgs.add(arg); 85 | } 86 | } else { 87 | safeArgs.add(arg); 88 | } 89 | } 90 | // todo: jCommander below is never used, if removed safeArgs is never used. It is unclear of it's purpose 91 | jCommander = new JCommander(configuration, safeArgs.toArray(new String[safeArgs.size()])); // todo: jcommander is never used 92 | configuration.crateArgs(crateArgs); 93 | LOGGER.debug("args: {}", configuration); 94 | return configuration; 95 | } 96 | 97 | 98 | private static Optional readCredentials() { 99 | if (System.getenv("MESOS_AUTHENTICATE") != null) { 100 | LOGGER.debug("Enabling authentication for the framework"); 101 | final String principal = System.getenv("DEFAULT_PRINCIPAL"); 102 | final String secret = System.getenv("DEFAULT_SECRET"); 103 | if (principal == null) { 104 | LOGGER.error("Expecting authentication principal in the environment"); 105 | System.exit(1); 106 | } 107 | Protos.Credential.Builder credential = Protos.Credential.newBuilder().setPrincipal(principal); 108 | if (secret == null) { 109 | LOGGER.error("Expecting authentication secret in the environment"); 110 | } else { 111 | credential.setSecret(secret); 112 | } 113 | return Optional.of(credential.build()); 114 | } else { 115 | return Optional.absent(); 116 | } 117 | } 118 | 119 | public static void main(String[] args) throws Exception { 120 | BasicConfigurator.configure(); 121 | Configuration configuration = parseConfiguration(args); 122 | 123 | final double frameworkFailoverTimeout = 31536000d; // 60 * 60 * 24 * 365 = 1y 124 | 125 | final String webUri = UriBuilder.fromPath("/cluster") 126 | .scheme("http") 127 | .host(host()) 128 | .port(configuration.apiPort) 129 | .build().toString(); 130 | Protos.FrameworkInfo.Builder frameworkBuilder = Protos.FrameworkInfo.newBuilder() 131 | .setName(configuration.frameworkName) 132 | .setUser(configuration.user) 133 | .setRole(configuration.role) 134 | .setWebuiUrl(webUri) 135 | .setCheckpoint(true) // will be enabled by default in Mesos 0.22 136 | .setFailoverTimeout(frameworkFailoverTimeout); 137 | 138 | PersistentStateStore stateStore = new PersistentStateStore( 139 | new ZooKeeperState(configuration.zookeeper, 20_000, TimeUnit.MILLISECONDS, 140 | String.format("/%s/%s", configuration.frameworkName, configuration.clusterName)), 141 | configuration.nodeCount); 142 | 143 | Optional frameworkId = stateStore.state().frameworkId(); 144 | if (frameworkId.isPresent()) { 145 | frameworkBuilder.setId(Protos.FrameworkID.newBuilder().setValue(frameworkId.get()).build()); 146 | } 147 | 148 | final Scheduler scheduler = new CrateScheduler(stateStore, configuration); 149 | 150 | // create the driver 151 | MesosSchedulerDriver driver; 152 | 153 | String mesosMaster = configuration.mesosMaster(); 154 | Optional credential = readCredentials(); 155 | if (credential.isPresent()) { 156 | frameworkBuilder.setPrincipal(credential.get().getPrincipal()); 157 | driver = new MesosSchedulerDriver(scheduler, frameworkBuilder.build(), mesosMaster, credential.get()); 158 | } else { 159 | frameworkBuilder.setPrincipal("crate-framework"); 160 | driver = new MesosSchedulerDriver(scheduler, frameworkBuilder.build(), mesosMaster); 161 | } 162 | 163 | CrateHttpService api = new CrateHttpService(stateStore, configuration); 164 | api.start(); 165 | int status = driver.run() == Protos.Status.DRIVER_STOPPED ? 0 : 1; 166 | 167 | // Ensure that the driver process terminates. 168 | api.stop(); 169 | driver.stop(); 170 | System.exit(status); 171 | } 172 | 173 | public static String host() { 174 | return ObjectUtils.firstNonNull(System.getenv("LIBPROCESS_IP"), 175 | System.getenv("HOST"), 176 | System.getenv("MESOS_HOSTNAME"), 177 | currentHost()); 178 | } 179 | 180 | private static String currentHost() { 181 | String host; 182 | try { 183 | host = InetAddress.getLocalHost().getHostName(); 184 | } catch (UnknownHostException e) { 185 | LOGGER.warn("Could not obtain hostname. Using localhost", e); 186 | host = "127.0.0.1"; 187 | } 188 | return host; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/test/java/io/crate/frameworks/mesos/integration/BaseIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.crate.frameworks.mesos.integration; 2 | 3 | import com.containersol.minimesos.cluster.MesosAgent; 4 | import com.containersol.minimesos.cluster.MesosCluster; 5 | import com.containersol.minimesos.junit.MesosClusterTestRule; 6 | import com.containersol.minimesos.state.Framework; 7 | import com.jayway.awaitility.Awaitility; 8 | import com.mashape.unirest.http.HttpResponse; 9 | import com.mashape.unirest.http.JsonNode; 10 | import com.mashape.unirest.http.Unirest; 11 | import com.mashape.unirest.http.exceptions.UnirestException; 12 | import org.apache.commons.io.IOUtils; 13 | import org.apache.commons.io.filefilter.RegexFileFilter; 14 | import org.json.JSONArray; 15 | import org.junit.BeforeClass; 16 | import org.junit.ClassRule; 17 | 18 | import java.io.File; 19 | import java.io.FileFilter; 20 | import java.io.FileInputStream; 21 | import java.io.IOException; 22 | import java.nio.file.Paths; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.concurrent.Callable; 26 | import java.util.concurrent.TimeUnit; 27 | import java.util.regex.Matcher; 28 | import java.util.regex.Pattern; 29 | 30 | import static junit.framework.TestCase.fail; 31 | 32 | public class BaseIntegrationTest { 33 | 34 | private static final String MESOS_JAR_PATTERN = "crate-mesos-(.*?).jar"; 35 | private static final int HTTP_PORT = 4200; 36 | private static final int TRANSPORT_PORT = 4300; 37 | private static final int API_PORT = 4242; 38 | private static final int WAIT_TIMEOUT_SECONDS = 90; 39 | 40 | @ClassRule 41 | public static MesosClusterTestRule testRule = 42 | MesosClusterTestRule.fromFile("src/test/resources/minimesosConf"); 43 | 44 | public static MesosCluster cluster = testRule.getMesosCluster(); 45 | 46 | private static String crateMesosFrameworkHostIp; 47 | 48 | @BeforeClass 49 | public static void beforeClass() throws IOException { 50 | cluster.getMarathon().deployApp(appMarathonJson()); 51 | waitForCrateFramework(); 52 | } 53 | 54 | private static String appMarathonJson() throws IOException { 55 | File taskFile = new File("src/test/resources/app.json"); 56 | if (!taskFile.exists()) { 57 | fail("Failed to find task info file " + taskFile.getAbsolutePath()); 58 | } 59 | 60 | try (FileInputStream fis = new FileInputStream(taskFile)) { 61 | String appJson = IOUtils.toString(fis); 62 | String mesosFrameworkPath = mesosCrateFrameworkJarPath(); 63 | appJson = replaceToken(appJson, "HTTP_PORT", String.valueOf(HTTP_PORT)); 64 | appJson = replaceToken(appJson, "TRANSPORT_PORT", String.valueOf(TRANSPORT_PORT)); 65 | appJson = replaceToken(appJson, "API_PORT", String.valueOf(API_PORT)); 66 | appJson = replaceToken(appJson, "CRATE_MESOS_FRAMEWORK_PATH", mesosFrameworkPath); 67 | appJson = replaceToken(appJson, "CRATE_MESOS_FRAMEWORK", 68 | Paths.get(mesosFrameworkPath).getFileName().toString()); 69 | appJson = replaceToken(appJson, "ZOOKEEPER", cluster.getZooKeeper().getIpAddress()); 70 | appJson = replaceToken(appJson, "MESOS_MASTER", cluster.getZooKeeper().getFormattedZKAddress()); 71 | appJson = replaceToken(appJson, "CRATE_VERSION", crateVersion()); 72 | return appJson; 73 | } 74 | } 75 | 76 | private static String replaceToken(String input, String token, String value) { 77 | String tokenRegex = String.format("\\$\\{%s\\}", token); 78 | return input.replaceAll(tokenRegex, value); 79 | } 80 | 81 | private static String crateVersion() { 82 | String cp = System.getProperty("java.class.path"); 83 | Matcher m = Pattern.compile("crate-client-([\\d\\.]{5,})\\.jar").matcher(cp); 84 | if (m.find()) { 85 | return m.group(1); 86 | } 87 | throw new RuntimeException("Cannot the crate version"); 88 | } 89 | 90 | private static String mesosCrateFrameworkJarPath() { 91 | File directory = Paths.get(System.getProperty("user.dir"), "build/libs").toFile(); 92 | FileFilter filter = new RegexFileFilter(MESOS_JAR_PATTERN); 93 | File[] files = directory.listFiles(filter); 94 | assert files.length == 1; 95 | return files[0].getAbsolutePath(); 96 | } 97 | 98 | private static void waitForCrateFramework() { 99 | Awaitility.await().atMost(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) 100 | .pollInterval(1, TimeUnit.SECONDS).until(new Callable() { 101 | @Override 102 | public Boolean call() throws Exception { 103 | Framework fw = cluster.getMaster().getState().getFramework("crate-mesos"); 104 | int status = 0; 105 | if (fw != null) { 106 | crateMesosFrameworkHostIp = cluster.getAgentStateInfo(fw.getHostname()).getString("hostname"); 107 | try { 108 | status = Unirest.head( 109 | String.format("http://%s:%d/cluster", crateMesosFrameworkHostIp, API_PORT)).asJson().getStatus(); 110 | } catch (UnirestException e) { 111 | //ignore 112 | } 113 | return status == 200; 114 | } 115 | return false; 116 | } 117 | }); 118 | } 119 | 120 | public void shutdown() { 121 | Awaitility.await().atMost(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) 122 | .pollInterval(3, TimeUnit.SECONDS).until(new Callable() { 123 | @Override 124 | public Boolean call() throws Exception { 125 | try { 126 | Unirest.post( 127 | String.format("http://%s:%d/cluster/shutdown", crateMesosFrameworkHostIp, API_PORT) 128 | ).asJson(); 129 | } catch (UnirestException e) { 130 | //ignore 131 | } 132 | return 0 == crateNodesCount(); 133 | } 134 | }); 135 | } 136 | 137 | public void scaleCrate(final int numNodes) { 138 | try { 139 | Unirest.post(String.format("http://%s:%d/cluster/resize", crateMesosFrameworkHostIp, API_PORT)) 140 | .header("Content-Type", "application/json") 141 | .body(String.format("{\"instances\": %d}", numNodes)).asJson(); 142 | } catch (Exception e) { 143 | e.printStackTrace(); 144 | throw new RuntimeException("Error in scaling crate."); 145 | } 146 | 147 | Awaitility.await().atMost(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) 148 | .pollInterval(1, TimeUnit.SECONDS).until(new Callable() { 149 | @Override 150 | public Boolean call() throws Exception { 151 | return numNodes == crateNodesCount(); 152 | } 153 | }); 154 | } 155 | 156 | public int crateNodesCount() throws UnirestException { 157 | String crateHost = crateActiveHost(); 158 | if (crateHost == null) { 159 | return 0; 160 | } 161 | JSONArray rows = Unirest.post(String.format("http://%s:%d/_sql", crateHost, HTTP_PORT)) 162 | .header("Content-Type", "application/json") 163 | .body("{\"stmt\": \"select count(*) from sys.nodes\"}") 164 | .asJson().getBody().getObject().getJSONArray("rows"); 165 | return rows.getJSONArray(0).getInt(0); 166 | } 167 | 168 | private String crateActiveHost() { 169 | String crateHostIp = null; 170 | for (MesosAgent agent : cluster.getAgents()) { 171 | try { 172 | String crateUrl = String.format("http://%s:%d", agent.getIpAddress(), HTTP_PORT); 173 | int status = Unirest.head(crateUrl).asJson().getStatus(); 174 | if (status == 200) { 175 | crateHostIp = agent.getIpAddress(); 176 | break; 177 | } 178 | } catch (UnirestException e) { 179 | //ignore 180 | } 181 | } 182 | return crateHostIp; 183 | } 184 | 185 | public HttpResponse execute(String stmt) throws UnirestException { 186 | String crateHost = crateActiveHost(); 187 | if (crateHost == null) { 188 | throw new RuntimeException("Crate nodes are not running"); 189 | } 190 | return Unirest.post(String.format("http://%s:%d/_sql", crateHost, HTTP_PORT)) 191 | .header("Content-Type", "application/json") 192 | .body(String.format("{\"stmt\": \"%s\"}", stmt)) 193 | .asJson(); 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/test/java/io/crate/frameworks/mesos/api/CrateRestResourceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos.api; 23 | 24 | import io.crate.frameworks.mesos.CrateInstance; 25 | import io.crate.frameworks.mesos.CrateState; 26 | import io.crate.frameworks.mesos.PersistentStateStore; 27 | import io.crate.frameworks.mesos.Version; 28 | import io.crate.frameworks.mesos.config.Configuration; 29 | import org.apache.curator.framework.CuratorFramework; 30 | import org.apache.curator.framework.api.GetChildrenBuilder; 31 | import org.apache.curator.framework.api.GetDataBuilder; 32 | import org.json.JSONObject; 33 | import org.junit.Before; 34 | import org.junit.Test; 35 | 36 | import javax.ws.rs.core.UriInfo; 37 | import java.util.Arrays; 38 | import java.util.Collections; 39 | import java.util.HashMap; 40 | 41 | import static org.hamcrest.CoreMatchers.is; 42 | import static org.hamcrest.CoreMatchers.nullValue; 43 | import static org.mockito.Mockito.*; 44 | import static org.junit.Assert.*; 45 | 46 | public class CrateRestResourceTest { 47 | 48 | private CrateRestResource resource; 49 | 50 | private CuratorFramework cf = mock(CuratorFramework.class); 51 | private GetDataBuilder dataBuilder = mock(GetDataBuilder.class); 52 | private GetChildrenBuilder childBuilder = mock(GetChildrenBuilder.class); 53 | 54 | @Before 55 | public void setUp() throws Exception { 56 | Configuration configuration = new Configuration(); 57 | configuration.version("0.48.0"); 58 | PersistentStateStore mockedStore = mock(PersistentStateStore.class); 59 | CrateState state = new CrateState(); 60 | CrateInstance crate1 = new CrateInstance("crate1", "task-1", "0.48.0", 44300, "exec-1", "slave-1"); 61 | state.crateInstances().addInstance(crate1); 62 | when(mockedStore.state()).thenReturn(state); 63 | resource = spy(new CrateRestResource(mockedStore, configuration)); 64 | 65 | when(cf.getChildren()).thenReturn(childBuilder); 66 | when(cf.getData()).thenReturn(dataBuilder); 67 | when(resource.zkClient()).thenReturn(cf); 68 | 69 | doReturn(Integer.MAX_VALUE).when(resource).numActiveSlaves(anyString()); 70 | doReturn("").when(resource).mesosMasterAddress(); 71 | } 72 | 73 | @Test 74 | public void testIndex() throws Exception { 75 | UriInfo mockedInfo = mock(UriInfo.class); 76 | GenericAPIResponse res = resource.index(mockedInfo); 77 | assertEquals(String.format("Crate Mesos Framework (%s)", Version.CURRENT), res.getMessage()); 78 | assertEquals(200, res.getStatus()); 79 | } 80 | 81 | @Test 82 | public void testClusterInfo() throws Exception { 83 | UriInfo mockedInfo = mock(UriInfo.class); 84 | GenericAPIResponse res = (GenericAPIResponse) resource.clusterIndex(mockedInfo).getEntity(); 85 | HashMap entity = (HashMap) res.getMessage(); 86 | assertEquals(entity.get("cluster"), new HashMap(){ 87 | { 88 | put("version", "0.48.0"); 89 | put("name", "crate"); 90 | put("httpPort", 4200); 91 | put("nodeCount", 0); 92 | } 93 | }); 94 | assertEquals(entity.get("instances"), new HashMap() { 95 | { 96 | put("desired", -1); 97 | put("running", 1); 98 | } 99 | }); 100 | assertEquals(200, res.getStatus()); 101 | } 102 | 103 | @Test 104 | public void testClusterResize() throws Exception { 105 | GenericAPIResponse res = (GenericAPIResponse) resource.clusterResize(new ClusterResizeRequest(2)).getEntity(); 106 | assertEquals("SUCCESS", res.getMessage()); 107 | assertEquals(200, res.getStatus()); 108 | res = (GenericAPIResponse) resource.clusterResize(new ClusterResizeRequest(1)).getEntity(); 109 | assertEquals("SUCCESS", res.getMessage()); 110 | assertEquals(200, res.getStatus()); 111 | } 112 | 113 | @Test 114 | public void testClusterResizeToZeroInstances() throws Exception { 115 | GenericAPIResponse res = (GenericAPIResponse) resource.clusterResize(new ClusterResizeRequest(0)).getEntity(); 116 | assertEquals("Could not change the number of instances. " + 117 | "Scaling down to zero instances is not allowed. " + 118 | "Please use '/cluster/shutdown' instead.", res.getMessage()); 119 | assertEquals(403, res.getStatus()); 120 | } 121 | 122 | @Test 123 | public void testClusterResizeNumInstancesEqualOrLessThanActiveAgents() throws Exception { 124 | doReturn(3).when(resource).numActiveSlaves(anyString()); 125 | 126 | GenericAPIResponse res = (GenericAPIResponse) resource.clusterResize(new ClusterResizeRequest(2)).getEntity(); 127 | assertEquals("SUCCESS", res.getMessage()); 128 | assertEquals(200, res.getStatus()); 129 | 130 | res = (GenericAPIResponse) resource.clusterResize(new ClusterResizeRequest(3)).getEntity(); 131 | assertEquals("SUCCESS", res.getMessage()); 132 | assertEquals(200, res.getStatus()); 133 | } 134 | 135 | @Test 136 | public void testClusterResizeNumInstancesGreaterThanActiveAgents() throws Exception { 137 | doReturn(3).when(resource).numActiveSlaves(anyString()); 138 | 139 | GenericAPIResponse res = (GenericAPIResponse) resource.clusterResize(new ClusterResizeRequest(4)).getEntity(); 140 | assertEquals("Could not initialize more Crate nodes than existing number of mesos agents", res.getMessage()); 141 | assertEquals(403, res.getStatus()); 142 | } 143 | 144 | @Test 145 | public void testMasterMesosAddress() throws Exception { 146 | when(childBuilder.forPath(anyString())).thenReturn(Arrays.asList("json.info_0000000000")); 147 | when(dataBuilder.forPath(anyString())).thenReturn("{\"address\":{\"ip\":\"host\",\"port\":5050}}".getBytes()); 148 | doCallRealMethod().when(resource).mesosMasterAddress(); 149 | 150 | String address = resource.mesosMasterAddress(); 151 | assertThat(address, is("host:5050")); 152 | } 153 | 154 | @Test 155 | public void testMultiMasterMesosAddress() throws Exception { 156 | when(childBuilder.forPath("/mesos")).thenReturn(Arrays.asList("json.info_0000000100","json.info_0000000004","json.info_0000000020","json.info_0000000001")); 157 | when(dataBuilder.forPath("/mesos/json.info_0000000001")).thenReturn("{\"address\":{\"ip\":\"host\",\"port\":5050}}".getBytes()); 158 | doCallRealMethod().when(resource).mesosMasterAddress(); 159 | 160 | String address = resource.mesosMasterAddress(); 161 | assertThat(address, is("host:5050")); 162 | } 163 | 164 | 165 | @Test 166 | public void testMasterMesosAddressNoAddressKeyInCFJsonData() throws Exception { 167 | when(childBuilder.forPath(anyString())).thenReturn(Arrays.asList("json.info_0000000000")); 168 | when(dataBuilder.forPath(anyString())).thenReturn("{\"a\":{\"ip\":\"172.17.0.3\",\"port\":5050}}".getBytes()); 169 | doCallRealMethod().when(resource).mesosMasterAddress(); 170 | 171 | String address = resource.mesosMasterAddress(); 172 | assertThat(address, is(nullValue())); 173 | } 174 | 175 | @Test 176 | public void testMasterMesosAddressChildrenListIsEmpty() throws Exception { 177 | when(childBuilder.forPath(anyString())).thenReturn(Collections.emptyList()); 178 | doCallRealMethod().when(resource).mesosMasterAddress(); 179 | 180 | String address = resource.mesosMasterAddress(); 181 | assertThat(address, is(nullValue())); 182 | } 183 | 184 | @Test 185 | public void testClusterShutdown() throws Exception { 186 | GenericAPIResponse res = (GenericAPIResponse) resource.clusterShutdown().getEntity(); 187 | assertEquals("SUCCESS", res.getMessage()); 188 | assertEquals(200, res.getStatus()); 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: sh 2 | 3 | .. _usage: 4 | 5 | ===== 6 | Usage 7 | ===== 8 | 9 | .. rubric:: Table of Contents 10 | 11 | .. contents:: 12 | :local: 13 | 14 | Command Line Options 15 | ==================== 16 | 17 | Framework specific options 18 | -------------------------- 19 | 20 | =========================== ============== ======================= 21 | OPTION REQUIRED DEFAULT 22 | =========================== ============== ======================= 23 | ``--crate-version`` true 24 | --------------------------- -------------- ----------------------- 25 | ``--mesos-master`` false zk://{zookeeper}/mesos 26 | --------------------------- -------------- ----------------------- 27 | ``--zookeeper`` false localhost:2181 28 | --------------------------- -------------- ----------------------- 29 | ``--crate-cluster-name`` false crate 30 | --------------------------- -------------- ----------------------- 31 | ``--crate-http-port`` false 4200 32 | --------------------------- -------------- ----------------------- 33 | ``--crate-transport-port`` false 4300 34 | --------------------------- -------------- ----------------------- 35 | ``--crate-data-path`` false not set 36 | --------------------------- -------------- ----------------------- 37 | ``--crate-blob-path`` false not set 38 | --------------------------- -------------- ----------------------- 39 | ``--api-port`` false 4040 40 | --------------------------- -------------- ----------------------- 41 | ``--resource-cpus`` false 0.5 42 | --------------------------- -------------- ----------------------- 43 | ``--resource-memory`` false 512 44 | --------------------------- -------------- ----------------------- 45 | ``--resource-heap`` false 256 46 | --------------------------- -------------- ----------------------- 47 | ``--resource-disk`` false 1024 48 | --------------------------- -------------- ----------------------- 49 | ``--framework-name`` false crate-mesos 50 | --------------------------- -------------- ----------------------- 51 | ``--framework-user`` false crate 52 | --------------------------- -------------- ----------------------- 53 | ``--framework-role`` false * 54 | =========================== ============== ======================= 55 | 56 | 57 | Zookeeper 58 | --------- 59 | 60 | The default value for the ``--zookeeper`` parameter is ``localhost:2181``, but 61 | in highly available production cases it must be a list of hostnames in your 62 | Zookeeper cluster. 63 | 64 | For example, if your Zookeeper nodes are ``mesos-master-1``, ``mesos-master-2``, 65 | and ``mesos-master-3``, then the values of the parameter will look like:: 66 | 67 | mesos-master-1:2181,mesos-master-2:2181,mesos-master-3:2181 68 | 69 | For DCOS_ clusters the ``--zookeeper`` parameter is ``master.mesos:2181``. 70 | 71 | 72 | .. _persistent_data_paths: 73 | 74 | Persistent Data Paths 75 | --------------------- 76 | 77 | Crate has 2 options for persistent data paths. One for data (tables), and one 78 | for blobs. 79 | 80 | You can specify both paths (``--crate-data-path``, ``--crate-blob-path``) when 81 | starting the framework. 82 | 83 | If the paths are specified, the executor will check if the path exists on the 84 | slave. If the path does not exist, the executor won't start Crate on that slave. 85 | 86 | 87 | Crate Options 88 | ------------- 89 | 90 | Configuration options for Crate instances can also be passed to the framework. 91 | These options will be passed to the Crate processes launched by the framework. 92 | 93 | All options starting with ``-Des.`` are considered Crate configuration options. 94 | 95 | For example to get the framework to launch instances that will have statistic 96 | collection enabled use the following:: 97 | 98 | java ... -jar crate-mesos-0.x.x.jar --crate-version 0.x.x 99 | -Des.stats.enabled=true 100 | 101 | 102 | Crate Node Tags 103 | --------------- 104 | 105 | Any attributes defined on a Mesos-Slave will be passed to the Crate processes as 106 | node tag with a ``mesos_`` prefix. 107 | 108 | For example if a Mesos-Slave is launched with ``--attributes=zone:a`` the Crate 109 | instance will have the ``node.mesos_zone=a`` tag set. 110 | 111 | This can be used to setup a `Multi Zone Crate Cluster`_. 112 | 113 | Assuming there are 4 slaves, 2 with the attribute ``zone:a`` and 2 with the 114 | attribute ``zone:b``, you would launch the framework with the following options 115 | for a working multi zone setup:: 116 | 117 | java ... -jar crate-mesos-0.1.0.jar --crate-version x.x.x \ 118 | -Des.cluster.routing.allocation.awareness.attributes=mesos_zone \ 119 | -Des.cluster.routing.allocation.awareness.force.mesos_zone.values=a,b 120 | 121 | 122 | Service Discovery 123 | ================= 124 | 125 | Service discovery for applications using DNS 126 | -------------------------------------------- 127 | 128 | For applications to discover the Crate nodes, `Mesos-DNS`_ can be used. 129 | 130 | If `Mesos-DNS` is running it will automatically retrieve information about the 131 | instances launched by the Crate framework and then the client applications can 132 | connect to the crate cluster using the following URL: 133 | 134 | ``.crateframework.:`` 135 | 136 | Both ```` and ```` can be specified when the Mesos 137 | Crate Framework is launched. The ```` is part of the Mesos-DNS 138 | configuration. 139 | 140 | 141 | Run Multiple Crate Clusters using Marathon 142 | ========================================== 143 | 144 | One Crate Framework can only manage one Crate cluster. To manage multiple Crate 145 | clusters it's possible to run the Crate Framework multiple times. 146 | 147 | The easiest and recommended method is to deploy the Crate Framework using 148 | Marathon. This has the advantage that the Crate Framework itself will be 149 | highly available. 150 | 151 | To deploy something on Marathon, create a json file. For example, 152 | ``crate-mesos.json`` with the following content:: 153 | 154 | { 155 | "id": "crate-demo", 156 | "instances": 1, 157 | "cpus": 0.25, 158 | "mem": 128, 159 | "portDefinitions": [ 160 | { 161 | "port": 4040, 162 | "protocol": "tcp", 163 | "name": "api" 164 | } 165 | ], 166 | "requirePorts": true, 167 | "env": { 168 | "CRATE_CLUSTER_NAME": "dev-local", 169 | "CRATE_VERSION": "0.54.8", 170 | "CRATE_HTTP_PORT": "4200", 171 | "CRATE_TRANSPORT_PORT": "4300" 172 | }, 173 | "fetch": [ 174 | { 175 | "uri": 176 | "https://cdn.crate.io/downloads/openjdk/jre-7u80-linux.tar.gz", 177 | "extract": true, 178 | "executable": false, 179 | "cache": false 180 | } 181 | ], 182 | "cmd": "env && $(pwd)/jre/bin/java $JAVA_OPTS -jar 183 | /tmp/crate-mesos-0.1.0.jar --crate-cluster-name $CRATE_CLUSTER_NAME 184 | --crate-version $CRATE_VERSION --api-port $PORT0 --crate-http-port 185 | $CRATE_HTTP_PORT --crate-transport-port $CRATE_TRANSPORT_PORT", 186 | "healthChecks": [ 187 | { 188 | "protocol": "HTTP", 189 | "path": "/cluster", 190 | "gracePeriodSeconds": 3, 191 | "intervalSeconds": 10, 192 | "portIndex": 0, 193 | "timeoutSeconds": 10, 194 | "maxConsecutiveFailures": 3 195 | } 196 | ] 197 | } 198 | 199 | 200 | 201 | You can use curl to instruct Marathon to deploy the Crate framework:: 202 | 203 | curl -s -XPOST http://marathon-url:8080/v2/apps -d@crate-mesos.json -H 204 | "Content-Type: application/json" 205 | 206 | If `Mesos-DNS`_ is available the launched Crate Framework can then be accessed 207 | using ``crate-demo.marathon.mesos``. Where ``crate-demo`` is the id specified in 208 | the ``crate-mesos.json`` and ``mesos`` is the configured `Mesos-DNS`_ domain. 209 | 210 | 211 | .. note:: 212 | 213 | The defined port (4040) must be available. Either extend the ports 214 | definitions in `/etc/mesos-slave/resources` or use a dynamic port (setting 215 | ports to [0]). 216 | 217 | Mesos-DNS also serves SRV records which can be queried to discover on which 218 | port the API is listening:: 219 | 220 | nslookup -querytype=srv _crate-demo._tcp.marathon.mesos 221 | 222 | For each additional cluster another "crate framework app" can be deployed using 223 | Marathon. Keep in mind that each cluster should have unique ports so the port 224 | configuration options should be set in each clusters' ``cmd`` definition. 225 | 226 | 227 | .. _Mesos-DNS: http://mesosphere.github.io/mesos-dns 228 | .. _`Multi Zone Crate Cluster`: https://crate.io/docs/en/latest/best_practice/multi_zone_setup.html 229 | .. _DCOS: https://docs.mesosphere.com/usage/services/crate 230 | -------------------------------------------------------------------------------- /src/test/java/io/crate/frameworks/mesos/CrateSchedulerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor 3 | * license agreements. See the NOTICE file distributed with this work for 4 | * additional information regarding copyright ownership. Crate licenses 5 | * this file to you under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. You may 7 | * obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * However, if you have executed another commercial license agreement 18 | * with Crate these terms will supersede the license and you may use the 19 | * software solely pursuant to the terms of the relevant commercial agreement. 20 | */ 21 | 22 | package io.crate.frameworks.mesos; 23 | 24 | import io.crate.frameworks.mesos.config.Configuration; 25 | import org.apache.mesos.Protos; 26 | import org.apache.mesos.SchedulerDriver; 27 | import org.junit.Before; 28 | import org.junit.Test; 29 | import org.mockito.ArgumentCaptor; 30 | import org.mockito.Captor; 31 | import org.mockito.Mock; 32 | import org.mockito.MockitoAnnotations; 33 | 34 | import java.util.*; 35 | 36 | import static io.crate.frameworks.mesos.SaneProtos.taskID; 37 | import static java.util.Arrays.asList; 38 | import static org.hamcrest.CoreMatchers.is; 39 | import static org.hamcrest.MatcherAssert.assertThat; 40 | import static org.mockito.Matchers.any; 41 | import static org.mockito.Matchers.anyCollectionOf; 42 | import static org.mockito.Mockito.times; 43 | import static org.mockito.Mockito.verify; 44 | import static org.mockito.Mockito.when; 45 | 46 | import static org.junit.Assert.*; 47 | 48 | public class CrateSchedulerTest { 49 | 50 | @Mock 51 | private SchedulerDriver driver; 52 | 53 | private CrateState state; 54 | 55 | @Mock 56 | private PersistentStateStore store; 57 | 58 | private Protos.MasterInfo masterInfo; 59 | 60 | @Captor 61 | private ArgumentCaptor> taskInfoCaptor; 62 | 63 | 64 | @Before 65 | public void setUp() throws Exception { 66 | MockitoAnnotations.initMocks(this); 67 | masterInfo = Protos.MasterInfo.getDefaultInstance(); 68 | state = new CrateState(); 69 | when(store.state()).thenReturn(state); 70 | } 71 | 72 | @Test 73 | public void testThatRegisteredWithInstancesRunning() throws Exception { 74 | CrateInstances instances = new CrateInstances(); 75 | instances.addInstance(new CrateInstance("foo", "1", "0.47.0", 4300, "exec-1", "slave-1")); 76 | state.desiredInstances(0); 77 | state.instances(instances); 78 | 79 | CrateScheduler crateScheduler = initScheduler(new Configuration(), "xx"); 80 | assertThat(crateScheduler.reconcileTasks.size(), is(1)); 81 | } 82 | 83 | @Test 84 | public void testNodeCountParameter() throws Exception { 85 | Configuration conf = new Configuration(); 86 | conf.nodeCount = 2; 87 | initScheduler(conf, "xx"); 88 | assertThat(state.desiredInstances().getValue(), is(2)); 89 | } 90 | 91 | @Test 92 | public void testResourceOffersDoesNotSpawnTooManyTasks() throws Exception { 93 | CrateInstances instances = new CrateInstances(); 94 | state.instances(instances); 95 | 96 | Protos.FrameworkID frameworkID = Protos.FrameworkID.newBuilder().setValue("xx").build(); 97 | Configuration configuration = new Configuration(); 98 | configuration.nodeCount = 4; 99 | CrateScheduler crateScheduler = initScheduler(configuration, frameworkID); 100 | 101 | List offers = new ArrayList<>(); 102 | for (int i = 0; i < 10; i++) { 103 | String idx = Integer.toString(i); 104 | offers.add(Protos.Offer.newBuilder() 105 | .setId(Protos.OfferID.newBuilder().setValue(idx)) 106 | .setHostname(idx) 107 | .setSlaveId(Protos.SlaveID.newBuilder().setValue(idx)) 108 | .setFrameworkId(frameworkID) 109 | .addAllResources(configuration.getAllRequiredResources()).build()); 110 | } 111 | 112 | crateScheduler.resourceOffers(driver, offers); 113 | 114 | verify(driver, times(4)).launchTasks(anyCollectionOf(Protos.OfferID.class), taskInfoCaptor.capture(), any(Protos.Filters.class)); 115 | assertThat(taskInfoCaptor.getValue().size(), is(1)); 116 | } 117 | 118 | @Test 119 | public void testReconcileTasksWithDifferentVersionAlreadyRunning() throws Exception { 120 | Protos.FrameworkID frameworkID = Protos.FrameworkID.newBuilder().setValue("xx").build(); 121 | Configuration configOld = new Configuration(); 122 | configOld.version("0.47.7"); 123 | CrateScheduler crateScheduler = initScheduler(configOld, frameworkID); 124 | 125 | Protos.TaskID task = taskID("1"); 126 | // configured version should be changed to the version of the running instance 127 | CrateInstances instances = new CrateInstances(); 128 | instances.addInstance(new CrateInstance("host1", task.getValue(), "0.47.7", 4300, "exec1", "slave1")); 129 | state.instances(instances); 130 | // for testing we need to remove observer 131 | // otherwise it is not possible to change the value of the desired instances 132 | state.desiredInstances().clearObservers(); 133 | state.desiredInstances(5); 134 | 135 | Configuration configNew = new Configuration(); 136 | configNew.version("0.48.0"); 137 | 138 | crateScheduler = reregisterScheduler(configNew); 139 | crateScheduler.reconcileTasks(driver); 140 | crateScheduler.statusUpdate(driver, 141 | Protos.TaskStatus.newBuilder() 142 | .setTaskId(task) 143 | .setState(Protos.TaskState.TASK_RUNNING).build()); 144 | 145 | // configuration holds the new version 146 | assertThat(configNew.version, is("0.48.0")); 147 | 148 | List offers = new ArrayList<>(); 149 | for (int i = 2; i < 4; i++) { 150 | String idx = Integer.toString(i); 151 | offers.add(Protos.Offer.newBuilder() 152 | .setId(Protos.OfferID.newBuilder().setValue(idx)) 153 | .setHostname("host"+idx) 154 | .setSlaveId(Protos.SlaveID.newBuilder().setValue("slave"+idx)) 155 | .setFrameworkId(frameworkID) 156 | .addAllResources(configNew.getAllRequiredResources()).build()); 157 | } 158 | 159 | crateScheduler.resourceOffers(driver, offers); 160 | 161 | // new instance has still version from existing cluster 162 | assertThat(state.crateInstances().get(0).version(), is("0.47.7")); 163 | assertThat(state.crateInstances().get(1).version(), is("0.47.7")); 164 | 165 | // only after shutting down all instances the new version from the configuration is used 166 | ArrayList taskIds = new ArrayList<>(state.crateInstances().size()); 167 | for (CrateInstance instance : state.crateInstances()) { 168 | taskIds.add(instance.taskId()); 169 | } 170 | for (String taskId : taskIds) { 171 | crateScheduler.statusUpdate(driver, 172 | Protos.TaskStatus.newBuilder() 173 | .setTaskId(taskID(taskId)) 174 | .setState(Protos.TaskState.TASK_KILLED).build()); 175 | } 176 | 177 | crateScheduler.resourceOffers(driver, offers); 178 | assertThat(state.crateInstances().get(0).version(), is("0.48.0")); 179 | assertThat(state.crateInstances().get(1).version(), is("0.48.0")); 180 | } 181 | 182 | private CrateScheduler initScheduler(Configuration configuration, String frameworkID) { 183 | return initScheduler(configuration, Protos.FrameworkID.newBuilder().setValue(frameworkID).build()); 184 | } 185 | 186 | private CrateScheduler initScheduler(Configuration configuration, Protos.FrameworkID frameworkID) { 187 | CrateScheduler crateScheduler = new CrateScheduler(store, configuration); 188 | crateScheduler.registered(driver, frameworkID, masterInfo); 189 | return crateScheduler; 190 | } 191 | 192 | private CrateScheduler reregisterScheduler(Configuration configuration) { 193 | CrateScheduler crateScheduler = new CrateScheduler(store, configuration); 194 | crateScheduler.reregistered(driver, masterInfo); 195 | return crateScheduler; 196 | } 197 | 198 | private static Protos.ExecutorInfo newExecutor(String n) { 199 | return Protos.ExecutorInfo.newBuilder() 200 | .setName("Crate Executor") 201 | .setExecutorId( 202 | Protos.ExecutorID.newBuilder() 203 | .setValue("newExecutor-" + n) 204 | .build() 205 | ) 206 | .setCommand(Protos.CommandInfo.newBuilder() 207 | .setValue(String.format("java -cp crate-mesos.jar io.crate.frameworks.mesos.CrateExecutor")) 208 | .build()) 209 | .build(); 210 | } 211 | 212 | @Test 213 | public void testSlaveExclusion() throws Exception { 214 | Protos.FrameworkID frameworkID = Protos.FrameworkID.newBuilder().setValue("framework-1").build(); 215 | Configuration configuration = new Configuration(); 216 | CrateScheduler scheduler = initScheduler(configuration, frameworkID); 217 | 218 | CrateInstances instances = new CrateInstances(); 219 | state.desiredInstances(1); 220 | state.instances(instances); 221 | 222 | Protos.SlaveID salve1 = Protos.SlaveID.newBuilder() 223 | .setValue("slave-1") 224 | .build(); 225 | 226 | CrateMessage msg = new CrateMessage<>(CrateMessage.Type.MESSAGE_MISSING_RESOURCE, 227 | MessageMissingResource.MISSING_DATA_PATH); 228 | 229 | scheduler.frameworkMessage(driver, newExecutor("1").getExecutorId(), salve1, msg.toStream()); 230 | 231 | List offers = new ArrayList<>(); 232 | offers.add(Protos.Offer.newBuilder() 233 | .setId(Protos.OfferID.newBuilder().setValue("offer1")) 234 | .setHostname("slave1.crate.io") 235 | .setSlaveId(salve1) 236 | .setFrameworkId(frameworkID) 237 | .addAllResources(configuration.getAllRequiredResources()).build()); 238 | 239 | scheduler.resourceOffers(driver, offers); 240 | 241 | final String reason = MessageMissingResource.MISSING_DATA_PATH.reason().toString(); 242 | assertEquals(0, state.crateInstances().size()); 243 | assertEquals(asList("slave-1"), state.excludedSlaveIds()); 244 | assertEquals(asList("slave-1"), state.excludedSlaveIds(reason)); 245 | assertEquals(new HashMap>() { 246 | { 247 | put(reason, asList("slave-1")); 248 | } 249 | }, state.excludedSlaves()); 250 | 251 | Protos.TaskStatus status1 = Protos.TaskStatus.newBuilder() 252 | .setSlaveId(salve1) 253 | .setTaskId(taskID("task-1")) 254 | .setState(Protos.TaskState.TASK_RUNNING) 255 | .build(); 256 | scheduler.statusUpdate(driver, status1); 257 | 258 | scheduler.resourceOffers(driver, offers); 259 | 260 | assertEquals(1, state.crateInstances().size()); 261 | assertEquals(Collections.emptyList(), state.excludedSlaveIds()); 262 | assertEquals(Collections.emptyList(), state.excludedSlaveIds(reason)); 263 | assertEquals(new HashMap>() { 264 | { 265 | put(reason, Collections.emptyList()); 266 | } 267 | }, state.excludedSlaves()); 268 | } 269 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Crate Mesos Framework 2 | Copyright 2013-2015 CRATE Technology GmbH ("Crate") 3 | 4 | 5 | Licensed to CRATE Technology GmbH (referred to in this notice as "Crate") 6 | under one or more contributor license agreements. See the NOTICE file 7 | distributed with this work for additional information regarding copyright 8 | ownership. 9 | 10 | Crate licenses this software to you under the Apache License, Version 2.0. 11 | However, if you have executed another commercial license agreement with 12 | Crate these terms will supersede the license and you may use the software 13 | solely pursuant to the terms of the relevant commercial agreement. 14 | 15 | 16 | ========================================================================= 17 | 18 | 19 | Apache License 20 | Version 2.0, January 2004 21 | http://www.apache.org/licenses/ 22 | 23 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 24 | 25 | 1. Definitions. 26 | 27 | "License" shall mean the terms and conditions for use, reproduction, 28 | and distribution as defined by Sections 1 through 9 of this document. 29 | 30 | "Licensor" shall mean the copyright owner or entity authorized by 31 | the copyright owner that is granting the License. 32 | 33 | "Legal Entity" shall mean the union of the acting entity and all 34 | other entities that control, are controlled by, or are under common 35 | control with that entity. For the purposes of this definition, 36 | "control" means (i) the power, direct or indirect, to cause the 37 | direction or management of such entity, whether by contract or 38 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 39 | outstanding shares, or (iii) beneficial ownership of such entity. 40 | 41 | "You" (or "Your") shall mean an individual or Legal Entity 42 | exercising permissions granted by this License. 43 | 44 | "Source" form shall mean the preferred form for making modifications, 45 | including but not limited to software source code, documentation 46 | source, and configuration files. 47 | 48 | "Object" form shall mean any form resulting from mechanical 49 | transformation or translation of a Source form, including but 50 | not limited to compiled object code, generated documentation, 51 | and conversions to other media types. 52 | 53 | "Work" shall mean the work of authorship, whether in Source or 54 | Object form, made available under the License, as indicated by a 55 | copyright notice that is included in or attached to the work 56 | (an example is provided in the Appendix below). 57 | 58 | "Derivative Works" shall mean any work, whether in Source or Object 59 | form, that is based on (or derived from) the Work and for which the 60 | editorial revisions, annotations, elaborations, or other modifications 61 | represent, as a whole, an original work of authorship. For the purposes 62 | of this License, Derivative Works shall not include works that remain 63 | separable from, or merely link (or bind by name) to the interfaces of, 64 | the Work and Derivative Works thereof. 65 | 66 | "Contribution" shall mean any work of authorship, including 67 | the original version of the Work and any modifications or additions 68 | to that Work or Derivative Works thereof, that is intentionally 69 | submitted to Licensor for inclusion in the Work by the copyright owner 70 | or by an individual or Legal Entity authorized to submit on behalf of 71 | the copyright owner. For the purposes of this definition, "submitted" 72 | means any form of electronic, verbal, or written communication sent 73 | to the Licensor or its representatives, including but not limited to 74 | communication on electronic mailing lists, source code control systems, 75 | and issue tracking systems that are managed by, or on behalf of, the 76 | Licensor for the purpose of discussing and improving the Work, but 77 | excluding communication that is conspicuously marked or otherwise 78 | designated in writing by the copyright owner as "Not a Contribution." 79 | 80 | "Contributor" shall mean Licensor and any individual or Legal Entity 81 | on behalf of whom a Contribution has been received by Licensor and 82 | subsequently incorporated within the Work. 83 | 84 | 2. Grant of Copyright License. Subject to the terms and conditions of 85 | this License, each Contributor hereby grants to You a perpetual, 86 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 87 | copyright license to reproduce, prepare Derivative Works of, 88 | publicly display, publicly perform, sublicense, and distribute the 89 | Work and such Derivative Works in Source or Object form. 90 | 91 | 3. Grant of Patent License. Subject to the terms and conditions of 92 | this License, each Contributor hereby grants to You a perpetual, 93 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 94 | (except as stated in this section) patent license to make, have made, 95 | use, offer to sell, sell, import, and otherwise transfer the Work, 96 | where such license applies only to those patent claims licensable 97 | by such Contributor that are necessarily infringed by their 98 | Contribution(s) alone or by combination of their Contribution(s) 99 | with the Work to which such Contribution(s) was submitted. If You 100 | institute patent litigation against any entity (including a 101 | cross-claim or counterclaim in a lawsuit) alleging that the Work 102 | or a Contribution incorporated within the Work constitutes direct 103 | or contributory patent infringement, then any patent licenses 104 | granted to You under this License for that Work shall terminate 105 | as of the date such litigation is filed. 106 | 107 | 4. Redistribution. You may reproduce and distribute copies of the 108 | Work or Derivative Works thereof in any medium, with or without 109 | modifications, and in Source or Object form, provided that You 110 | meet the following conditions: 111 | 112 | (a) You must give any other recipients of the Work or 113 | Derivative Works a copy of this License; and 114 | 115 | (b) You must cause any modified files to carry prominent notices 116 | stating that You changed the files; and 117 | 118 | (c) You must retain, in the Source form of any Derivative Works 119 | that You distribute, all copyright, patent, trademark, and 120 | attribution notices from the Source form of the Work, 121 | excluding those notices that do not pertain to any part of 122 | the Derivative Works; and 123 | 124 | (d) If the Work includes a "NOTICE" text file as part of its 125 | distribution, then any Derivative Works that You distribute must 126 | include a readable copy of the attribution notices contained 127 | within such NOTICE file, excluding those notices that do not 128 | pertain to any part of the Derivative Works, in at least one 129 | of the following places: within a NOTICE text file distributed 130 | as part of the Derivative Works; within the Source form or 131 | documentation, if provided along with the Derivative Works; or, 132 | within a display generated by the Derivative Works, if and 133 | wherever such third-party notices normally appear. The contents 134 | of the NOTICE file are for informational purposes only and 135 | do not modify the License. You may add Your own attribution 136 | notices within Derivative Works that You distribute, alongside 137 | or as an addendum to the NOTICE text from the Work, provided 138 | that such additional attribution notices cannot be construed 139 | as modifying the License. 140 | 141 | You may add Your own copyright statement to Your modifications and 142 | may provide additional or different license terms and conditions 143 | for use, reproduction, or distribution of Your modifications, or 144 | for any such Derivative Works as a whole, provided Your use, 145 | reproduction, and distribution of the Work otherwise complies with 146 | the conditions stated in this License. 147 | 148 | 5. Submission of Contributions. Unless You explicitly state otherwise, 149 | any Contribution intentionally submitted for inclusion in the Work 150 | by You to the Licensor shall be under the terms and conditions of 151 | this License, without any additional terms or conditions. 152 | Notwithstanding the above, nothing herein shall supersede or modify 153 | the terms of any separate license agreement you may have executed 154 | with Licensor regarding such Contributions. 155 | 156 | 6. Trademarks. This License does not grant permission to use the trade 157 | names, trademarks, service marks, or product names of the Licensor, 158 | except as required for reasonable and customary use in describing the 159 | origin of the Work and reproducing the content of the NOTICE file. 160 | 161 | 7. Disclaimer of Warranty. Unless required by applicable law or 162 | agreed to in writing, Licensor provides the Work (and each 163 | Contributor provides its Contributions) on an "AS IS" BASIS, 164 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 165 | implied, including, without limitation, any warranties or conditions 166 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 167 | PARTICULAR PURPOSE. You are solely responsible for determining the 168 | appropriateness of using or redistributing the Work and assume any 169 | risks associated with Your exercise of permissions under this License. 170 | 171 | 8. Limitation of Liability. In no event and under no legal theory, 172 | whether in tort (including negligence), contract, or otherwise, 173 | unless required by applicable law (such as deliberate and grossly 174 | negligent acts) or agreed to in writing, shall any Contributor be 175 | liable to You for damages, including any direct, indirect, special, 176 | incidental, or consequential damages of any character arising as a 177 | result of this License or out of the use or inability to use the 178 | Work (including but not limited to damages for loss of goodwill, 179 | work stoppage, computer failure or malfunction, or any and all 180 | other commercial damages or losses), even if such Contributor 181 | has been advised of the possibility of such damages. 182 | 183 | 9. Accepting Warranty or Additional Liability. While redistributing 184 | the Work or Derivative Works thereof, You may choose to offer, 185 | and charge a fee for, acceptance of support, warranty, indemnity, 186 | or other liability obligations and/or rights consistent with this 187 | License. However, in accepting such obligations, You may act only 188 | on Your own behalf and on Your sole responsibility, not on behalf 189 | of any other Contributor, and only if You agree to indemnify, 190 | defend, and hold each Contributor harmless for any liability 191 | incurred by, or claims asserted against, such Contributor by reason 192 | of your accepting any such warranty or additional liability. 193 | 194 | END OF TERMS AND CONDITIONS 195 | 196 | APPENDIX: How to apply the Apache License to your work. 197 | 198 | To apply the Apache License to your work, attach the following 199 | boilerplate notice, with the fields enclosed by brackets "[]" 200 | replaced with your own identifying information. (Don't include 201 | the brackets!) The text should be enclosed in the appropriate 202 | comment syntax for the file format. We also recommend that a 203 | file or class name and description of purpose be included on the 204 | same "printed page" as the copyright notice for easier 205 | identification within third-party archives. 206 | 207 | Copyright [yyyy] [name of copyright owner] 208 | 209 | Licensed under the Apache License, Version 2.0 (the "License"); 210 | you may not use this file except in compliance with the License. 211 | You may obtain a copy of the License at 212 | 213 | http://www.apache.org/licenses/LICENSE-2.0 214 | 215 | Unless required by applicable law or agreed to in writing, software 216 | distributed under the License is distributed on an "AS IS" BASIS, 217 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 218 | See the License for the specific language governing permissions and 219 | limitations under the License. 220 | --------------------------------------------------------------------------------