├── .drone.yml
├── .gitignore
├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── .pullapprove.yml
├── README.md
├── mesos-starter-sample
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── containersolutions
│ │ └── mesos
│ │ └── SampleApplication.java
│ └── resources
│ ├── application.yml
│ └── banner.txt
├── mvnw
├── mvnw.cmd
├── pom.xml
└── spring-boot-starter-mesos
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── containersolutions
│ │ └── mesos
│ │ ├── config
│ │ ├── annotation
│ │ │ └── EnableMesosScheduler.java
│ │ ├── autoconfigure
│ │ │ └── MesosSchedulerConfiguration.java
│ │ └── validation
│ │ │ └── MesosSchedulerPropertiesValidator.java
│ │ ├── scheduler
│ │ ├── CommandInfoMesosProtoFactory.java
│ │ ├── CredentialFactory.java
│ │ ├── ExecutionParameters.java
│ │ ├── FrameworkInfoFactory.java
│ │ ├── InstanceCount.java
│ │ ├── MesosProtoFactory.java
│ │ ├── OfferStrategyFilter.java
│ │ ├── TaskInfoFactory.java
│ │ ├── TaskInfoFactoryCommand.java
│ │ ├── TaskInfoFactoryDocker.java
│ │ ├── TaskMaterializer.java
│ │ ├── TaskMaterializerMinimal.java
│ │ ├── TaskProposal.java
│ │ ├── TaskReaper.java
│ │ ├── TeardownManager.java
│ │ ├── UniversalScheduler.java
│ │ ├── config
│ │ │ ├── DockerConfigProperties.java
│ │ │ ├── MesosConfigProperties.java
│ │ │ ├── ResourcePortConfigProperties.java
│ │ │ ├── ResourceVolumeConfigProperties.java
│ │ │ ├── ResourcesConfigProperties.java
│ │ │ └── ZookeeperConfigProperties.java
│ │ ├── events
│ │ │ ├── ErrorEvent.java
│ │ │ ├── ExecutorLostEvent.java
│ │ │ ├── FrameworkMessageEvent.java
│ │ │ ├── FrameworkRegistreredEvent.java
│ │ │ ├── FrameworkRemovedEvent.java
│ │ │ ├── FrameworkReregistreredEvent.java
│ │ │ ├── InstanceCountChangeEvent.java
│ │ │ ├── MesosEvent.java
│ │ │ ├── SlaveLostEvent.java
│ │ │ ├── StatusUpdateEvent.java
│ │ │ └── TearDownFrameworkEvent.java
│ │ ├── requirements
│ │ │ ├── DistinctSlaveRequirement.java
│ │ │ ├── InstancesCountRequirement.java
│ │ │ ├── OfferEvaluation.java
│ │ │ ├── PortMapping.java
│ │ │ ├── PortsRequirement.java
│ │ │ ├── ResourceRequirement.java
│ │ │ ├── RoleRequirement.java
│ │ │ ├── VolumeMapping.java
│ │ │ └── VolumesRequirement.java
│ │ └── state
│ │ │ ├── StateRepository.java
│ │ │ ├── StateRepositoryFile.java
│ │ │ └── StateRepositoryZookeeper.java
│ │ └── utils
│ │ ├── MesosHelper.java
│ │ └── StreamHelper.java
└── resources
│ └── META-INF
│ └── spring.factories
└── test
└── java
└── com
└── containersolutions
└── mesos
├── TestHelper.java
├── config
└── validation
│ └── MesosSchedulerPropertiesValidatorTest.java
└── scheduler
├── FrameworkInfoFactoryTest.java
├── OfferStrategyFilterTest.java
├── TaskReaperTest.java
├── UniversalSchedulerTest.java
└── requirements
├── DistinctSlaveRequirementTest.java
├── InstancesCountRequirementTest.java
└── PortsRequirementTest.java
/.drone.yml:
--------------------------------------------------------------------------------
1 | pipeline:
2 | publish:
3 | image: maven:3.5-jdk-9
4 | environment:
5 | - MAVEN_USERNAME=drone
6 | secrets:
7 | - maven_password
8 | commands:
9 | - mkdir -p ~/.m2
10 | - >
11 | echo '
12 |
13 |
20 |
21 |
22 | humio-releases
23 | $${MAVEN_USERNAME}
24 | $${MAVEN_PASSWORD}
25 |
26 |
27 | humio-public
28 | $${MAVEN_USERNAME}
29 | $${MAVEN_PASSWORD}
30 |
31 |
32 | ' > ~/.m2/settings.xml
33 | - mvn -B versions:set versions:commit -DnewVersion=build-${DRONE_BUILD_NUMBER}
34 | - mvn -B deploy
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 |
3 | *.iml
4 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ContainerSolutions/mesos-starter/22fb58bf10fc6c726b34117220d54def141beacc/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip
--------------------------------------------------------------------------------
/.pullapprove.yml:
--------------------------------------------------------------------------------
1 | approve_by_comment: true
2 | approve_regex: '^(Approved|LGTM)'
3 | reject_regex: ^Rejected
4 | reset_on_push: false
5 | reviewers:
6 | -
7 | name: admins
8 | members:
9 | - mwl
10 | required: 1
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spring Boot starter for Mesos
2 |
3 | [](https://gitter.im/ContainerSolutions/mesos-starter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 |
5 | Spring Boot starter package for writing Mesos frameworks
6 |
7 | ## Features
8 | - Vertical scaling
9 | - Deploy executor on all slaves
10 | - Support for Docker containers
11 |
12 | ## Getting Started
13 | Start by adding the `spring-boot-starter-mesos` dependency to your project
14 |
15 | ```
16 |
17 | com.github.containersolutions.mesos-starter
18 | spring-boot-starter-mesos
19 | 0.1
20 |
21 | ```
22 |
23 | Make sure your Spring Boot application has a name, by adding the `spring.application.name` to the `application.properties` file i.e.
24 |
25 | ```
26 | spring.application.name=Sample application
27 | ```
28 |
29 | To run three instances of a Docker image add the following in `application.properties`
30 |
31 | ```
32 | mesos.resources.distinctSlave=true
33 | mesos.resources.count=3
34 | mesos.resources.cpus=0.1
35 | mesos.resources.mem=64
36 | mesos.docker.image=tutum/hello-world:latest
37 | ```
38 |
39 | That is all you need to do if you have annotated your Spring configuration with `@SpringBootApplication` or `@EnableAutoConfiguration`.
40 |
41 | ```
42 | @SpringBootApplication
43 | public class SampleApplication {
44 |
45 | public static void main(String[] args) {
46 | SpringApplication.run(SampleApplication.class, args);
47 | }
48 | }
49 | ```
50 |
51 | For a complete example, see `mesos-starter-sample` module.
52 |
53 | ## Starting the application
54 | The only required parameter for the scheduler is `mesos.master`. The value of the parameter is passed directly to the Mesos Scheduler Driver which allows the following formats
55 |
56 | - `host:port`
57 | - `zk://host1:port1,host2:port2,.../path`
58 | - `zk://username:password@host1:port1,host2:port2,.../path`
59 | - `file:///path/to/file`
60 |
61 | For resiliency it is also recommended to point the application to a Zookeeper cluster and giving the instance a name.
62 | ```
63 | mesos.zookeeper.server=zookeeper:2181
64 | mesos.framework.name=sampleApp1
65 | ```
66 |
67 | The purpose of `mesos.framework.name` is to distinguish instances of the scheduler.
68 |
69 | ## Running tasks
70 | Currently only Docker and shell commands are supported.
71 |
72 | ### Docker
73 | Run any Docker image by setting the `mesos.docker.image` property. Eventually by overriding the `CMD` by `mesos.docker.command`
74 |
75 | ### Shell command
76 | Run a command on by setting the `mesos.shell.command` property.
77 |
78 | ### Custom task
79 | Extend the `TaskInfoFactory` class to create your own task.
80 |
81 | # Offers evaluation
82 | Mesos-starter offers a set of offer evaluation rules
83 | - Physical requirements
84 | - Distinct slave
85 | - Instances count
86 | - Role assigned
87 |
88 | They all work in combination with each other, though this might change in the future.
89 |
90 | ## Physical requirements
91 | Reject offers that does not have the required amount of either CPUs, memory or ports.
92 |
93 | ### Ports
94 | The ports requirement serves two purposes. Grapping ports and mapping them to the application.
95 | A very base configuration looks like this
96 | ```
97 | mesos.resources.ports.http.host=ANY
98 | mesos.command=runwebserver.sh --port=$HTTP
99 | ```
100 | This will grap any unprivileged port, and expose it in an environment variable. Value of `.host` can be any off
101 | - `ANY`, `UNPRIVILEGED` will reserve any any port above 1024
102 | - `PRIVILEGED` will only reserve a port below 1024 (included)
103 | - *Any positive number* will only reserve a fixed port.
104 |
105 | When running with containers you can add the `.container` to map it to a container port when running in bridge mode, i.e.
106 |
107 | ```
108 | mesos.resources.ports.http.host=ANY
109 | mesos.resources.ports.http.container=80
110 | mesos.docker.network=BRIDGE
111 | mesos.docker.image=tutum/hello-world
112 | ```
113 |
114 | This will reserve any port above 1024 and let docker map it to port 80 on the container.
115 |
116 | ## Distinct slave
117 | This rule will make sure that offers for hosts where the application is already running are being rejected.
118 |
119 | ## Instances count
120 | This rule will make sure that only a certain number of instances are running in the Mesos cluster. The instances count is exposed as a managed bean that can be accessed through Actuator Management API. Furthermore it'll also be possible to insert your own instances count bean.
121 | It is recommended to have this rule enabled in most cases.
122 |
123 | ## Role assigned
124 | This rule only accept offers assigned to the Role defined in `mesos.role`.
125 |
126 | ## Framework Authentication/Authorisation
127 | To use [Framework Authentication](http://mesos.apache.org/documentation/latest/authentication/), please pass the following settings:
128 |
129 | | Command | Description | Default | Required |
130 | | --- | --- | --- | --- |
131 | | mesos.principal | The Mesos principal | | |
132 | | mesos.secret | The Mesos secret | | |
133 |
134 | # Use cases
135 |
136 | A few good examples
137 |
138 | ## Stateless web application
139 | For a stateless web application that can run anywhere in the cluster with only a requirement for a single network port, the following should be sufficient
140 |
141 | ```
142 | mesos.resources.count=3
143 | mesos.resources.cpus=0.1
144 | mesos.resources.mem=64
145 | mesos.resources.ports=1
146 | ```
147 |
148 | This will run 3 instances of the application with one port exposed. Bare in mind that they all might run on the very same host.
149 |
150 | ## Distributed database application
151 | For a distributed database you want to run a certain number of instances and never more than one on every host. To achieve that you can enable `count` and `distinctSlave`, like
152 |
153 | ```
154 | mesos.resources.count=3
155 | mesos.resources.distinctSlave=true
156 | mesos.resources.cpus=0.1
157 | mesos.resources.mem=64
158 | mesos.resources.ports=1
159 | ```
160 |
161 | ## Cluster wide system daemon
162 | Often operations would like to run a single application on each host in the cluster to harvest information from every single node. This can be achieved by not adding the instances count rule and adding the Distinct slave rule.
163 |
164 | ```
165 | mesos.resources.distinctSlave=true
166 | mesos.resources.cpus=0.1
167 | mesos.resources.mem=64
168 | ```
169 |
170 | Another, safer, way to achieve the same result is by assigning resources to a specific role on all nodes. I.e. by adding the following to `/etc/mesos-slave/resources`
171 | ```
172 | cpus(sampleDaemon):0.2; mem(sampleDaemon):64; ports(sampleDaemon):[514-514];
173 | ```
174 |
175 | And configure the scheduler with the following options
176 |
177 | ```
178 | mesos.role=sampleDaemon
179 | mesos.resources.distinctSlave=true
180 | mesos.resources.role=all
181 | ```
182 |
183 | This way the scheduler will take all resources allocated to the role and make sure it's only running once in every single slave.
184 |
185 | It is always recommended to run the scheduler with a Mesos role and reserved resources in such cases to make sure that scheduler is being offered resources for all nodes in the cluster.
186 |
187 | ### Framework shutdown
188 | The scheduler can survive `SIGKILL` or being lost (system crashes etc.). If you want to completely de-register the framework and shutdown all tasks, just stop the scheduler with a plain `SIGTERM`.
189 |
--------------------------------------------------------------------------------
/mesos-starter-sample/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | com.containersolutions.mesos
9 | mesos-starter
10 | 1.0-SNAPSHOT
11 |
12 |
13 | mesos-starter-sample
14 |
15 |
16 |
17 | org.springframework.boot
18 | spring-boot-starter
19 |
20 |
21 | ${project.groupId}
22 | spring-boot-starter-mesos
23 | ${project.version}
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/mesos-starter-sample/src/main/java/com/containersolutions/mesos/SampleApplication.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class SampleApplication {
8 | public static void main(String[] args) {
9 | SpringApplication.run(SampleApplication.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/mesos-starter-sample/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | application:
3 | name: Sample App
4 |
5 | mesos:
6 | command: sleep 600
7 | resources:
8 | # distinctSlave: true
9 | count: 1
10 | cpus: 0.1
11 | mem: 64
12 | # ports:
13 | # nc-port:
14 | # host: 31101
15 | # container: 80
16 | # volumes:
17 | # - hostPath: /etc
18 | # containerPath: /hostetc
19 | docker:
20 | # image: tutum/hello-world:latest
21 | image: alpine
22 |
--------------------------------------------------------------------------------
/mesos-starter-sample/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ${AnsiColor.BRIGHT_BLUE} _____ _________ __ __ ${AnsiColor.DEFAULT} .__
2 | ${AnsiColor.BRIGHT_BLUE} / \ ____ __________ ______ / _____// |______ ________/ |_ ___________ ${AnsiColor.DEFAULT} ___________ _____ ______ | | ____
3 | ${AnsiColor.BRIGHT_BLUE} / \ / \_/ __ \ / ___/ _ \/ ___/ ______ \_____ \\ __\__ \\_ __ \ __\/ __ \_ __ \${AnsiColor.DEFAULT} / ___/\__ \ / \\____ \| | _/ __ \
4 | ${AnsiColor.BRIGHT_BLUE}/ Y \ ___/ \___ ( <_> )___ \ /_____/ / \| | / __ \| | \/| | \ ___/| | \/${AnsiColor.DEFAULT} \___ \ / __ \| Y Y \ |_> > |_\ ___/
5 | ${AnsiColor.BRIGHT_BLUE}\____|__ /\___ >____ >____/____ > /_______ /|__| (____ /__| |__| \___ >__| ${AnsiColor.DEFAULT} /____ >(____ /__|_| / __/|____/\___ >
6 | ${AnsiColor.BRIGHT_BLUE} \/ \/ \/ \/ \/ \/ \/ ${AnsiColor.DEFAULT} \/ \/ \/|__| \/
7 | :: Running Spring Boot ${spring-boot.version} ::${AnsiColor.BLACK}
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | #
58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look
59 | # for the new JDKs provided by Oracle.
60 | #
61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
62 | #
63 | # Apple JDKs
64 | #
65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
66 | fi
67 |
68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
69 | #
70 | # Apple JDKs
71 | #
72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
73 | fi
74 |
75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
76 | #
77 | # Oracle JDKs
78 | #
79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
80 | fi
81 |
82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
83 | #
84 | # Apple JDKs
85 | #
86 | export JAVA_HOME=`/usr/libexec/java_home`
87 | fi
88 | ;;
89 | esac
90 |
91 | if [ -z "$JAVA_HOME" ] ; then
92 | if [ -r /etc/gentoo-release ] ; then
93 | JAVA_HOME=`java-config --jre-home`
94 | fi
95 | fi
96 |
97 | if [ -z "$M2_HOME" ] ; then
98 | ## resolve links - $0 may be a link to maven's home
99 | PRG="$0"
100 |
101 | # need this for relative symlinks
102 | while [ -h "$PRG" ] ; do
103 | ls=`ls -ld "$PRG"`
104 | link=`expr "$ls" : '.*-> \(.*\)$'`
105 | if expr "$link" : '/.*' > /dev/null; then
106 | PRG="$link"
107 | else
108 | PRG="`dirname "$PRG"`/$link"
109 | fi
110 | done
111 |
112 | saveddir=`pwd`
113 |
114 | M2_HOME=`dirname "$PRG"`/..
115 |
116 | # make it fully qualified
117 | M2_HOME=`cd "$M2_HOME" && pwd`
118 |
119 | cd "$saveddir"
120 | # echo Using m2 at $M2_HOME
121 | fi
122 |
123 | # For Cygwin, ensure paths are in UNIX format before anything is touched
124 | if $cygwin ; then
125 | [ -n "$M2_HOME" ] &&
126 | M2_HOME=`cygpath --unix "$M2_HOME"`
127 | [ -n "$JAVA_HOME" ] &&
128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
129 | [ -n "$CLASSPATH" ] &&
130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
131 | fi
132 |
133 | # For Migwn, ensure paths are in UNIX format before anything is touched
134 | if $mingw ; then
135 | [ -n "$M2_HOME" ] &&
136 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
137 | [ -n "$JAVA_HOME" ] &&
138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
139 | # TODO classpath?
140 | fi
141 |
142 | if [ -z "$JAVA_HOME" ]; then
143 | javaExecutable="`which javac`"
144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
145 | # readlink(1) is not available as standard on Solaris 10.
146 | readLink=`which readlink`
147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
148 | if $darwin ; then
149 | javaHome="`dirname \"$javaExecutable\"`"
150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
151 | else
152 | javaExecutable="`readlink -f \"$javaExecutable\"`"
153 | fi
154 | javaHome="`dirname \"$javaExecutable\"`"
155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
156 | JAVA_HOME="$javaHome"
157 | export JAVA_HOME
158 | fi
159 | fi
160 | fi
161 |
162 | if [ -z "$JAVACMD" ] ; then
163 | if [ -n "$JAVA_HOME" ] ; then
164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
165 | # IBM's JDK on AIX uses strange locations for the executables
166 | JAVACMD="$JAVA_HOME/jre/sh/java"
167 | else
168 | JAVACMD="$JAVA_HOME/bin/java"
169 | fi
170 | else
171 | JAVACMD="`which java`"
172 | fi
173 | fi
174 |
175 | if [ ! -x "$JAVACMD" ] ; then
176 | echo "Error: JAVA_HOME is not defined correctly." >&2
177 | echo " We cannot execute $JAVACMD" >&2
178 | exit 1
179 | fi
180 |
181 | if [ -z "$JAVA_HOME" ] ; then
182 | echo "Warning: JAVA_HOME environment variable is not set."
183 | fi
184 |
185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
186 |
187 | # For Cygwin, switch paths to Windows format before running java
188 | if $cygwin; then
189 | [ -n "$M2_HOME" ] &&
190 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
191 | [ -n "$JAVA_HOME" ] &&
192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
193 | [ -n "$CLASSPATH" ] &&
194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
195 | fi
196 |
197 | # traverses directory structure from process work directory to filesystem root
198 | # first directory with .mvn subdirectory is considered project base directory
199 | find_maven_basedir() {
200 | local basedir=$(pwd)
201 | local wdir=$(pwd)
202 | while [ "$wdir" != '/' ] ; do
203 | if [ -d "$wdir"/.mvn ] ; then
204 | basedir=$wdir
205 | break
206 | fi
207 | wdir=$(cd "$wdir/.."; pwd)
208 | done
209 | echo "${basedir}"
210 | }
211 |
212 | # concatenates all lines of a file
213 | concat_lines() {
214 | if [ -f "$1" ]; then
215 | echo "$(tr -s '\n' ' ' < "$1")"
216 | fi
217 | }
218 |
219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
221 |
222 | # Provide a "standardized" way to retrieve the CLI args that will
223 | # work with both Windows and non-Windows executions.
224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
225 | export MAVEN_CMD_LINE_ARGS
226 |
227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
228 |
229 | exec "$JAVACMD" \
230 | $MAVEN_OPTS \
231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
233 | ${WRAPPER_LAUNCHER} $MAVEN_CMD_LINE_ARGS
234 |
235 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
40 |
41 | @REM set %HOME% to equivalent of $HOME
42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43 |
44 | @REM Execute a user defined script before this one
45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49 | :skipRcPre
50 |
51 | @setlocal
52 |
53 | set ERROR_CODE=0
54 |
55 | @REM To isolate internal variables from possible post scripts, we use another setlocal
56 | @setlocal
57 |
58 | @REM ==== START VALIDATION ====
59 | if not "%JAVA_HOME%" == "" goto OkJHome
60 |
61 | echo.
62 | echo Error: JAVA_HOME not found in your environment. >&2
63 | echo Please set the JAVA_HOME variable in your environment to match the >&2
64 | echo location of your Java installation. >&2
65 | echo.
66 | goto error
67 |
68 | :OkJHome
69 | if exist "%JAVA_HOME%\bin\java.exe" goto init
70 |
71 | echo.
72 | echo Error: JAVA_HOME is set to an invalid directory. >&2
73 | echo JAVA_HOME = "%JAVA_HOME%" >&2
74 | echo Please set the JAVA_HOME variable in your environment to match the >&2
75 | echo location of your Java installation. >&2
76 | echo.
77 | goto error
78 |
79 | @REM ==== END VALIDATION ====
80 |
81 | :init
82 |
83 | set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %*
84 |
85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86 | @REM Fallback to current working directory if not found.
87 |
88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90 |
91 | set EXEC_DIR=%CD%
92 | set WDIR=%EXEC_DIR%
93 | :findBaseDir
94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
95 | cd ..
96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
97 | set WDIR=%CD%
98 | goto findBaseDir
99 |
100 | :baseDirFound
101 | set MAVEN_PROJECTBASEDIR=%WDIR%
102 | cd "%EXEC_DIR%"
103 | goto endDetectBaseDir
104 |
105 | :baseDirNotFound
106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107 | cd "%EXEC_DIR%"
108 |
109 | :endDetectBaseDir
110 |
111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112 |
113 | @setlocal EnableExtensions EnableDelayedExpansion
114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116 |
117 | :endReadAdditionalConfig
118 |
119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120 |
121 | set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar""
122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
123 |
124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS%
125 | if ERRORLEVEL 1 goto error
126 | goto end
127 |
128 | :error
129 | set ERROR_CODE=1
130 |
131 | :end
132 | @endlocal & set ERROR_CODE=%ERROR_CODE%
133 |
134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
138 | :skipRcPost
139 |
140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
142 |
143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
144 |
145 | exit /B %ERROR_CODE%
146 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.containersolutions.mesos
8 | mesos-starter
9 | 1.0-SNAPSHOT
10 | pom
11 |
12 | Mesos starter
13 | Spring Boot starter package for Mesos
14 | https://github.com/containersolutions/mesos-starter
15 |
16 |
17 | The Apache License, Version 2.0
18 | http://www.apache.org/licenses/LICENSE-2.0.txt
19 |
20 |
21 |
22 |
23 | Martin Westergaard Lassen
24 | martin@mwl.dk
25 |
26 |
27 |
28 | scm:git:git@github.com:containersolutions/mesos-starter.git
29 | scm:git:git@github.com:containersolutions/mesos-starter.git
30 | git@github.com:containersolutions/mesos-starter.git
31 |
32 |
33 |
34 | true
35 |
36 |
37 |
38 | spring-boot-starter-mesos
39 | mesos-starter-sample
40 |
41 |
42 |
43 |
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-dependencies
48 | 2.0.4.RELEASE
49 | pom
50 | import
51 |
52 |
53 |
54 |
55 |
56 |
57 | humio-releases
58 | Humio releases
59 | https://repo.humio.com/repository/maven-releases/
60 |
61 |
62 | humio-snapshots
63 | Humio snapshots
64 | https://repo.humio.com/repository/maven-snapshots/
65 |
66 |
67 |
68 |
69 |
70 |
71 | org.apache.maven.plugins
72 | maven-compiler-plugin
73 | 3.3
74 | true
75 |
76 | 1.8
77 | 1.8
78 |
79 |
80 |
81 | org.apache.maven.plugins
82 | maven-javadoc-plugin
83 | 2.10.3
84 | true
85 |
86 |
87 |
88 | jar
89 |
90 |
91 |
92 |
93 |
94 | org.apache.maven.plugins
95 | maven-source-plugin
96 | 3.0.0
97 | true
98 |
99 |
100 |
101 | jar
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | com.containersolutions.mesos
9 | mesos-starter
10 | 1.0-SNAPSHOT
11 |
12 |
13 | spring-boot-starter-mesos
14 |
15 |
16 |
17 | org.springframework.boot
18 | spring-boot-starter
19 |
20 |
21 | org.springframework.boot
22 | spring-boot-configuration-processor
23 |
24 |
25 | org.apache.mesos
26 | mesos
27 | 1.5.0
28 |
29 |
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-test
34 | test
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/config/annotation/EnableMesosScheduler.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.config.annotation;
2 |
3 | import com.containersolutions.mesos.config.autoconfigure.MesosSchedulerConfiguration;
4 | import org.springframework.context.annotation.Import;
5 |
6 | import java.lang.annotation.ElementType;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.RetentionPolicy;
9 | import java.lang.annotation.Target;
10 |
11 | @Target(ElementType.TYPE)
12 | @Retention(RetentionPolicy.RUNTIME)
13 | @Import(MesosSchedulerConfiguration.class)
14 | public @interface EnableMesosScheduler {
15 | }
16 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/config/autoconfigure/MesosSchedulerConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.config.autoconfigure;
2 |
3 | import com.containersolutions.mesos.config.validation.MesosSchedulerPropertiesValidator;
4 | import com.containersolutions.mesos.scheduler.*;
5 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
6 | import com.containersolutions.mesos.scheduler.requirements.*;
7 | import com.containersolutions.mesos.scheduler.state.StateRepository;
8 | import com.containersolutions.mesos.scheduler.state.StateRepositoryFile;
9 | import com.containersolutions.mesos.scheduler.state.StateRepositoryZookeeper;
10 | import org.apache.mesos.Protos;
11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
13 | import org.springframework.context.ApplicationEventPublisher;
14 | import org.springframework.context.annotation.Bean;
15 | import org.springframework.context.annotation.Configuration;
16 | import org.springframework.core.Ordered;
17 | import org.springframework.core.annotation.Order;
18 | import org.springframework.core.env.Environment;
19 |
20 | import java.time.Clock;
21 | import java.util.Collections;
22 | import java.util.Map;
23 | import java.util.UUID;
24 | import java.util.concurrent.atomic.AtomicMarkableReference;
25 | import java.util.function.Supplier;
26 |
27 | @Configuration
28 | public class MesosSchedulerConfiguration {
29 |
30 | @Bean
31 | public Clock systemClock() {
32 | return Clock.systemUTC();
33 | }
34 |
35 | @Bean
36 | public MesosSchedulerPropertiesValidator configurationPropertiesValidator() {
37 | return new MesosSchedulerPropertiesValidator();
38 | }
39 |
40 | @Bean
41 | @ConditionalOnMissingBean
42 | public UniversalScheduler scheduler(MesosConfigProperties mesosConfig, OfferStrategyFilter offerStrategyFilter, ApplicationEventPublisher applicationEventPublisher, Supplier uuidSupplier, StateRepository stateRepository, TaskMaterializer taskMaterializer, FrameworkInfoFactory frameworkInfoFactory, CredentialFactory credentialFactory) {
43 | return new UniversalScheduler(mesosConfig, offerStrategyFilter, applicationEventPublisher, uuidSupplier, stateRepository, taskMaterializer, frameworkInfoFactory, credentialFactory);
44 | }
45 |
46 | @Bean
47 | public OfferStrategyFilter offerStrategyFilter(Map resourceRequirements) {
48 | return new OfferStrategyFilter(resourceRequirements);
49 | }
50 |
51 | private ResourceRequirement simpleScalarRequirement(String name, double minimumRequirement) {
52 | return (requirement, taskId, offer) -> {
53 | if (ResourceRequirement.scalarSum(offer, name) > minimumRequirement) {
54 | return OfferEvaluation.accept(
55 | requirement,
56 | taskId,
57 | offer,
58 | Collections.emptyMap(),
59 | Collections.emptyList(),
60 | Collections.emptyList(),
61 | Protos.Resource.newBuilder()
62 | .setType(Protos.Value.Type.SCALAR)
63 | .setName(name)
64 | .setRole(offer.getResourcesList().stream().filter(resource -> resource.getName().equalsIgnoreCase(name)).findFirst().map(Protos.Resource::getRole).orElse("*"))
65 | .setScalar(Protos.Value.Scalar.newBuilder().setValue(minimumRequirement))
66 | .build()
67 | );
68 | }
69 | return OfferEvaluation.decline(requirement, taskId, offer, "Not enough resources for " + name);
70 | };
71 |
72 | }
73 |
74 | @Bean
75 | public AtomicMarkableReference frameworkId() {
76 | return new AtomicMarkableReference<>(Protos.FrameworkID.newBuilder().setValue("").build(), false);
77 | }
78 |
79 | @Bean
80 | @ConditionalOnProperty(prefix = "mesos.state.file", name = "location")
81 | public StateRepository stateRepositoryFile() {
82 | return new StateRepositoryFile();
83 | }
84 |
85 | @Bean(initMethod = "connect")
86 | @ConditionalOnMissingBean(StateRepository.class)
87 | public StateRepositoryZookeeper stateRepositoryZookeeper(Environment environment) {
88 | return new StateRepositoryZookeeper(environment);
89 | }
90 |
91 | @Bean
92 | public Supplier uuidSupplier() {
93 | return UUID::randomUUID;
94 | }
95 |
96 | @Bean
97 | public MesosConfigProperties mesosConfig() {
98 | return new MesosConfigProperties();
99 | }
100 |
101 | @Bean
102 | @ConditionalOnMissingBean(name = "commandInfoMesosProtoFactory")
103 | public MesosProtoFactory> commandInfoMesosProtoFactory(MesosConfigProperties mesosConfig) {
104 | return new CommandInfoMesosProtoFactory(mesosConfig);
105 | }
106 |
107 | @Bean
108 | @ConditionalOnMissingBean(TaskInfoFactory.class)
109 | @ConditionalOnProperty(prefix = "mesos.docker", name = {"image"})
110 | public TaskInfoFactory taskInfoFactoryDocker(MesosConfigProperties mesosConfig, MesosProtoFactory> commandInfoMesosProtoFactory) {
111 | return new TaskInfoFactoryDocker(mesosConfig, commandInfoMesosProtoFactory);
112 | }
113 |
114 | @Bean
115 | @ConditionalOnMissingBean(TaskInfoFactory.class)
116 | public TaskInfoFactory taskInfoFactoryCommand(MesosProtoFactory> commandInfoMesosProtoFactory, Supplier uuidSupplier) {
117 | return new TaskInfoFactoryCommand(commandInfoMesosProtoFactory, uuidSupplier);
118 | }
119 |
120 | @Bean
121 | @ConditionalOnMissingBean(name = "distinctHostRequirement")
122 | @ConditionalOnProperty(prefix = "mesos.resources", name = "distinctSlave", havingValue = "true")
123 | @Order(Ordered.LOWEST_PRECEDENCE)
124 | public ResourceRequirement distinctHostRequirement(Clock systemClock, StateRepository stateRepository) {
125 | return new DistinctSlaveRequirement(systemClock, stateRepository);
126 | }
127 |
128 | @Bean
129 | @ConditionalOnMissingBean(name = "instancesCountRequirement")
130 | @ConditionalOnProperty(prefix = "mesos.resources", name = "count")
131 | @Order(Ordered.LOWEST_PRECEDENCE)
132 | public ResourceRequirement instancesCountRequirement(StateRepository stateRepository, InstanceCount instanceCount) {
133 | return new InstancesCountRequirement(stateRepository, instanceCount);
134 | }
135 |
136 | @Bean
137 | @ConditionalOnProperty(prefix = "mesos.resources", name = "count")
138 | public TaskReaper taskReaper(StateRepository stateRepository, InstanceCount instanceCount, UniversalScheduler universalScheduler) {
139 | return new TaskReaper(stateRepository, instanceCount, universalScheduler);
140 | }
141 |
142 | @Bean
143 | @ConditionalOnProperty(prefix = "mesos.resources", name = "count")
144 | public InstanceCount instanceCount(Environment env) {
145 | return new InstanceCount(env.getProperty("mesos.resources.count", Integer.class, 1));
146 | }
147 |
148 | @Bean
149 | @ConditionalOnMissingBean(name = "roleRequirement")
150 | @ConditionalOnProperty(prefix = "mesos.resources", name = "role", havingValue = "all")
151 | @Order(Ordered.LOWEST_PRECEDENCE)
152 | public ResourceRequirement roleRequirement(MesosConfigProperties mesosConfig) {
153 | return new RoleRequirement(mesosConfig);
154 | }
155 |
156 |
157 | @Bean
158 | @ConditionalOnMissingBean(name = "cpuRequirement")
159 | @ConditionalOnProperty(prefix = "mesos.resources", name = "cpus")
160 | @Order(Ordered.HIGHEST_PRECEDENCE)
161 | public ResourceRequirement cpuRequirement(MesosConfigProperties mesosConfigProperties) {
162 | return simpleScalarRequirement("cpus", mesosConfigProperties.getResources().getCpus());
163 | }
164 |
165 | @Bean
166 | @ConditionalOnMissingBean(name = "memRequirement")
167 | @ConditionalOnProperty(prefix = "mesos.resources", name = "mem")
168 | @Order(Ordered.HIGHEST_PRECEDENCE)
169 | public ResourceRequirement memRequirement(Environment environment) {
170 | return simpleScalarRequirement("mem", environment.getRequiredProperty("mesos.resources.mem", Double.class));
171 | }
172 |
173 | @Bean
174 | @ConditionalOnMissingBean(name = "portsRequirement")
175 | // @ConditionalOnProperty(prefix = "mesos.resources", name = "ports")
176 | @Order(Ordered.HIGHEST_PRECEDENCE)
177 | public ResourceRequirement portsRequirement(MesosConfigProperties mesosConfig) {
178 | return new PortsRequirement(mesosConfig);
179 | }
180 |
181 | @Bean
182 | @ConditionalOnMissingBean(name = "volumesRequirements")
183 | @Order(Ordered.HIGHEST_PRECEDENCE)
184 | public ResourceRequirement volumesRequirement(MesosConfigProperties mesosConfig) {
185 | return new VolumesRequirement(mesosConfig);
186 | }
187 |
188 | @Bean
189 | public TaskMaterializer taskMaterializer(TaskInfoFactory taskInfoFactory) {
190 | return new TaskMaterializerMinimal(taskInfoFactory);
191 | }
192 |
193 | @Bean
194 | @ConditionalOnMissingBean
195 | public FrameworkInfoFactory frameworkInfoFactory(MesosConfigProperties mesosConfig, StateRepository stateRepository, CredentialFactory credentialFactory) {
196 | return new FrameworkInfoFactory(mesosConfig, stateRepository, credentialFactory);
197 | }
198 |
199 | @Bean
200 | @ConditionalOnMissingBean
201 | public CredentialFactory credentialFactory(MesosConfigProperties mesosConfig) {
202 | return new CredentialFactory(mesosConfig);
203 | }
204 |
205 | @Bean
206 | public TeardownManager teardownManager() {
207 | return new TeardownManager();
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/config/validation/MesosSchedulerPropertiesValidator.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.config.validation;
2 |
3 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
4 | import com.containersolutions.mesos.scheduler.config.ResourcesConfigProperties;
5 | import org.springframework.validation.Errors;
6 | import org.springframework.validation.Validator;
7 |
8 | import static org.springframework.util.StringUtils.isEmpty;
9 | import static org.springframework.validation.ValidationUtils.rejectIfEmptyOrWhitespace;
10 |
11 | public class MesosSchedulerPropertiesValidator implements Validator {
12 | @Override
13 | public boolean supports(Class> clazz) {
14 | return clazz == MesosConfigProperties.class;
15 | }
16 |
17 | @Override
18 | public void validate(Object target, Errors errors) {
19 | if (target instanceof MesosConfigProperties) {
20 | MesosConfigProperties config = (MesosConfigProperties) target;
21 |
22 | rejectIfEmptyOrWhitespace(errors, "master", "master.empty", "No Mesos Master set");
23 | rejectIfEmptyOrWhitespace(errors, "zookeeper.server", "zookeeper.server.empty", "");
24 |
25 | validateResources(errors, config.getResources(), isContainerized(config) && config.getDocker().getNetwork().equalsIgnoreCase("bridge"));
26 | }
27 | }
28 |
29 | private boolean isContainerized(MesosConfigProperties config) {
30 | return config.getDocker() != null && !isEmpty(config.getDocker().getImage());
31 | }
32 |
33 | private void validateResources(Errors errors, ResourcesConfigProperties resources, boolean containerizedNetwork) {
34 | if (resources == null) {
35 | errors.rejectValue("resources", "resources.empty", "Resources are not set");
36 | return;
37 | }
38 |
39 | if (resources.getCount() < 0) {
40 | errors.rejectValue("resources.count", "resources.count.not_positive", "Count property must be a positive number");
41 | }
42 |
43 | if (resources.getCpus() <= 0.0) {
44 | errors.rejectValue("resources.cpus", "resources.cpus.not_positive", "cpus must be a positive number");
45 | }
46 | if (resources.getMem() <= 0.0) {
47 | errors.rejectValue("resources.mem", "resources.mem.not_positive", "mem must be a positive number");
48 | }
49 |
50 | resources.getPorts().entrySet().stream()
51 | .forEach(entry -> {
52 | String name = "resources.ports." + entry.getKey();
53 | if (isEmpty(entry.getValue().getHost())) {
54 | errors.rejectValue("resources.ports", name + ".host.empty");
55 | }
56 | if (containerizedNetwork && entry.getValue().getContainer() < 1) {
57 | errors.rejectValue("resources.ports", name + ".container.empty");
58 | }
59 | if (!containerizedNetwork && entry.getValue().getContainer() != 0) {
60 | errors.rejectValue("resources.ports", name + ".container.not_containerized");
61 | }
62 | });
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/CommandInfoMesosProtoFactory.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
4 | import org.apache.mesos.Protos;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 |
7 | import java.util.LinkedHashMap;
8 | import java.util.Map;
9 | import java.util.Optional;
10 | import java.util.stream.Collectors;
11 |
12 | public class CommandInfoMesosProtoFactory implements MesosProtoFactory> {
13 | private final MesosConfigProperties mesosConfig;
14 |
15 | public CommandInfoMesosProtoFactory(MesosConfigProperties mesosConfig) {
16 | this.mesosConfig = mesosConfig;
17 | }
18 |
19 | @Override
20 | public Protos.CommandInfo create(Map additionalEnvironmentVariables) {
21 | Protos.CommandInfo.Builder builder = Protos.CommandInfo.newBuilder();
22 | Optional command = Optional.ofNullable(mesosConfig.getCommand());
23 | builder.setShell(command.isPresent());
24 | command.ifPresent(builder::setValue);
25 | builder.addAllUris(mesosConfig.getUri().stream().map(uri -> Protos.CommandInfo.URI.newBuilder().setValue(uri).build()).collect(Collectors.toList()));
26 |
27 | Map environmentVariables = new LinkedHashMap<>();
28 | environmentVariables.putAll(additionalEnvironmentVariables);
29 | environmentVariables.putAll(mesosConfig.getEnvironment());
30 |
31 | //todo: migrate to foreach
32 | environmentVariables.entrySet().stream()
33 | .map(kv -> Protos.Environment.Variable.newBuilder().setName(kv.getKey()).setValue(kv.getValue()).build())
34 | .collect(Collectors.collectingAndThen(
35 | Collectors.toList(),
36 | variables -> builder.setEnvironment(Protos.Environment.newBuilder().addAllVariables(variables))));
37 | return builder.build();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/CredentialFactory.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
4 | import org.apache.mesos.Protos;
5 |
6 | /**
7 | * Creates credentials from user supplied principals and secrets
8 | */
9 | public class CredentialFactory {
10 | private final MesosConfigProperties mesosConfig;
11 |
12 | public CredentialFactory(MesosConfigProperties mesosConfig) {
13 | this.mesosConfig = mesosConfig;
14 | }
15 |
16 | public Protos.Credential create() {
17 | if (mesosConfig.getPrincipal() != null && mesosConfig.getSecret() != null) {
18 | return Protos.Credential.newBuilder()
19 | .setPrincipal(mesosConfig.getPrincipal())
20 | .setSecret(mesosConfig.getSecret())
21 | .build();
22 | } else {
23 | return Protos.Credential.getDefaultInstance();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/ExecutionParameters.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.requirements.PortMapping;
4 | import com.containersolutions.mesos.scheduler.requirements.VolumeMapping;
5 |
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | public class ExecutionParameters {
10 | Map environmentVariables;
11 | List portMappings;
12 | private List volumeMappings;
13 |
14 | public ExecutionParameters(Map environmentVariables, List portMappings, List volumeMappings) {
15 | this.environmentVariables = environmentVariables;
16 | this.portMappings = portMappings;
17 | this.volumeMappings = volumeMappings;
18 | }
19 |
20 | public Map getEnvironmentVariables() {
21 | return environmentVariables;
22 | }
23 |
24 | public List getPortMappings() {
25 | return portMappings;
26 | }
27 |
28 | public List getVolumeMappings() {
29 | return volumeMappings;
30 | }
31 |
32 | public void setVolumes(List volumes) {
33 | this.volumeMappings = volumes;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/FrameworkInfoFactory.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
4 | import com.containersolutions.mesos.scheduler.state.StateRepository;
5 | import org.apache.commons.logging.Log;
6 | import org.apache.commons.logging.LogFactory;
7 | import org.apache.mesos.Protos;
8 | import org.springframework.beans.factory.annotation.Value;
9 |
10 | import java.util.Arrays;
11 | import java.util.Optional;
12 |
13 | /**
14 | * Creates framework info
15 | */
16 | public class FrameworkInfoFactory {
17 | protected final Log logger = LogFactory.getLog(getClass());
18 |
19 | @Value("${spring.application.name}")
20 | protected String applicationName;
21 |
22 | private final MesosConfigProperties mesosConfig;
23 | private final StateRepository stateRepository;
24 | private final CredentialFactory credentialFactory;
25 |
26 | public FrameworkInfoFactory(MesosConfigProperties mesosConfig, StateRepository stateRepository, CredentialFactory credentialFactory) {
27 | this.mesosConfig = mesosConfig;
28 | this.stateRepository = stateRepository;
29 | this.credentialFactory = credentialFactory;
30 | }
31 |
32 | public Protos.FrameworkInfo.Builder create() {
33 | Protos.FrameworkInfo.Builder frameworkBuilder = Protos.FrameworkInfo.newBuilder()
34 | .setName(applicationName)
35 | .setUser("root")
36 | .addRoles(mesosConfig.getRole())
37 | .addCapabilities(Protos.FrameworkInfo.Capability.newBuilder().setType(Protos.FrameworkInfo.Capability.Type.MULTI_ROLE).build())
38 | .setCheckpoint(true)
39 | .setFailoverTimeout(150.0)
40 | .setId(stateRepository.getFrameworkID().orElseGet(() -> Protos.FrameworkID.newBuilder().setValue("").build()));
41 | Protos.Credential credential = credentialFactory.create();
42 | if (credential.isInitialized()) {
43 | logger.debug("Adding framework principal: " + credential.getPrincipal());
44 | frameworkBuilder.setPrincipal(credential.getPrincipal());
45 | }
46 | Optional.ofNullable(mesosConfig.getWebuiUrl()).ifPresent(frameworkBuilder::setWebuiUrl);
47 | return frameworkBuilder;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/InstanceCount.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.events.InstanceCountChangeEvent;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.context.ApplicationEventPublisher;
6 | import org.springframework.jmx.export.annotation.ManagedAttribute;
7 | import org.springframework.jmx.export.annotation.ManagedResource;
8 |
9 | import java.util.concurrent.atomic.AtomicInteger;
10 |
11 | @ManagedResource
12 | public class InstanceCount {
13 | final AtomicInteger count;
14 |
15 | @Autowired
16 | ApplicationEventPublisher applicationEventPublisher;
17 |
18 | public InstanceCount(int count) {
19 | this.count = new AtomicInteger(count);
20 | }
21 |
22 | @ManagedAttribute
23 | public int getCount() {
24 | return count.get();
25 | }
26 |
27 | @ManagedAttribute
28 | public void setCount(int count) {
29 | this.count.set(count);
30 | applicationEventPublisher.publishEvent(new InstanceCountChangeEvent(count));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/MesosProtoFactory.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | public interface MesosProtoFactory {
4 | T create(A argument);
5 | }
6 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/OfferStrategyFilter.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.requirements.OfferEvaluation;
4 | import com.containersolutions.mesos.scheduler.requirements.ResourceRequirement;
5 | import org.apache.commons.logging.Log;
6 | import org.apache.commons.logging.LogFactory;
7 | import org.apache.mesos.Protos;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 |
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.stream.Collectors;
13 |
14 | public class OfferStrategyFilter {
15 | protected final Log logger = LogFactory.getLog(getClass());
16 |
17 | private final Map resourceRequirements;
18 |
19 | public OfferStrategyFilter(Map resourceRequirements) {
20 | this.resourceRequirements = resourceRequirements;
21 | }
22 |
23 | public OfferEvaluation evaluate(String taskId, Protos.Offer offer) {
24 | List offerEvaluations = resourceRequirements.entrySet().stream()
25 | .map(kv -> kv.getValue().check(kv.getKey(), taskId, offer))
26 | .peek(offerEvaluation -> logger.debug((offerEvaluation.isValid() ? "Accepting" : "Rejecting") + " offer offerId=" + offer.getId().getValue() + ", by requirement=" + offerEvaluation.getRequirement() + offerEvaluation.getDeclineReason().map(reason -> " with reason=" + reason).orElse("")))
27 | .collect(Collectors.toList());
28 |
29 | if (offerEvaluations.stream().allMatch(OfferEvaluation::isValid)) {
30 | return OfferEvaluation.accept(
31 | "*",
32 | taskId,
33 | offer,
34 | offerEvaluations.stream().flatMap(offerEvaluation -> offerEvaluation.getEnvironmentVariables().entrySet().stream()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)),
35 | offerEvaluations.stream().flatMap(offerEvaluation -> offerEvaluation.getPortMappings().stream()).collect(Collectors.toList()),
36 | offerEvaluations.stream().flatMap(offerEvaluation -> offerEvaluation.getVolumeMappings().stream()).collect(Collectors.toList()),
37 | offerEvaluations.stream().flatMap(offerEvaluation -> offerEvaluation.getResources().stream()).collect(Collectors.toList())
38 | );
39 | }
40 | return OfferEvaluation.decline("*", taskId, offer, null);
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/TaskInfoFactory.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | public interface TaskInfoFactory {
9 |
10 | Protos.TaskInfo create(String taskId, Protos.Offer offer, List resources, ExecutionParameters executionParameters);
11 | }
12 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/TaskInfoFactoryCommand.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import org.apache.commons.logging.Log;
4 | import org.apache.commons.logging.LogFactory;
5 | import org.apache.mesos.Protos;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.beans.factory.annotation.Value;
8 |
9 | import java.util.List;
10 | import java.util.Map;
11 | import java.util.UUID;
12 | import java.util.function.Supplier;
13 |
14 | public class TaskInfoFactoryCommand implements TaskInfoFactory {
15 | protected final Log logger = LogFactory.getLog(getClass());
16 |
17 | @Value("${spring.application.name}")
18 | protected String applicationName;
19 |
20 | private final MesosProtoFactory> commandInfoMesosProtoFactory;
21 |
22 | private final Supplier uuidSupplier;
23 |
24 | public TaskInfoFactoryCommand(MesosProtoFactory> commandInfoMesosProtoFactory, Supplier uuidSupplier) {
25 | this.commandInfoMesosProtoFactory = commandInfoMesosProtoFactory;
26 | this.uuidSupplier = uuidSupplier;
27 | }
28 |
29 | @Override
30 | public Protos.TaskInfo create(String taskId, Protos.Offer offer, List resources, ExecutionParameters executionParameters) {
31 | logger.debug("Creating Mesos task for taskId=" + taskId);
32 | return Protos.TaskInfo.newBuilder()
33 | .setName(applicationName + ".task")
34 | .setSlaveId(offer.getSlaveId())
35 | .setTaskId(Protos.TaskID.newBuilder().setValue(taskId))
36 | .addAllResources(resources)
37 | .setCommand(commandInfoMesosProtoFactory.create(executionParameters.getEnvironmentVariables()))
38 | .build();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/TaskInfoFactoryDocker.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
4 | import com.containersolutions.mesos.scheduler.requirements.PortMapping;
5 | import com.containersolutions.mesos.scheduler.requirements.VolumeMapping;
6 | import org.apache.commons.logging.Log;
7 | import org.apache.commons.logging.LogFactory;
8 | import org.apache.mesos.Protos;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.beans.factory.annotation.Value;
11 |
12 | import java.util.List;
13 | import java.util.Map;
14 | import java.util.stream.Collectors;
15 |
16 | import static java.util.Collections.emptyList;
17 |
18 | public class TaskInfoFactoryDocker implements TaskInfoFactory {
19 | protected final Log logger = LogFactory.getLog(getClass());
20 |
21 | @Value("${mesos.docker.image}")
22 | protected String dockerImage;
23 |
24 | @Value("${spring.application.name}")
25 | protected String applicationName;
26 |
27 | @Value("${mesos.docker.network:BRIDGE}")
28 | protected String networkMode; // May be BRIDGE or HOST
29 |
30 | private final MesosConfigProperties mesosConfig;
31 |
32 | private final MesosProtoFactory> commandInfoMesosProtoFactory;
33 |
34 | public TaskInfoFactoryDocker(MesosConfigProperties mesosConfig, MesosProtoFactory> commandInfoMesosProtoFactory) {
35 | this.mesosConfig = mesosConfig;
36 | this.commandInfoMesosProtoFactory = commandInfoMesosProtoFactory;
37 | }
38 |
39 | @Override
40 | public Protos.TaskInfo create(String taskId, Protos.Offer offer, List resources, ExecutionParameters executionParameters) {
41 | logger.info("Creating task with taskId=" + taskId + " from offerId=" + offer.getId().getValue());
42 | return Protos.TaskInfo.newBuilder()
43 | .setName(applicationName + ".task")
44 | .setSlaveId(offer.getSlaveId())
45 | .setTaskId(Protos.TaskID.newBuilder().setValue(taskId))
46 | .addAllResources(resources)
47 | .setContainer(Protos.ContainerInfo.newBuilder()
48 | .setType(Protos.ContainerInfo.Type.DOCKER)
49 | .setDocker(Protos.ContainerInfo.DockerInfo.newBuilder()
50 | .setImage(dockerImage)
51 | .addAllPortMappings(portMappings(executionParameters.getPortMappings()))
52 | .setNetwork(Protos.ContainerInfo.DockerInfo.Network.valueOf(networkMode))
53 | )
54 | .addAllVolumes(volumeMappings(executionParameters.getVolumeMappings()))
55 | )
56 | .setCommand(command(executionParameters.getEnvironmentVariables()))
57 | .build();
58 | }
59 |
60 | private Iterable extends Protos.Volume> volumeMappings(List volumeMappings) {
61 | return volumeMappings.stream()
62 | .map(volumeMapping -> Protos.Volume.newBuilder()
63 | .setHostPath(volumeMapping.getHostPath())
64 | .setContainerPath(volumeMapping.getContainerPath())
65 | .setMode(volumeMapping.isReadOnly() ? Protos.Volume.Mode.RO : Protos.Volume.Mode.RW)
66 | .build())
67 | .peek(volume -> logger.info("Mapping host volume to container volume"))
68 | .collect(Collectors.toList());
69 | }
70 |
71 | private Protos.CommandInfo command(Map additionalEnvironmentVariables) {
72 | return commandInfoMesosProtoFactory.create(additionalEnvironmentVariables);
73 | }
74 |
75 | private Iterable extends Protos.ContainerInfo.DockerInfo.PortMapping> portMappings(List portMappings) {
76 | if (networkMode.equalsIgnoreCase("BRIDGE")) {
77 | return portMappings.stream()
78 | .map(portMapping -> Protos.ContainerInfo.DockerInfo.PortMapping.newBuilder()
79 | .setHostPort(portMapping.getOfferedPort())
80 | .setContainerPort(portMapping.getContainerPort().orElseThrow(() -> new IllegalArgumentException("No container port specified for " + portMapping.getName())))
81 | .build())
82 | .peek(portMapping -> logger.info("Mapped host port " + portMapping.getHostPort() + " to container port " + portMapping.getContainerPort()))
83 | .collect(Collectors.toList());
84 | } else {
85 | return emptyList();
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/TaskMaterializer.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.requirements.OfferEvaluation;
4 |
5 | public interface TaskMaterializer {
6 | TaskProposal createProposal(OfferEvaluation offerEvaluation);
7 | }
8 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/TaskMaterializerMinimal.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.requirements.OfferEvaluation;
4 | import org.apache.mesos.Protos;
5 |
6 | import java.util.stream.Collectors;
7 |
8 | public class TaskMaterializerMinimal implements TaskMaterializer {
9 | private final TaskInfoFactory taskInfoFactory;
10 |
11 | public TaskMaterializerMinimal(TaskInfoFactory taskInfoFactory) {
12 | this.taskInfoFactory = taskInfoFactory;
13 | }
14 |
15 | @Override
16 | public TaskProposal createProposal(OfferEvaluation offerEvaluation) {
17 | return new TaskProposal(
18 | offerEvaluation.getOffer(),
19 | taskInfoFactory.create(
20 | offerEvaluation.getTaskId(),
21 | offerEvaluation.getOffer(),
22 | offerEvaluation.getResources().stream().filter(resource -> resource.getType() != Protos.Value.Type.RANGES || !resource.getRanges().getRangeList().isEmpty()).collect(Collectors.toList()),
23 | new ExecutionParameters(
24 | offerEvaluation.getEnvironmentVariables(),
25 | offerEvaluation.getPortMappings(),
26 | offerEvaluation.getVolumeMappings()
27 | )
28 | )
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/TaskProposal.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | class TaskProposal {
6 | Protos.Offer offer;
7 | Protos.TaskInfo taskInfo;
8 |
9 | public TaskProposal(Protos.Offer offer, Protos.TaskInfo taskInfo) {
10 | this.offer = offer;
11 | this.taskInfo = taskInfo;
12 | }
13 |
14 | public Protos.OfferID getOfferId() {
15 | return offer.getId();
16 | }
17 |
18 | public Protos.TaskInfo getTaskInfo() {
19 | return taskInfo;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/TaskReaper.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.events.InstanceCountChangeEvent;
4 | import com.containersolutions.mesos.scheduler.state.StateRepository;
5 | import org.apache.commons.logging.Log;
6 | import org.apache.commons.logging.LogFactory;
7 | import org.apache.mesos.Protos;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.context.ApplicationListener;
10 |
11 | import java.util.Set;
12 |
13 | public class TaskReaper implements ApplicationListener {
14 | private final Log logger = LogFactory.getLog(getClass());
15 |
16 | private final StateRepository stateRepository;
17 | private final InstanceCount instanceCount;
18 | private final UniversalScheduler universalScheduler;
19 |
20 | public TaskReaper(StateRepository stateRepository, InstanceCount instanceCount, UniversalScheduler universalScheduler) {
21 | this.stateRepository = stateRepository;
22 | this.instanceCount = instanceCount;
23 | this.universalScheduler = universalScheduler;
24 | }
25 |
26 | @Override
27 | public void onApplicationEvent(InstanceCountChangeEvent event) {
28 | int expectedInstances = instanceCount.getCount();
29 | Set taskInfos = stateRepository.allTaskInfos();
30 | int runningInstances = taskInfos.size();
31 |
32 | int rest = runningInstances - expectedInstances;
33 | taskInfos.stream()
34 | .limit(Math.max(0, rest))
35 | .map(Protos.TaskInfo::getTaskId)
36 | .peek(taskId -> logger.info("Killing taskId=" + taskId.getValue()))
37 | .forEach(universalScheduler::killTask);
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/TeardownManager.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.events.FrameworkRemovedEvent;
4 | import com.containersolutions.mesos.scheduler.events.TearDownFrameworkEvent;
5 | import org.apache.commons.logging.Log;
6 | import org.apache.commons.logging.LogFactory;
7 | import org.springframework.beans.BeansException;
8 | import org.springframework.boot.SpringApplication;
9 | import org.springframework.context.ApplicationContext;
10 | import org.springframework.context.ApplicationContextAware;
11 | import org.springframework.context.ApplicationEventPublisher;
12 | import org.springframework.context.ApplicationEventPublisherAware;
13 | import org.springframework.context.event.EventListener;
14 |
15 | public class TeardownManager implements ApplicationContextAware {
16 | protected final Log logger = LogFactory.getLog(getClass());
17 | private ApplicationContext applicationContext;
18 |
19 | @EventListener
20 | public void onFrameworkRemovedEvent(FrameworkRemovedEvent event) {
21 | logger.info("Framework removed. Shutting down scheduler, reason=\"" + event.getSource() + "\"");
22 | System.exit(SpringApplication.exit(applicationContext, () -> 10));
23 | }
24 | @EventListener
25 | public void onTearDownFrameworkEvent(TearDownFrameworkEvent event) {
26 | logger.info("Framework shutting down");
27 | final Thread shutdownTask = new Thread(() -> {
28 | try {
29 | Thread.sleep(2000);
30 | } catch (InterruptedException e) {
31 | e.printStackTrace();
32 | }
33 | System.exit(SpringApplication.exit(applicationContext, () -> 0));
34 | });
35 | shutdownTask.setDaemon(true);
36 | shutdownTask.start();
37 | }
38 |
39 | public void teardownFramework() {
40 | System.exit(SpringApplication.exit(applicationContext, () -> 10));
41 | }
42 |
43 | @Override
44 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
45 | this.applicationContext = applicationContext;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/UniversalScheduler.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
4 | import com.containersolutions.mesos.scheduler.events.*;
5 | import com.containersolutions.mesos.scheduler.requirements.OfferEvaluation;
6 | import com.containersolutions.mesos.scheduler.state.StateRepository;
7 | import com.containersolutions.mesos.utils.StreamHelper;
8 | import org.apache.commons.logging.Log;
9 | import org.apache.commons.logging.LogFactory;
10 | import org.apache.mesos.MesosSchedulerDriver;
11 | import org.apache.mesos.Protos;
12 | import org.apache.mesos.Scheduler;
13 | import org.apache.mesos.SchedulerDriver;
14 | import org.springframework.beans.factory.annotation.Value;
15 | import org.springframework.boot.context.event.ApplicationReadyEvent;
16 | import org.springframework.context.ApplicationEventPublisher;
17 | import org.springframework.context.ApplicationListener;
18 | import org.springframework.context.event.EventListener;
19 |
20 | import javax.annotation.PreDestroy;
21 | import java.util.Collections;
22 | import java.util.List;
23 | import java.util.UUID;
24 | import java.util.concurrent.ExecutionException;
25 | import java.util.concurrent.atomic.AtomicInteger;
26 | import java.util.concurrent.atomic.AtomicReference;
27 | import java.util.function.Supplier;
28 |
29 | public class UniversalScheduler implements Scheduler, ApplicationListener {
30 | protected final Log logger = LogFactory.getLog(getClass());
31 |
32 | @Value("${mesos.master}")
33 | protected String mesosMaster;
34 |
35 | @Value("${mesos.zookeeper.server}")
36 | protected String zookeeperMaster;
37 |
38 | @Value("${spring.application.name}")
39 | protected String applicationName;
40 |
41 | private final MesosConfigProperties mesosConfig;
42 |
43 | private final OfferStrategyFilter offerStrategyFilter;
44 |
45 | private final ApplicationEventPublisher applicationEventPublisher;
46 |
47 | private final Supplier uuidSupplier;
48 |
49 | private final StateRepository stateRepository;
50 |
51 | private final TaskMaterializer taskMaterializer;
52 |
53 | private final FrameworkInfoFactory frameworkInfoFactory;
54 |
55 | private final CredentialFactory credentialFactory;
56 |
57 | protected AtomicReference frameworkID = new AtomicReference<>();
58 |
59 | protected AtomicReference driver = new AtomicReference<>();
60 |
61 | public UniversalScheduler(MesosConfigProperties mesosConfig, OfferStrategyFilter offerStrategyFilter, ApplicationEventPublisher applicationEventPublisher, Supplier uuidSupplier, StateRepository stateRepository, TaskMaterializer taskMaterializer, FrameworkInfoFactory frameworkInfoFactory, CredentialFactory credentialFactory) {
62 | this.mesosConfig = mesosConfig;
63 | this.offerStrategyFilter = offerStrategyFilter;
64 | this.applicationEventPublisher = applicationEventPublisher;
65 | this.uuidSupplier = uuidSupplier;
66 | this.stateRepository = stateRepository;
67 | this.taskMaterializer = taskMaterializer;
68 | this.frameworkInfoFactory = frameworkInfoFactory;
69 | this.credentialFactory = credentialFactory;
70 | }
71 |
72 | @Override
73 | public void onApplicationEvent(ApplicationReadyEvent event) {
74 | start();
75 | }
76 |
77 | public void start() {
78 | logger.info("Starting Mesos-Starter Framework");
79 |
80 | MesosSchedulerDriver driver;
81 | Protos.Credential credential = credentialFactory.create();
82 | final String zookeeperUrl = "zk://" + zookeeperMaster + "/mesos";
83 | if (credential.isInitialized()) {
84 | logger.debug("Starting scheduler driver with supplied credentials for principal=" + credential.getPrincipal());
85 | driver = new MesosSchedulerDriver(this, frameworkInfoFactory.create().build(), zookeeperUrl, credential);
86 | } else {
87 | logger.debug("Starting scheduler driver without authorisation.");
88 | //TODO: The follow MesosSchedulerDriver constructor will be deprecated at some point.
89 | driver = new MesosSchedulerDriver(this, frameworkInfoFactory.create().build(), zookeeperUrl);
90 | }
91 |
92 | if (!this.driver.compareAndSet(null, driver)) {
93 | throw new IllegalStateException("Driver already initialised");
94 | }
95 | driver.start();
96 |
97 | new Thread(driver::join).start();
98 | }
99 |
100 | @PreDestroy
101 | public void stop() {
102 | if (driver.get() != null) {
103 | driver.get().abort();
104 | logger.info("Driver aborted");
105 | }
106 | }
107 |
108 |
109 | @Override
110 | public void registered(SchedulerDriver schedulerDriver, Protos.FrameworkID frameworkID, Protos.MasterInfo masterInfo) {
111 | logger.info("Framework registrered with frameworkId=" + frameworkID.getValue());
112 | this.frameworkID.set(frameworkID);
113 | applicationEventPublisher.publishEvent(new FrameworkRegistreredEvent(frameworkID, masterInfo));
114 | }
115 |
116 | @Override
117 | public void reregistered(SchedulerDriver schedulerDriver, Protos.MasterInfo masterInfo) {
118 | logger.info("Framework re-registrered");
119 | applicationEventPublisher.publishEvent(new FrameworkReregistreredEvent(masterInfo));
120 | }
121 |
122 | @Override
123 | public void resourceOffers(SchedulerDriver schedulerDriver, List offers) {
124 | logger.info("Initiating new offer round of " + offers.size() + " offers");
125 | AtomicInteger acceptedOffers = new AtomicInteger(0);
126 | AtomicInteger rejectedOffers = new AtomicInteger(0);
127 | offers.stream()
128 | .peek(offer -> logger.debug("Received offerId=" + offer.getId().getValue() + " for slaveId=" + offer.getSlaveId().getValue()))
129 | .map(offer -> offerStrategyFilter.evaluate(uuidSupplier.get().toString(), offer))
130 | .peek(offerEvaluation -> (offerEvaluation.isValid() ? acceptedOffers : rejectedOffers).incrementAndGet())
131 | .filter(StreamHelper.onNegative(
132 | OfferEvaluation::isValid,
133 | offerEvaluation -> schedulerDriver.declineOffer(offerEvaluation.getOffer().getId())))
134 | .peek(offerEvaluation -> logger.info("Accepting offer offerId=" + offerEvaluation.getOffer().getId().getValue() + " on slaveId=" + offerEvaluation.getOffer().getSlaveId().getValue()))
135 | .map(taskMaterializer::createProposal)
136 | // .peek(taskProposal -> logger.debug("Launching task " + taskProposal.getTaskInfo().toString()))
137 | .forEach(taskProposal -> {
138 | schedulerDriver.launchTasks(Collections.singleton(taskProposal.getOfferId()), Collections.singleton(taskProposal.getTaskInfo()));
139 | stateRepository.store(taskProposal.taskInfo);
140 | });
141 | logger.info("Finished evaluating " + offers.size() + " offers. Accepted " + acceptedOffers.get() + " offers and rejected " + rejectedOffers.get());
142 | }
143 |
144 | @Override
145 | public void offerRescinded(SchedulerDriver schedulerDriver, Protos.OfferID offerID) {
146 | logger.warn("Offer rescinded offerId=" + offerID.getValue());
147 | }
148 |
149 | @Override
150 | public void statusUpdate(SchedulerDriver schedulerDriver, Protos.TaskStatus taskStatus) {
151 | if (taskStatus.getState().equals(Protos.TaskState.TASK_ERROR)) {
152 | logger.warn("Received status update for taskID=" + taskStatus.getTaskId().getValue() + " state=" + taskStatus.getState() + " message='" + taskStatus.getMessage() + "' ");
153 | }
154 |
155 | applicationEventPublisher.publishEvent(new StatusUpdateEvent(taskStatus));
156 | }
157 |
158 | @Override
159 | public void frameworkMessage(SchedulerDriver schedulerDriver, Protos.ExecutorID executorID, Protos.SlaveID slaveID, byte[] data) {
160 | logger.debug("Received framework message from slaveId=" + slaveID.getValue());
161 | applicationEventPublisher.publishEvent(new FrameworkMessageEvent(data, executorID, slaveID));
162 | }
163 |
164 | @Override
165 | public void disconnected(SchedulerDriver schedulerDriver) {
166 | logger.warn("Disconnected");
167 | }
168 |
169 | @Override
170 | public void slaveLost(SchedulerDriver schedulerDriver, Protos.SlaveID slaveID) {
171 | logger.info("Received slave lost on slaveId=" + slaveID.getValue());
172 | applicationEventPublisher.publishEvent(new SlaveLostEvent(slaveID));
173 | }
174 |
175 | @Override
176 | public void executorLost(SchedulerDriver schedulerDriver, Protos.ExecutorID executorID, Protos.SlaveID slaveID, int status) {
177 | logger.info("Received executor lost on slaveId=" + slaveID.getValue());
178 | applicationEventPublisher.publishEvent(new ExecutorLostEvent(status, executorID, slaveID));
179 | }
180 |
181 | @Override
182 | public void error(SchedulerDriver schedulerDriver, String message) {
183 | logger.error("Received error: " + message);
184 |
185 | if (message.equalsIgnoreCase("Framework has been removed")) {
186 | applicationEventPublisher.publishEvent(new FrameworkRemovedEvent(message));
187 | }
188 | applicationEventPublisher.publishEvent(new ErrorEvent(message));
189 | }
190 |
191 | public void killTask(Protos.TaskID taskId) {
192 | driver.get().killTask(taskId);
193 | }
194 |
195 | public void sendFrameworkMessage(String executorId, String slaveId, byte[] data) {
196 | driver.get().sendFrameworkMessage(Protos.ExecutorID.newBuilder().setValue(executorId).build(), Protos.SlaveID.newBuilder().setValue(slaveId).build(), data);
197 | }
198 |
199 | @EventListener
200 | public void onTearDownFrameworkEvent(TearDownFrameworkEvent event) {
201 | driver.get().stop();
202 | logger.info("Driver stopped");
203 | driver.set(null);
204 | }
205 |
206 | }
207 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/config/DockerConfigProperties.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.config;
2 |
3 | public class DockerConfigProperties {
4 | private String image;
5 | private String network;
6 |
7 | public String getImage() {
8 | return image;
9 | }
10 |
11 | public void setImage(String image) {
12 | this.image = image;
13 | }
14 |
15 | public String getNetwork() {
16 | return network;
17 | }
18 |
19 | public void setNetwork(String network) {
20 | this.network = network;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/config/MesosConfigProperties.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.config;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | import java.util.ArrayList;
6 | import java.util.HashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | @ConfigurationProperties(prefix = "mesos")
11 | public class MesosConfigProperties {
12 | private String master;
13 | private ZookeeperConfigProperties zookeeper;
14 | private String role = "*";
15 | private String command;
16 | private DockerConfigProperties docker;
17 | private Map environment = new HashMap<>();
18 | private ResourcesConfigProperties resources = new ResourcesConfigProperties();
19 | private String principal;
20 | private String secret;
21 | private List uri = new ArrayList<>();
22 | private String webuiUrl;
23 |
24 | public String getMaster() {
25 | return master;
26 | }
27 |
28 | public void setMaster(String master) {
29 | this.master = master;
30 | }
31 |
32 | public ZookeeperConfigProperties getZookeeper() {
33 | return zookeeper;
34 | }
35 |
36 | public void setZookeeper(ZookeeperConfigProperties zookeeper) {
37 | this.zookeeper = zookeeper;
38 | }
39 |
40 | public String getRole() {
41 | return role;
42 | }
43 |
44 | public void setRole(String role) {
45 | this.role = role;
46 | }
47 |
48 | public String getCommand() {
49 | return command;
50 | }
51 |
52 | public void setCommand(String command) {
53 | this.command = command;
54 | }
55 |
56 | public Map getEnvironment() {
57 | return environment;
58 | }
59 |
60 | public void setEnvironment(Map environment) {
61 | this.environment = environment;
62 | }
63 |
64 | public ResourcesConfigProperties getResources() {
65 | return resources;
66 | }
67 |
68 | public void setResources(ResourcesConfigProperties resources) {
69 | this.resources = resources;
70 | }
71 |
72 | public String getPrincipal() {
73 | return principal;
74 | }
75 |
76 | public void setPrincipal(String principal) {
77 | this.principal = principal;
78 | }
79 |
80 | public String getSecret() {
81 | return secret;
82 | }
83 |
84 | public void setSecret(String secret) {
85 | this.secret = secret;
86 | }
87 |
88 | public List getUri() {
89 | return uri;
90 | }
91 |
92 | public void setUri(List uri) {
93 | this.uri = uri;
94 | }
95 |
96 | public DockerConfigProperties getDocker() {
97 | return docker;
98 | }
99 |
100 | public void setDocker(DockerConfigProperties docker) {
101 | this.docker = docker;
102 | }
103 |
104 | public String getWebuiUrl() {
105 | return webuiUrl;
106 | }
107 |
108 | public void setWebuiUrl(String webuiUrl) {
109 | this.webuiUrl = webuiUrl;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/config/ResourcePortConfigProperties.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.config;
2 |
3 | public class ResourcePortConfigProperties {
4 | String host;
5 | int container = 0;
6 | String role;
7 |
8 | public String getHost() {
9 | return host;
10 | }
11 |
12 | public void setHost(String host) {
13 | this.host = host;
14 | }
15 |
16 | public int getContainer() {
17 | return container;
18 | }
19 |
20 | public void setContainer(int container) {
21 | this.container = container;
22 | }
23 |
24 | public String getRole() {
25 | return role;
26 | }
27 |
28 | public void setRole(String role) {
29 | this.role = role;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/config/ResourceVolumeConfigProperties.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.config;
2 |
3 | public class ResourceVolumeConfigProperties {
4 | String hostPath;
5 | String containerPath;
6 | boolean readonly = false;
7 |
8 | public String getHostPath() {
9 | return hostPath;
10 | }
11 |
12 | public void setHostPath(String hostPath) {
13 | this.hostPath = hostPath;
14 | }
15 |
16 | public String getContainerPath() {
17 | return containerPath;
18 | }
19 |
20 | public void setContainerPath(String containerPath) {
21 | this.containerPath = containerPath;
22 | }
23 |
24 | public boolean isReadonly() {
25 | return readonly;
26 | }
27 |
28 | public void setReadonly(boolean readonly) {
29 | this.readonly = readonly;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/config/ResourcesConfigProperties.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.config;
2 |
3 | import java.util.ArrayList;
4 | import java.util.LinkedHashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | public class ResourcesConfigProperties {
9 |
10 | private double cpus;
11 | private double mem;
12 |
13 | private Map ports = new LinkedHashMap<>();
14 | private List volumes = new ArrayList<>();
15 | private int count;
16 |
17 | public double getCpus() {
18 | return cpus;
19 | }
20 |
21 | public void setCpus(double cpus) {
22 | this.cpus = cpus;
23 | }
24 |
25 | public double getMem() {
26 | return mem;
27 | }
28 |
29 | public void setMem(double mem) {
30 | this.mem = mem;
31 | }
32 |
33 | public Map getPorts() {
34 | return ports;
35 | }
36 |
37 | public void setPorts(Map ports) {
38 | this.ports = ports;
39 | }
40 |
41 | public List getVolumes() {
42 | return volumes;
43 | }
44 |
45 | public void setVolumes(List volumes) {
46 | this.volumes = volumes;
47 | }
48 |
49 | public void setCount(int count) {
50 | this.count = count;
51 | }
52 |
53 | public int getCount() {
54 | return count;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/config/ZookeeperConfigProperties.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.config;
2 |
3 | public class ZookeeperConfigProperties {
4 | private String server;
5 |
6 | public String getServer() {
7 | return server;
8 | }
9 |
10 | public void setServer(String server) {
11 | this.server = server;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/events/ErrorEvent.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.events;
2 |
3 | public class ErrorEvent extends MesosEvent {
4 | public ErrorEvent(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/events/ExecutorLostEvent.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.events;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | public class ExecutorLostEvent extends MesosEvent {
6 | private final Protos.ExecutorID executorID;
7 | private final Protos.SlaveID slaveID;
8 |
9 | public ExecutorLostEvent(int status, Protos.ExecutorID executorID, Protos.SlaveID slaveID) {
10 | super(status);
11 | this.executorID = executorID;
12 | this.slaveID = slaveID;
13 | }
14 |
15 | public Protos.ExecutorID getExecutorID() {
16 | return executorID;
17 | }
18 |
19 | public Protos.SlaveID getSlaveID() {
20 | return slaveID;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/events/FrameworkMessageEvent.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.events;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | public class FrameworkMessageEvent extends MesosEvent {
6 | private final Protos.ExecutorID executorID;
7 | private final Protos.SlaveID slaveID;
8 |
9 | /**
10 | * Create a new ApplicationEvent.
11 | *
12 | * @param data the component that published the event (never {@code null})
13 | * @param executorID
14 | * @param slaveID
15 | */
16 | public FrameworkMessageEvent(byte[] data, Protos.ExecutorID executorID, Protos.SlaveID slaveID) {
17 | super(data);
18 | this.executorID = executorID;
19 | this.slaveID = slaveID;
20 | }
21 |
22 | public Protos.ExecutorID getExecutorID() {
23 | return executorID;
24 | }
25 |
26 | public Protos.SlaveID getSlaveID() {
27 | return slaveID;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/events/FrameworkRegistreredEvent.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.events;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | public class FrameworkRegistreredEvent extends MesosEvent {
6 | private final Protos.MasterInfo masterInfo;
7 |
8 | public FrameworkRegistreredEvent(Protos.FrameworkID frameworkID, Protos.MasterInfo masterInfo) {
9 | super(frameworkID);
10 | this.masterInfo = masterInfo;
11 | }
12 |
13 | public Protos.FrameworkID getFrameworkID() {
14 | return (Protos.FrameworkID) getSource();
15 | }
16 |
17 | public Protos.MasterInfo getMasterInfo() {
18 | return masterInfo;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/events/FrameworkRemovedEvent.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.events;
2 |
3 | /**
4 | * Remote party has removed the framework.
5 | */
6 | public class FrameworkRemovedEvent extends MesosEvent {
7 | public FrameworkRemovedEvent(String message) {
8 | super(message);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/events/FrameworkReregistreredEvent.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.events;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | public class FrameworkReregistreredEvent extends MesosEvent {
6 | public FrameworkReregistreredEvent(Protos.MasterInfo masterInfo) {
7 | super(masterInfo);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/events/InstanceCountChangeEvent.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.events;
2 |
3 | import org.springframework.context.ApplicationEvent;
4 |
5 | public class InstanceCountChangeEvent extends ApplicationEvent {
6 | public InstanceCountChangeEvent(int newCount) {
7 | super(newCount);
8 | }
9 |
10 | public int getCount() {
11 | return (int) getSource();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/events/MesosEvent.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.events;
2 |
3 | import org.springframework.context.ApplicationEvent;
4 |
5 | public abstract class MesosEvent extends ApplicationEvent {
6 | /**
7 | * Create a new ApplicationEvent.
8 | *
9 | * @param source the component that published the event (never {@code null})
10 | */
11 | public MesosEvent(Object source) {
12 | super(source);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/events/SlaveLostEvent.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.events;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | public class SlaveLostEvent extends MesosEvent {
6 | public SlaveLostEvent(Protos.SlaveID slaveID) {
7 | super(slaveID);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/events/StatusUpdateEvent.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.events;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | public class StatusUpdateEvent extends MesosEvent {
6 | public StatusUpdateEvent(Protos.TaskStatus taskStatus) {
7 | super(taskStatus);
8 | }
9 |
10 | public Protos.TaskStatus getTaskStatus() {
11 | return (Protos.TaskStatus) getSource();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/events/TearDownFrameworkEvent.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.events;
2 |
3 | public class TearDownFrameworkEvent extends MesosEvent {
4 | /**
5 | * Create a new ApplicationEvent.
6 | *
7 | * @param source the component that published the event (never {@code null})
8 | */
9 | public TearDownFrameworkEvent(Object source) {
10 | super(source);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/requirements/DistinctSlaveRequirement.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | import com.containersolutions.mesos.scheduler.events.StatusUpdateEvent;
4 | import com.containersolutions.mesos.scheduler.state.StateRepository;
5 | import org.apache.commons.logging.Log;
6 | import org.apache.commons.logging.LogFactory;
7 | import org.apache.mesos.Protos;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.context.event.EventListener;
10 |
11 | import java.time.Clock;
12 | import java.time.Instant;
13 | import java.util.Collections;
14 | import java.util.Map;
15 | import java.util.concurrent.ConcurrentHashMap;
16 |
17 | import static com.containersolutions.mesos.utils.MesosHelper.isTerminalTaskState;
18 |
19 | public class DistinctSlaveRequirement implements ResourceRequirement {
20 | protected final Log logger = LogFactory.getLog(getClass());
21 |
22 | private final Clock clock;
23 |
24 | private final StateRepository stateRepository;
25 |
26 | private Map tentativeAccept = new ConcurrentHashMap<>();
27 |
28 | public DistinctSlaveRequirement(Clock clock, StateRepository stateRepository) {
29 | this.clock = clock;
30 | this.stateRepository = stateRepository;
31 | }
32 |
33 | @Override
34 | public OfferEvaluation check(String requirement, String taskId, Protos.Offer offer) {
35 | final Instant now = clock.instant();
36 | cleanUpTentatives(now);
37 |
38 | final String slaveId = offer.getSlaveId().getValue();
39 | final boolean valid = slaveIsRunningTask(slaveId);
40 | if (valid) {
41 | tentativeAccept.put(slaveId, now.plusSeconds(60));
42 | return OfferEvaluation.accept(requirement, taskId, offer, Collections.emptyMap(), Collections.emptyList(), Collections.emptyList());
43 | }
44 | return OfferEvaluation.decline(requirement, taskId, offer, "Slave " + slaveId + " is already running task");
45 | }
46 |
47 | private boolean slaveIsRunningTask(String slaveId) {
48 | return stateRepository.allTaskInfos().stream().noneMatch(taskInfo -> taskInfo.getSlaveId().getValue().equals(slaveId)) && !tentativeAccept.containsKey(slaveId);
49 | }
50 |
51 | private void cleanUpTentatives(Instant now) {
52 | tentativeAccept.entrySet().stream()
53 | .filter(entry -> entry.getValue().isBefore(now))
54 | .map(Map.Entry::getKey)
55 | .peek(key -> logger.debug("removing key = " + key))
56 | .forEach(tentativeAccept::remove);
57 | }
58 |
59 | @EventListener
60 | public void onStatusUpdate(StatusUpdateEvent event) {
61 | if (isTerminalTaskState(event.getTaskStatus().getState())) {
62 | if (tentativeAccept.remove(event.getTaskStatus().getSlaveId().getValue()) != null && logger.isDebugEnabled()) {
63 | logger.debug("Removed tentative accept for SlaveId=" + event.getTaskStatus().getSlaveId().getValue());
64 | }
65 | }
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/requirements/InstancesCountRequirement.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | import com.containersolutions.mesos.scheduler.InstanceCount;
4 | import com.containersolutions.mesos.scheduler.state.StateRepository;
5 | import org.apache.commons.logging.Log;
6 | import org.apache.commons.logging.LogFactory;
7 | import org.apache.mesos.Protos;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 |
10 | import java.util.Collections;
11 |
12 | public class InstancesCountRequirement implements ResourceRequirement {
13 | private final Log logger = LogFactory.getLog(getClass());
14 |
15 | private final StateRepository stateRepository;
16 |
17 | private final InstanceCount instanceCount;
18 |
19 | public InstancesCountRequirement(StateRepository stateRepository, InstanceCount instanceCount) {
20 | this.stateRepository = stateRepository;
21 | this.instanceCount = instanceCount;
22 | }
23 |
24 | @Override
25 | public OfferEvaluation check(String requirement, String taskId, Protos.Offer offer) {
26 | int totalCount = tasksCount();
27 | int instanceCount = this.instanceCount.getCount();
28 |
29 | if (totalCount < instanceCount) {
30 | return OfferEvaluation.accept(requirement, taskId, offer, Collections.emptyMap(), Collections.emptyList(), Collections.emptyList());
31 | }
32 | return OfferEvaluation.decline(requirement, taskId, offer, "tasksCount=" + totalCount + " < instanceCount=" + instanceCount);
33 | }
34 |
35 | private int tasksCount() {
36 | return stateRepository.allTaskInfos().size();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/requirements/OfferEvaluation.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | import java.util.List;
6 | import java.util.Map;
7 | import java.util.Optional;
8 |
9 | import static java.util.Arrays.asList;
10 |
11 | public class OfferEvaluation {
12 | String requirement;
13 | Protos.Offer offer;
14 | boolean valid;
15 | String declineReason;
16 | String taskId;
17 | private Map environmentVariables;
18 | final List portMappings;
19 | List resources;
20 | private List volumeMappings;
21 |
22 | private OfferEvaluation(String requirement, String taskId, Protos.Offer offer, boolean valid, String declineReason, Map environmentVariables, List portMappings, List volumeMappings, List resources) {
23 | this.requirement = requirement;
24 | this.taskId = taskId;
25 | this.offer = offer;
26 | this.valid = valid;
27 | this.declineReason = declineReason;
28 | this.environmentVariables = environmentVariables;
29 | this.portMappings = portMappings;
30 | this.volumeMappings = volumeMappings;
31 | this.resources = resources;
32 | }
33 |
34 | public static OfferEvaluation accept(String requirement, String taskId, Protos.Offer offer, Map environmentVariables, List portMappings, List volumeMappings, List resources) {
35 | return new OfferEvaluation(requirement, taskId, offer, true, null, environmentVariables, portMappings, volumeMappings, resources);
36 | }
37 |
38 | public static OfferEvaluation accept(String requirement, String taskId, Protos.Offer offer, Map environmentVariables, List portMappings, List volumeMappings, Protos.Resource ... resources) {
39 | return new OfferEvaluation(requirement, taskId, offer, true, null, environmentVariables, portMappings, volumeMappings, asList(resources));
40 | }
41 |
42 | public static OfferEvaluation decline(String requirement, String taskId, Protos.Offer offer, String reason) {
43 | return new OfferEvaluation(requirement, taskId, offer, false, reason, null, null, null, null);
44 | }
45 |
46 | public boolean isValid() {
47 | return valid;
48 | }
49 |
50 | public Protos.Offer getOffer() {
51 | return offer;
52 | }
53 |
54 | public List getResources() {
55 | return resources;
56 | }
57 |
58 | public String getTaskId() {
59 | return taskId;
60 | }
61 |
62 | public String getRequirement() {
63 | return requirement;
64 | }
65 |
66 | public Map getEnvironmentVariables() {
67 | return environmentVariables;
68 | }
69 |
70 | public void setEnvironmentVariables(Map environmentVariables) {
71 | this.environmentVariables = environmentVariables;
72 | }
73 |
74 | public List getPortMappings() {
75 | return portMappings;
76 | }
77 |
78 | public Optional getDeclineReason() {
79 | return Optional.ofNullable(this.declineReason);
80 | }
81 |
82 | public List getVolumeMappings() {
83 | return volumeMappings;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/requirements/PortMapping.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | import java.util.OptionalInt;
6 |
7 | public class PortMapping {
8 | String name;
9 | int offeredPort;
10 | int containerPort = 0;
11 |
12 | public PortMapping(String name, int offeredPort, int containerPort) {
13 | this.name = name;
14 | this.offeredPort = offeredPort;
15 | this.containerPort = containerPort;
16 | }
17 |
18 | public String envName() {
19 | return name.toUpperCase().replace("-", "_");
20 | }
21 |
22 | public String envValue() {
23 | return String.valueOf(offeredPort);
24 | }
25 |
26 | public int getOfferedPort() {
27 | return offeredPort;
28 | }
29 |
30 | public OptionalInt getContainerPort() {
31 | return OptionalInt.of(containerPort);
32 | }
33 |
34 | Protos.Value.Range toRange() {
35 | return Protos.Value.Range.newBuilder().setBegin(offeredPort).setEnd(offeredPort).build();
36 | }
37 |
38 | public String getName() {
39 | return name;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/requirements/PortsRequirement.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
4 | import com.containersolutions.mesos.scheduler.config.ResourcePortConfigProperties;
5 | import org.apache.mesos.Protos;
6 | import org.springframework.util.NumberUtils;
7 |
8 | import java.util.*;
9 | import java.util.concurrent.ConcurrentLinkedQueue;
10 | import java.util.stream.Collectors;
11 | import java.util.stream.IntStream;
12 |
13 | public class PortsRequirement implements ResourceRequirement {
14 | private final MesosConfigProperties mesosConfig;
15 |
16 | public PortsRequirement(MesosConfigProperties mesosConfig) {
17 | this.mesosConfig = mesosConfig;
18 | }
19 |
20 | @Override
21 | public OfferEvaluation check(String requirement, String taskId, Protos.Offer offer) {
22 | Queue unprivilegedPorts = new ConcurrentLinkedQueue<>();
23 | Queue priviledgedPorts = new ConcurrentLinkedQueue<>();
24 | Map fixedPorts = new HashMap<>();
25 |
26 | Set> ports = mesosConfig.getResources().getPorts().entrySet();
27 | ports.forEach(kv -> {
28 | String hostPort = kv.getValue().getHost();
29 | NameAndContainerPort nameAndContainerPort = new NameAndContainerPort(kv.getKey(), kv.getValue().getContainer());
30 | if (hostPort.equalsIgnoreCase("ANY") || hostPort.equalsIgnoreCase("UNPRIVILEGED")) {
31 | unprivilegedPorts.add(nameAndContainerPort);
32 | } else if (hostPort.equalsIgnoreCase("PRIVILEGED")) {
33 | priviledgedPorts.add(nameAndContainerPort);
34 | } else {
35 | fixedPorts.put(NumberUtils.parseNumber(hostPort, Integer.class), nameAndContainerPort);
36 | }
37 | });
38 |
39 | List portMappings = offer.getResourcesList().stream()
40 | .filter(resource -> resource.getName().equals("ports"))
41 | .flatMap(resource -> resource.getRanges().getRangeList().stream())
42 | .flatMapToInt(range -> IntStream.rangeClosed((int) range.getBegin(), (int) range.getEnd()))
43 | .sorted()
44 | .mapToObj(offeredPort -> {
45 | if (!unprivilegedPorts.isEmpty() && offeredPort > 1024) {
46 | return unprivilegedPorts.remove().toPortMapping(offeredPort);
47 | } else if (!priviledgedPorts.isEmpty() && offeredPort <= 1024) {
48 | return priviledgedPorts.remove().toPortMapping(offeredPort);
49 | } else if (fixedPorts.containsKey(offeredPort)) {
50 | return fixedPorts.remove(offeredPort).toPortMapping(offeredPort);
51 | }
52 | return null;
53 | })
54 | .filter(Objects::nonNull)
55 | .limit(ports.size())
56 | .collect(Collectors.toList());
57 |
58 | if (portMappings.size() == ports.size()) {
59 | return OfferEvaluation.accept(
60 | requirement,
61 | taskId,
62 | offer,
63 | portMappings.stream().collect(Collectors.toMap(PortMapping::envName, PortMapping::envValue)),
64 | portMappings,
65 | Collections.emptyList(),
66 | Protos.Resource.newBuilder()
67 | .setType(Protos.Value.Type.RANGES)
68 | .setName("ports")
69 | .setRanges(Protos.Value.Ranges.newBuilder().addAllRange(
70 | portMappings.stream().map(PortMapping::toRange).collect(Collectors.toList())
71 | ))
72 | .build()
73 | );
74 | }
75 | return OfferEvaluation.decline(requirement, taskId, offer, null);
76 | }
77 |
78 | private static class NameAndContainerPort {
79 | String name;
80 | int containerPort;
81 |
82 | public NameAndContainerPort(String name, int containerPort) {
83 | this.name = name;
84 | this.containerPort = containerPort;
85 | }
86 |
87 | PortMapping toPortMapping(int offeredPort) {
88 | return new PortMapping(name, offeredPort, containerPort);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/requirements/ResourceRequirement.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | @FunctionalInterface
6 | public interface ResourceRequirement {
7 | OfferEvaluation check(String requirement, String taskId, Protos.Offer offer);
8 |
9 | static double scalarSum(Protos.Offer offer, String name) {
10 | return offer.getResourcesList().stream().filter(resource -> resource.getName().equals(name)).mapToDouble(resource -> resource.getScalar().getValue()).sum();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/requirements/RoleRequirement.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
4 | import org.apache.mesos.Protos;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 |
7 | import java.util.Collections;
8 | import java.util.List;
9 | import java.util.stream.Collectors;
10 |
11 | public class RoleRequirement implements ResourceRequirement {
12 | private final MesosConfigProperties mesosConfig;
13 |
14 | public RoleRequirement(MesosConfigProperties mesosConfig) {
15 | this.mesosConfig = mesosConfig;
16 | }
17 |
18 | @Override
19 | public OfferEvaluation check(String requirement, String taskId, Protos.Offer offer) {
20 | List roleResources = offer.getResourcesList().stream()
21 | .filter(Protos.Resource::hasRole)
22 | .filter(resource -> resource.getRole().equals(mesosConfig.getRole()))
23 | .collect(Collectors.toList());
24 | if (!roleResources.isEmpty()) {
25 | return OfferEvaluation.accept(
26 | requirement,
27 | taskId,
28 | offer,
29 | Collections.emptyMap(),
30 | Collections.emptyList(),
31 | Collections.emptyList(),
32 | roleResources
33 | );
34 | }
35 | return OfferEvaluation.decline(requirement, taskId, offer, "No resources for role " + mesosConfig.getRole());
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/requirements/VolumeMapping.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | public class VolumeMapping {
4 | private String hostPath;
5 | private String containerPath;
6 | private boolean readOnly;
7 |
8 | public VolumeMapping(String hostPath, String containerPath, boolean readOnly) {
9 | this.hostPath = hostPath;
10 | this.containerPath = containerPath;
11 | this.readOnly = readOnly;
12 | }
13 |
14 | public String getHostPath() {
15 | return hostPath;
16 | }
17 |
18 | public void setHostPath(String hostPath) {
19 | this.hostPath = hostPath;
20 | }
21 |
22 | public String getContainerPath() {
23 | return containerPath;
24 | }
25 |
26 | public void setContainerPath(String containerPath) {
27 | this.containerPath = containerPath;
28 | }
29 |
30 | public boolean isReadOnly() {
31 | return readOnly;
32 | }
33 |
34 | public void setReadOnly(boolean readOnly) {
35 | this.readOnly = readOnly;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/requirements/VolumesRequirement.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
4 | import org.apache.mesos.Protos;
5 |
6 | import java.util.Collections;
7 | import java.util.stream.Collectors;
8 |
9 | public class VolumesRequirement implements ResourceRequirement {
10 | private final MesosConfigProperties mesosConfig;
11 |
12 | public VolumesRequirement(MesosConfigProperties mesosConfig) {
13 | this.mesosConfig = mesosConfig;
14 | }
15 |
16 | @Override
17 | public OfferEvaluation check(String requirement, String taskId, Protos.Offer offer) {
18 | return OfferEvaluation.accept(
19 | requirement,
20 | taskId,
21 | offer,
22 | Collections.emptyMap(),
23 | Collections.emptyList(),
24 | mesosConfig.getResources().getVolumes().stream()
25 | .map(properties -> new VolumeMapping(
26 | properties.getHostPath(),
27 | properties.getContainerPath(),
28 | properties.isReadonly()))
29 | .collect(Collectors.toList())
30 | //TODO: Resources needed?
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/state/StateRepository.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.state;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | import java.util.Optional;
6 | import java.util.Set;
7 |
8 | public interface StateRepository {
9 | Optional getFrameworkID();
10 |
11 | void store(Protos.TaskInfo taskInfo);
12 |
13 | Set allTaskInfos();
14 | }
15 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/state/StateRepositoryFile.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.state;
2 |
3 | import com.containersolutions.mesos.scheduler.events.FrameworkRegistreredEvent;
4 | import com.containersolutions.mesos.utils.MesosHelper;
5 | import com.containersolutions.mesos.scheduler.events.StatusUpdateEvent;
6 | import org.apache.commons.logging.Log;
7 | import org.apache.commons.logging.LogFactory;
8 | import org.apache.mesos.Protos;
9 | import org.springframework.beans.factory.annotation.Value;
10 | import org.springframework.context.event.EventListener;
11 | import org.springframework.util.FileCopyUtils;
12 | import org.springframework.util.SerializationUtils;
13 |
14 | import javax.annotation.PreDestroy;
15 | import java.io.File;
16 | import java.io.IOException;
17 | import java.util.HashSet;
18 | import java.util.Optional;
19 | import java.util.Set;
20 | import java.util.concurrent.atomic.AtomicReference;
21 |
22 | public class StateRepositoryFile implements StateRepository {
23 | protected final Log logger = LogFactory.getLog(getClass());
24 |
25 | AtomicReference frameworkId = new AtomicReference<>();
26 |
27 | File stateHome = new File(".state");
28 |
29 | @Value("${mesos.framework.name:default}")
30 | String frameworkName;
31 |
32 | @Override
33 | public Optional getFrameworkID() {
34 | if (!stateHome.exists()) {
35 | stateHome.mkdir();
36 | return Optional.empty();
37 | }
38 | try {
39 | File frameworkid = new File(frameworkStateHome(), "frameworkid");
40 | if (!frameworkid.exists()) {
41 | return Optional.empty();
42 | }
43 | return Optional.of(Protos.FrameworkID.newBuilder().setValue(new String(FileCopyUtils.copyToByteArray(frameworkid))).build());
44 | } catch (IOException e) {
45 | throw new RuntimeException("Failed to open frameworkId");
46 | }
47 | }
48 |
49 | @EventListener
50 | public void onFrameworkRegistered(FrameworkRegistreredEvent event) {
51 | logger.debug("Received frameworkId=" + event.getFrameworkID().getValue());
52 | frameworkId.set(event.getFrameworkID());
53 | }
54 |
55 | @EventListener
56 | public void onStatusUpdate(StatusUpdateEvent event) {
57 | if (MesosHelper.isTerminalTaskState(event.getTaskStatus().getState())) {
58 | final Set taskInfos = allTaskInfos();
59 | final Optional taskInfo = taskInfos.stream().filter(task -> task.getTaskId().equals(event.getTaskStatus().getTaskId())).findFirst();
60 | taskInfo.ifPresent(taskInfos::remove);
61 | save(taskInfos);
62 | }
63 | }
64 |
65 | @PreDestroy
66 | public void onExit() {
67 | frameworkStateHome().delete();
68 | }
69 |
70 | private void save(Set taskIDs) {
71 | try {
72 | if (!stateHome.exists()) {
73 | logger.info("Creating stateHome directory: " + stateHome.getAbsolutePath());
74 | stateHome.mkdirs();
75 | FileCopyUtils.copy(frameworkId.get().toByteArray(), new File(frameworkStateHome(), "frameworkid"));
76 | }
77 | FileCopyUtils.copy(SerializationUtils.serialize(new HashSet<>(taskIDs)), tasksFile());
78 | } catch (IOException e) {
79 | logger.error("Failed to save taskID list", e);
80 | }
81 | }
82 |
83 | @Override
84 | public void store(Protos.TaskInfo taskInfo) {
85 | final Set taskInfos = allTaskInfos();
86 | taskInfos.add(taskInfo);
87 | save(taskInfos);
88 | }
89 |
90 | @Override
91 | public Set allTaskInfos() {
92 | if (frameworkId.get() == null) {
93 | logger.warn("Attempted to fetch TaskInfo list before framework has been registered");
94 | return new HashSet<>();
95 | }
96 |
97 | final File stateFile = tasksFile();
98 | try {
99 | return (Set) SerializationUtils.deserialize(FileCopyUtils.copyToByteArray(stateFile));
100 | } catch (Exception e) {
101 | // logger.error("Failed to read file: " + stateFile.getAbsolutePath(), e);
102 | return new HashSet<>();
103 | }
104 | }
105 |
106 | private File tasksFile() {
107 | final File stateFile = new File(frameworkStateHome(), "tasks");
108 | if (!stateFile.exists()) {
109 | try {
110 | stateFile.createNewFile();
111 | } catch (IOException e) {
112 | throw new RuntimeException("Failed to create state file", e);
113 | }
114 | }
115 | return stateFile;
116 | }
117 |
118 | private File frameworkStateHome() {
119 | File file = new File(stateHome, frameworkName);
120 | if (!file.exists()) {
121 | file.mkdir();
122 | }
123 | return file;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/scheduler/state/StateRepositoryZookeeper.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.state;
2 |
3 | import com.containersolutions.mesos.scheduler.events.FrameworkRegistreredEvent;
4 | import com.containersolutions.mesos.scheduler.events.FrameworkRemovedEvent;
5 | import com.containersolutions.mesos.scheduler.events.StatusUpdateEvent;
6 | import com.containersolutions.mesos.scheduler.events.TearDownFrameworkEvent;
7 | import org.apache.commons.logging.Log;
8 | import org.apache.commons.logging.LogFactory;
9 | import org.apache.mesos.Protos;
10 | import org.apache.mesos.state.State;
11 | import org.apache.mesos.state.ZooKeeperState;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.beans.factory.annotation.Value;
14 | import org.springframework.context.event.EventListener;
15 | import org.springframework.core.env.Environment;
16 | import org.springframework.util.SerializationUtils;
17 |
18 | import javax.annotation.PreDestroy;
19 | import java.util.HashSet;
20 | import java.util.Optional;
21 | import java.util.Set;
22 | import java.util.concurrent.ExecutionException;
23 | import java.util.concurrent.TimeUnit;
24 | import java.util.concurrent.atomic.AtomicReference;
25 | import java.util.stream.Collectors;
26 |
27 | import static com.containersolutions.mesos.utils.MesosHelper.isTerminalTaskState;
28 |
29 | public class StateRepositoryZookeeper implements StateRepository {
30 | protected final Log logger = LogFactory.getLog(getClass());
31 | private final AtomicReference frameworkId = new AtomicReference<>();
32 |
33 | private State zkState;
34 |
35 | private final Environment environment;
36 |
37 | @Value("${mesos.framework.name:default}")
38 | String frameworkName;
39 |
40 | public StateRepositoryZookeeper(Environment environment) {
41 | this.environment = environment;
42 | }
43 |
44 | @Override
45 | public Optional getFrameworkID() {
46 | byte[] value;
47 | try {
48 | value = zkState.fetch("frameworkid").get().value();
49 | } catch (InterruptedException | ExecutionException e) {
50 | throw new RuntimeException("Failed to fetch framework id from Zookeeper");
51 | }
52 | if (value.length == 0) {
53 | return Optional.empty();
54 | }
55 | final Optional frameworkID = Optional.of(Protos.FrameworkID.newBuilder().setValue(((String) SerializationUtils.deserialize(value))).build());
56 | logger.info("Signing in frameworkName=" + frameworkName + " using FrameworkId=" + frameworkID.map(Protos.FrameworkID::getValue).orElse("NULL"));
57 | return frameworkID;
58 | }
59 |
60 | public void connect() {
61 | zkState = new ZooKeeperState(
62 | environment.getRequiredProperty("mesos.zookeeper.server"),
63 | 1000,
64 | TimeUnit.MILLISECONDS,
65 | "/" + environment.getProperty("mesos.framework.name", "default")
66 | );
67 | }
68 |
69 | @EventListener
70 | public void onFrameworkRegistered(FrameworkRegistreredEvent event) {
71 | logger.debug("Received frameworkId=" + event.getFrameworkID().getValue());
72 | frameworkId.set(event.getFrameworkID());
73 | set("frameworkid", frameworkId.get().getValue());
74 | }
75 |
76 | @EventListener
77 | public void onStatusUpdate(StatusUpdateEvent event) {
78 | if (isTerminalTaskState(event.getTaskStatus().getState())) {
79 | set("tasks",
80 | allTaskInfos().stream()
81 | .filter(task -> !task.getTaskId().equals(event.getTaskStatus().getTaskId()))
82 | .collect(Collectors.toSet())
83 | );
84 | }
85 | }
86 |
87 | @EventListener
88 | public void onFrameworkRemovedEvent(FrameworkRemovedEvent event) throws Exception {
89 | cleanup();
90 | }
91 |
92 | @EventListener
93 | public void onTearDownFrameworkEvent(TearDownFrameworkEvent event) throws Exception {
94 | cleanup();
95 | }
96 |
97 | @Override
98 | public void store(Protos.TaskInfo taskInfo) {
99 | logger.debug("Persisting taskInfo for taskId=" + taskInfo.getTaskId().getValue());
100 | Set taskInfos = allTaskInfos();
101 | taskInfos.add(taskInfo);
102 | set("tasks", taskInfos);
103 | }
104 |
105 | @Override
106 | public Set allTaskInfos() {
107 | try {
108 | byte[] existingNodes = zkState.fetch("tasks").get().value();
109 | if (existingNodes.length == 0) {
110 | return new HashSet<>();
111 | }
112 | return (Set) SerializationUtils.deserialize(existingNodes);
113 | } catch (InterruptedException | ExecutionException e) {
114 | throw new RuntimeException("Failed to get state from Zookeeper", e);
115 | }
116 |
117 | }
118 |
119 | private void set(String key, Object value) {
120 | try {
121 | zkState.store(zkState.fetch(key).get().mutate(SerializationUtils.serialize(value))).get();
122 | } catch (InterruptedException | ExecutionException e) {
123 | throw new RuntimeException("Unable to set zNode", e);
124 | }
125 | }
126 |
127 | public void cleanup() throws ExecutionException, InterruptedException {
128 | zkState.names().get().forEachRemaining(name -> {
129 | try {
130 | zkState.expunge(zkState.fetch(name).get()).get();
131 | } catch (InterruptedException | ExecutionException e) {
132 | logger.warn("Failed to delete key " + name);
133 | }
134 | });
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/utils/MesosHelper.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.utils;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | import static java.util.Arrays.asList;
6 |
7 | public class MesosHelper {
8 |
9 | public static boolean isTerminalTaskState(Protos.TaskState state) {
10 | return asList(Protos.TaskState.TASK_FINISHED, Protos.TaskState.TASK_FAILED, Protos.TaskState.TASK_KILLED, Protos.TaskState.TASK_LOST, Protos.TaskState.TASK_ERROR).contains(state);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/java/com/containersolutions/mesos/utils/StreamHelper.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.utils;
2 |
3 | import java.util.function.Consumer;
4 | import java.util.function.Predicate;
5 |
6 | public class StreamHelper {
7 |
8 | /**
9 | * Create a new predicate that will evaluate a test predicate and eventually action consumer if
10 | * test predicate returns negative
11 | *
12 | * @param test predicate for test
13 | * @param action action to be performed on negative test
14 | * @param the type of the input to the predicate
15 | * @return Predicate that will evaluate test predicate and eventually action consumer
16 | */
17 | public static Predicate onNegative(Predicate test, Consumer action) {
18 | return t -> {
19 | if (test.test(t)) {
20 | return true;
21 | }
22 | action.accept(t);
23 | return false;
24 | };
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2 | com.containersolutions.mesos.config.autoconfigure.MesosSchedulerConfiguration
3 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/test/java/com/containersolutions/mesos/TestHelper.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos;
2 |
3 | import org.apache.mesos.Protos;
4 |
5 | import java.util.function.Consumer;
6 |
7 | public class TestHelper {
8 | public static Protos.Offer createDummyOffer() {
9 | return createDummyOffer(null);
10 | }
11 |
12 | public static Protos.Offer createDummyOffer(Consumer mapper) {
13 | final Protos.Offer.Builder offer = Protos.Offer.newBuilder()
14 | .setId(Protos.OfferID.newBuilder().setValue("offer id"))
15 | .setFrameworkId(Protos.FrameworkID.newBuilder().setValue("framework id"))
16 | .setSlaveId(Protos.SlaveID.newBuilder().setValue("slave id"))
17 | .setHostname("hostname");
18 | if (mapper != null) {
19 | mapper.accept(offer);
20 | }
21 | return offer.build();
22 |
23 | }
24 |
25 | public static Protos.TaskInfo createDummyTask(String name) {
26 | return createDummyTask(name, null);
27 | }
28 |
29 | public static Protos.TaskInfo createDummyTask(String name, Consumer mapper) {
30 | Protos.TaskInfo.Builder task = Protos.TaskInfo.newBuilder()
31 | .setName(name)
32 | .setTaskId(Protos.TaskID.newBuilder().setValue(name + " id"))
33 | .setSlaveId(Protos.SlaveID.newBuilder().setValue("slave id"));
34 | if (mapper != null) {
35 | mapper.accept(task);
36 | }
37 | return task.build();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/test/java/com/containersolutions/mesos/config/validation/MesosSchedulerPropertiesValidatorTest.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.config.validation;
2 |
3 | import com.containersolutions.mesos.scheduler.config.*;
4 | import org.junit.Test;
5 | import org.springframework.validation.DirectFieldBindingResult;
6 | import org.springframework.validation.Errors;
7 |
8 | import java.util.Collections;
9 |
10 | import static org.junit.Assert.assertEquals;
11 |
12 | public class MesosSchedulerPropertiesValidatorTest {
13 | private MesosConfigProperties config = new MesosConfigProperties();
14 |
15 | private Errors errors = new DirectFieldBindingResult(config, "mesos");
16 |
17 |
18 | private MesosSchedulerPropertiesValidator validator = new MesosSchedulerPropertiesValidator();
19 |
20 | @Test
21 | public void willRejectIfMasterIsEmpty() throws Exception {
22 | config.setMaster(" ");
23 |
24 | validator.validate(config, errors);
25 | assertEquals("master.empty", errors.getFieldError("master").getCode());
26 | }
27 |
28 | @Test
29 | public void willRejectIfZookeeperIsEmpty() throws Exception {
30 | config.setZookeeper(new ZookeeperConfigProperties());
31 | config.getZookeeper().setServer(" ");
32 |
33 | validator.validate(config, errors);
34 | assertEquals("zookeeper.server.empty", errors.getFieldError("zookeeper.server").getCode());
35 | }
36 |
37 | @Test
38 | public void willRejectCountIfNegative() throws Exception {
39 | config.setResources(new ResourcesConfigProperties());
40 | config.getResources().setCount(-1);
41 |
42 | validator.validate(config, errors);
43 | assertEquals("resources.count.not_positive", errors.getFieldError("resources.count").getCode());
44 |
45 |
46 | }
47 | @Test
48 | public void willRejectCpuIfNegative() throws Exception {
49 | config.setResources(new ResourcesConfigProperties());
50 | config.getResources().setCpus(-1.0);
51 |
52 | validator.validate(config, errors);
53 | assertEquals("resources.cpus.not_positive", errors.getFieldError("resources.cpus").getCode());
54 |
55 | }
56 |
57 | @Test
58 | public void willRejectMemIfNegative() throws Exception {
59 | config.setResources(new ResourcesConfigProperties());
60 | config.getResources().setMem(-128.0);
61 |
62 | validator.validate(config, errors);
63 | assertEquals("resources.mem.not_positive", errors.getFieldError("resources.mem").getCode());
64 | }
65 |
66 | @Test
67 | public void willRequireHostPort() throws Exception {
68 | config.setResources(new ResourcesConfigProperties() {{
69 | setPorts(Collections.singletonMap("test", new ResourcePortConfigProperties() {{
70 | setContainer(1);
71 | }}));
72 | }});
73 |
74 | validator.validate(config, errors);
75 |
76 | assertEquals("resources.ports.test.host.empty", errors.getFieldError("resources.ports").getCode());
77 | }
78 |
79 | @Test
80 | public void willRejectContainerPortWhenNotContainerized() throws Exception {
81 | config.setResources(new ResourcesConfigProperties() {{
82 | setPorts(Collections.singletonMap("test", new ResourcePortConfigProperties() {{
83 | setHost("ANY");
84 | setContainer(1);
85 | }}));
86 | }});
87 |
88 | validator.validate(config, errors);
89 |
90 | assertEquals("resources.ports.test.container.not_containerized", errors.getFieldError("resources.ports").getCode());
91 | }
92 |
93 | @Test
94 | public void willNotRejectAGoodConfiguration() throws Exception {
95 | config.setMaster("leader.mesos:5050");
96 | config.setZookeeper(new ZookeeperConfigProperties() {{
97 | setServer("leader.mesos:2181");
98 | }});
99 | config.setResources(new ResourcesConfigProperties() {{
100 | setCpus(1.0);
101 | setMem(128);
102 | }});
103 |
104 | validator.validate(config, errors);
105 |
106 | assertEquals(0, errors.getErrorCount());
107 | }
108 | }
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/test/java/com/containersolutions/mesos/scheduler/FrameworkInfoFactoryTest.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
4 | import com.containersolutions.mesos.scheduler.state.StateRepository;
5 | import org.apache.mesos.Protos;
6 | import org.junit.Before;
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.mockito.InjectMocks;
10 | import org.mockito.Mock;
11 | import org.mockito.junit.MockitoJUnitRunner;
12 |
13 | import java.util.Optional;
14 |
15 | import static org.junit.Assert.assertEquals;
16 | import static org.mockito.Mockito.when;
17 |
18 | /**
19 | * Tests
20 | */
21 | @RunWith(MockitoJUnitRunner.class)
22 | public class FrameworkInfoFactoryTest {
23 | public static final String PRINCIPAL = "principal";
24 | @Mock
25 | MesosConfigProperties mesosConfigProperties;
26 | @Mock
27 | StateRepository stateRepository;
28 | @Mock
29 | CredentialFactory credentialFactory;
30 | @InjectMocks
31 | FrameworkInfoFactory factory;
32 |
33 | @Before
34 | public void before() {
35 | factory.applicationName = "";
36 | when(mesosConfigProperties.getRole()).thenReturn("");
37 | when(stateRepository.getFrameworkID()).thenReturn(Optional.empty());
38 | }
39 |
40 | @Test
41 | public void shouldIncludeCredentialsWhenProvided() {
42 | // When given a valid credential
43 | when(credentialFactory.create()).thenReturn(getCredential(true));
44 |
45 | // Verify that getPrincipal() contains the principal
46 | assertEquals(PRINCIPAL, factory.create().getPrincipal());
47 | }
48 |
49 | @Test
50 | public void shouldNotIncludeCredentialsWhenNotProvided() {
51 | // When given a valid credential
52 | when(credentialFactory.create()).thenReturn(getCredential(false));
53 |
54 | // Verify that getPrincipal() contains the principal
55 | assertEquals("", factory.create().getPrincipal());
56 | }
57 |
58 | private Protos.Credential getCredential(Boolean auth) {
59 | if (auth) {
60 | return Protos.Credential.newBuilder()
61 | .setPrincipal(PRINCIPAL)
62 | .setSecret("secret")
63 | .build();
64 | } else {
65 | return Protos.Credential.getDefaultInstance();
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/test/java/com/containersolutions/mesos/scheduler/OfferStrategyFilterTest.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.TestHelper;
4 | import com.containersolutions.mesos.scheduler.requirements.OfferEvaluation;
5 | import com.containersolutions.mesos.scheduler.requirements.ResourceRequirement;
6 | import org.apache.mesos.Protos;
7 | import org.junit.Before;
8 | import org.junit.Ignore;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.mockito.Mock;
12 | import org.mockito.junit.MockitoJUnitRunner;
13 |
14 | import java.util.Collections;
15 | import java.util.HashMap;
16 |
17 | import static org.junit.Assert.*;
18 | import static org.mockito.Mockito.*;
19 |
20 |
21 | @RunWith(MockitoJUnitRunner.class)
22 | public class OfferStrategyFilterTest {
23 | private final HashMap resourceRequirements = new HashMap<>();
24 | private Protos.Offer offer = TestHelper.createDummyOffer();
25 | @Mock
26 | private ResourceRequirement resourceRequirement;
27 | private OfferStrategyFilter filter = new OfferStrategyFilter(resourceRequirements);
28 | private String taskId = "taskId";
29 |
30 | @Before
31 | public void setUp() throws Exception {
32 | resourceRequirements.put("requirement 1", resourceRequirement);
33 | }
34 |
35 | @Test
36 | public void willApproveValidOffer() throws Exception {
37 | when(resourceRequirement.check("requirement 1", taskId, offer)).thenReturn(OfferEvaluation.accept("test requirement", taskId, offer, Collections.emptyMap(), Collections.emptyList(), Collections.emptyList()));
38 |
39 | final OfferEvaluation result = filter.evaluate(taskId, offer);
40 | assertTrue(result.isValid());
41 | assertSame(offer, result.getOffer());
42 | verify(resourceRequirement).check("requirement 1", taskId, offer);
43 | }
44 |
45 | @Test
46 | public void willRejectInvalidOffer() throws Exception {
47 | when(resourceRequirement.check("requirement 1", taskId, offer)).thenReturn(OfferEvaluation.decline("test requirement", taskId, offer, null));
48 | assertFalse(filter.evaluate(taskId, offer).isValid());
49 | verify(resourceRequirement).check("requirement 1", taskId, offer);
50 | }
51 |
52 | @Test
53 | @Ignore("A nice to have that's not ready yet")
54 | public void willNotCheckSecondRequirementIfFirstRejects() throws Exception {
55 | ResourceRequirement decliningRequirement = mock(ResourceRequirement.class);
56 | resourceRequirements.put("requirement 2", decliningRequirement);
57 |
58 | when(resourceRequirement.check("requirement 1", taskId, offer)).thenReturn(OfferEvaluation.decline("requirement 1", taskId, offer, null));
59 | when(decliningRequirement.check("requirement 2", taskId, offer)).thenReturn(OfferEvaluation.decline("requirement 2", taskId, offer, null));
60 |
61 | assertFalse(filter.evaluate(taskId, offer).isValid());
62 |
63 | verifyZeroInteractions(decliningRequirement);
64 | }
65 |
66 | @Test
67 | public void willCheckSecondRequirementIfFirstApproves() throws Exception {
68 | ResourceRequirement approvingRequirement = mock(ResourceRequirement.class);
69 | resourceRequirements.put("requirement 2", approvingRequirement);
70 |
71 | when(resourceRequirement.check("requirement 1", taskId, offer)).thenReturn(OfferEvaluation.accept("requirement 1", taskId, offer, Collections.emptyMap(), Collections.emptyList(), Collections.emptyList()));
72 | when(approvingRequirement.check("requirement 2", taskId, offer)).thenReturn(OfferEvaluation.accept("requirement 2", taskId, offer, Collections.emptyMap(), Collections.emptyList(), Collections.emptyList()));
73 |
74 | assertTrue(filter.evaluate(taskId, offer).isValid());
75 |
76 | verify(approvingRequirement).check("requirement 2", taskId, offer);
77 | }
78 |
79 | }
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/test/java/com/containersolutions/mesos/scheduler/TaskReaperTest.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.TestHelper;
4 | import com.containersolutions.mesos.scheduler.events.InstanceCountChangeEvent;
5 | import com.containersolutions.mesos.scheduler.state.StateRepository;
6 | import org.apache.mesos.Protos;
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.mockito.junit.MockitoJUnitRunner;
10 |
11 | import java.util.Arrays;
12 | import java.util.Set;
13 | import java.util.stream.Collectors;
14 |
15 | import static org.mockito.Matchers.any;
16 | import static org.mockito.Mockito.*;
17 |
18 | @RunWith(MockitoJUnitRunner.class)
19 | public class TaskReaperTest {
20 | private InstanceCount instanceCount = mock(InstanceCount.class);
21 |
22 | private StateRepository stateRepository = mock(StateRepository.class);
23 |
24 | private UniversalScheduler universalScheduler = mock(UniversalScheduler.class);
25 |
26 | private TaskReaper taskReaper = new TaskReaper(stateRepository, instanceCount, universalScheduler);
27 |
28 | private static Set tasksInfoSet(String... names) {
29 | return Arrays.stream(names)
30 | .map(TestHelper::createDummyTask)
31 | .collect(Collectors.toSet());
32 | }
33 |
34 | @Test
35 | public void willNotKillTasksWhenCountIsFullfilled() throws Exception {
36 | when(stateRepository.allTaskInfos()).thenReturn(tasksInfoSet("task 1", "task 2"));
37 | when(instanceCount.getCount()).thenReturn(2);
38 | taskReaper.onApplicationEvent(new InstanceCountChangeEvent(1));
39 |
40 | verifyZeroInteractions(universalScheduler);
41 | }
42 |
43 | @Test
44 | public void willKillTaskWhenScalingDown() throws Exception {
45 | when(stateRepository.allTaskInfos()).thenReturn(tasksInfoSet("task 1", "task 2"));
46 | when(instanceCount.getCount()).thenReturn(1);
47 | taskReaper.onApplicationEvent(new InstanceCountChangeEvent(1));
48 |
49 | verify(universalScheduler).killTask(any(Protos.TaskID.class));
50 | }
51 | }
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/test/java/com/containersolutions/mesos/scheduler/UniversalSchedulerTest.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler;
2 |
3 | import com.containersolutions.mesos.TestHelper;
4 | import com.containersolutions.mesos.scheduler.requirements.OfferEvaluation;
5 | import com.containersolutions.mesos.scheduler.state.StateRepository;
6 | import org.apache.mesos.Protos;
7 | import org.apache.mesos.SchedulerDriver;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.ArgumentCaptor;
11 | import org.mockito.junit.MockitoJUnitRunner;
12 |
13 | import java.util.Collections;
14 | import java.util.UUID;
15 | import java.util.function.Supplier;
16 |
17 | import static org.junit.Assert.assertEquals;
18 | import static org.mockito.Mockito.*;
19 |
20 | @RunWith(MockitoJUnitRunner.class)
21 | public class UniversalSchedulerTest {
22 | private OfferStrategyFilter offerStrategyFilter = mock(OfferStrategyFilter.class);
23 |
24 | private TaskInfoFactory taskInfoFactory = mock(TaskInfoFactory.class);
25 |
26 | private SchedulerDriver schedulerDriver = mock(SchedulerDriver.class);
27 |
28 | private Supplier uuidSupplier = mock(Supplier.class);
29 |
30 | private StateRepository stateRepository = mock(StateRepository.class);
31 |
32 | private TaskMaterializer taskMaterializer = mock(TaskMaterializer.class);
33 |
34 | private UniversalScheduler scheduler = new UniversalScheduler(null, offerStrategyFilter, null, uuidSupplier, stateRepository, taskMaterializer, null, null);
35 |
36 | private UUID uuid = UUID.randomUUID();
37 | private String taskId = uuid.toString();
38 |
39 | @Test
40 | public void willDeclineInvalidOffers() throws Exception {
41 | Protos.Offer offer = TestHelper.createDummyOffer();
42 |
43 | when(uuidSupplier.get()).thenReturn(uuid);
44 | when(offerStrategyFilter.evaluate(taskId, offer)).thenReturn(OfferEvaluation.decline("test", taskId, offer, null));
45 |
46 | scheduler.resourceOffers(schedulerDriver, Collections.singletonList(offer));
47 |
48 | verify(schedulerDriver).declineOffer(offer.getId());
49 | verifyNoMoreInteractions(schedulerDriver);
50 | }
51 |
52 | @Test
53 | public void willLaunchTaskFromValidOffer() throws Exception {
54 | Protos.Offer offer = TestHelper.createDummyOffer();
55 | Protos.TaskInfo task = TestHelper.createDummyTask("task", builder -> builder.setTaskId(Protos.TaskID.newBuilder().setValue(taskId)));
56 |
57 | when(uuidSupplier.get()).thenReturn(uuid);
58 | OfferEvaluation offerEvaluation = OfferEvaluation.accept("test", taskId, offer, Collections.emptyMap(), Collections.emptyList(), Collections.emptyList());
59 | when(offerStrategyFilter.evaluate(taskId, offer)).thenReturn(offerEvaluation);
60 | when(taskMaterializer.createProposal(offerEvaluation)).thenReturn(new TaskProposal(offer, task));
61 |
62 | scheduler.resourceOffers(schedulerDriver, Collections.singletonList(offer));
63 |
64 | verify(schedulerDriver, never()).declineOffer(any(Protos.OfferID.class));
65 | verify(schedulerDriver).launchTasks(Collections.singleton(offer.getId()), Collections.singleton(task));
66 |
67 | ArgumentCaptor taskInfoArgumentCaptor = ArgumentCaptor.forClass(Protos.TaskInfo.class);
68 | verify(stateRepository).store(taskInfoArgumentCaptor.capture());
69 | Protos.TaskInfo taskInfo = taskInfoArgumentCaptor.getValue();
70 | assertEquals(taskId, taskInfo.getTaskId().getValue());
71 | }
72 | }
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/test/java/com/containersolutions/mesos/scheduler/requirements/DistinctSlaveRequirementTest.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | import com.containersolutions.mesos.TestHelper;
4 | import com.containersolutions.mesos.scheduler.state.StateRepository;
5 | import org.apache.mesos.Protos;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.mockito.junit.MockitoJUnitRunner;
9 |
10 | import java.time.Clock;
11 | import java.time.Instant;
12 | import java.util.Collections;
13 |
14 | import static org.junit.Assert.assertFalse;
15 | import static org.junit.Assert.assertTrue;
16 | import static org.mockito.Mockito.mock;
17 | import static org.mockito.Mockito.when;
18 |
19 | @RunWith(MockitoJUnitRunner.class)
20 | public class DistinctSlaveRequirementTest {
21 | private Clock clock = mock(Clock.class);
22 |
23 | private StateRepository stateRepository = mock(StateRepository.class);
24 |
25 | private DistinctSlaveRequirement requirement = new DistinctSlaveRequirement(clock, stateRepository);
26 |
27 | private String taskId = "taskId";
28 | private Instant now = Instant.now();
29 |
30 | @Test
31 | public void willRejectOffersForHostWithTentativeRunningTask() throws Exception {
32 | when(clock.instant()).thenReturn(now);
33 | assertTrue(requirement.check("test requirement", taskId, createOffer("slave 1")).isValid());
34 | assertFalse(requirement.check("test requirement", taskId, createOffer("slave 1")).isValid());
35 | }
36 |
37 | @Test
38 | public void willNotAcceptTwoTentativeOffersForSameSlave() throws Exception {
39 | when(clock.instant()).thenReturn(now);
40 | assertTrue(requirement.check("test requirement", taskId, createOffer("slave 1")).isValid());
41 | assertFalse(requirement.check("test requirement", taskId, createOffer("slave 1")).isValid());
42 | }
43 |
44 | @Test
45 | public void willAcceptOfferForTentativeHostAfterAWhile() throws Exception {
46 | when(clock.instant()).thenReturn(now);
47 | assertTrue(requirement.check("test requirement", taskId, createOffer("slave 1")).isValid());
48 |
49 | when(clock.instant()).thenReturn(now.plusSeconds(120));
50 | assertTrue(requirement.check("test requirement", taskId, createOffer("slave 1")).isValid());
51 | }
52 |
53 | @Test
54 | public void willRejectOfferForHostWithRunningTask() {
55 | when(stateRepository.allTaskInfos()).thenReturn(Collections.singleton(createTaskInfo("slave 1", taskId)));
56 |
57 | assertFalse(requirement.check("test requirement", taskId, createOffer("slave 1")).isValid());
58 | }
59 |
60 | private Protos.TaskInfo createTaskInfo(String slave, String taskId) {
61 | return Protos.TaskInfo.newBuilder()
62 | .setName("test")
63 | .setTaskId(Protos.TaskID.newBuilder().setValue(taskId))
64 | .setSlaveId(Protos.SlaveID.newBuilder().setValue(slave))
65 | .build();
66 | }
67 |
68 | private Protos.Offer createOffer(String slave) {
69 | return TestHelper.createDummyOffer(builder -> builder.setSlaveId(Protos.SlaveID.newBuilder().setValue(slave)));
70 | }
71 | }
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/test/java/com/containersolutions/mesos/scheduler/requirements/InstancesCountRequirementTest.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | import com.containersolutions.mesos.TestHelper;
4 | import com.containersolutions.mesos.scheduler.InstanceCount;
5 | import com.containersolutions.mesos.scheduler.state.StateRepository;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.mockito.junit.MockitoJUnitRunner;
9 |
10 | import java.util.Collections;
11 |
12 | import static org.junit.Assert.assertFalse;
13 | import static org.junit.Assert.assertTrue;
14 | import static org.mockito.Mockito.mock;
15 | import static org.mockito.Mockito.when;
16 |
17 | @RunWith(MockitoJUnitRunner.class)
18 | public class InstancesCountRequirementTest {
19 | private StateRepository stateRepository = mock(StateRepository.class);
20 |
21 | private InstanceCount instanceCount = mock(InstanceCount.class);
22 |
23 | private InstancesCountRequirement requirement = new InstancesCountRequirement(stateRepository, instanceCount);
24 |
25 | @Test
26 | public void willAcceptOfferWhenCountIsNotReached() throws Exception {
27 | when(instanceCount.getCount()).thenReturn(1);
28 | when(stateRepository.allTaskInfos()).thenReturn(Collections.emptySet());
29 | assertTrue(requirement.check("test requirement", "taskId 1", TestHelper.createDummyOffer()).isValid());
30 | }
31 |
32 | @Test
33 | public void willRejectOfferWhenCountIsReached() throws Exception {
34 | when(instanceCount.getCount()).thenReturn(1);
35 | when(stateRepository.allTaskInfos()).thenReturn(Collections.singleton(TestHelper.createDummyTask("task")));
36 | assertFalse(requirement.check("test requirement", "taskId 2", TestHelper.createDummyOffer()).isValid());
37 | }
38 | }
--------------------------------------------------------------------------------
/spring-boot-starter-mesos/src/test/java/com/containersolutions/mesos/scheduler/requirements/PortsRequirementTest.java:
--------------------------------------------------------------------------------
1 | package com.containersolutions.mesos.scheduler.requirements;
2 |
3 | import com.containersolutions.mesos.scheduler.config.MesosConfigProperties;
4 | import com.containersolutions.mesos.scheduler.config.ResourcePortConfigProperties;
5 | import com.containersolutions.mesos.scheduler.config.ResourcesConfigProperties;
6 | import org.apache.mesos.Protos;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.junit.MockitoJUnitRunner;
11 |
12 | import static com.containersolutions.mesos.TestHelper.createDummyOffer;
13 | import static junit.framework.TestCase.assertFalse;
14 | import static org.junit.Assert.assertEquals;
15 | import static org.junit.Assert.assertTrue;
16 |
17 | @RunWith(MockitoJUnitRunner.class)
18 | public class PortsRequirementTest {
19 |
20 | private MesosConfigProperties mesosConfig = new MesosConfigProperties();
21 |
22 | private PortsRequirement requirement = new PortsRequirement(mesosConfig);
23 |
24 |
25 | @Before
26 | public void setUp() throws Exception {
27 | mesosConfig.setResources(new ResourcesConfigProperties());
28 | }
29 |
30 | private ResourcePortConfigProperties createPort(String hostPort) {
31 | ResourcePortConfigProperties port = new ResourcePortConfigProperties();
32 | port.setHost(hostPort);
33 | return port;
34 |
35 | }
36 |
37 | @Test
38 | public void willChooseDesiredPorts() throws Exception {
39 | mesosConfig.getResources().getPorts().put("a", createPort("PRIVILEGED"));
40 | mesosConfig.getResources().getPorts().put("b", createPort("UNPRIVILEGED"));
41 | mesosConfig.getResources().getPorts().put("c", createPort("ANY"));
42 | mesosConfig.getResources().getPorts().put("d", createPort("1005"));
43 | mesosConfig.getResources().getPorts().put("e", createPort("3333"));
44 |
45 | final OfferEvaluation ports = requirement.check("test", "taskId", createDummyOffer(builder -> {
46 | builder.addResources(Protos.Resource.newBuilder()
47 | .setName("ports")
48 | .setType(Protos.Value.Type.RANGES)
49 | .setRanges(Protos.Value.Ranges.newBuilder().addRange(Protos.Value.Range.newBuilder().setBegin(1000).setEnd(2000)))
50 | );
51 | builder.addResources(Protos.Resource.newBuilder()
52 | .setName("ports")
53 | .setType(Protos.Value.Type.RANGES)
54 | .setRanges(Protos.Value.Ranges.newBuilder().addRange(Protos.Value.Range.newBuilder().setBegin(3000).setEnd(4000)))
55 | );
56 | }));
57 |
58 | assertTrue(ports.isValid());
59 | assertEquals(1000, ports.getResources().get(0).getRanges().getRange(0).getBegin());
60 | assertEquals(5, ports.getEnvironmentVariables().size());
61 | assertEquals(1025, ports.getResources().get(0).getRanges().getRange(2).getBegin());
62 | assertEquals("1000", ports.getEnvironmentVariables().get("A"));
63 | assertEquals("1025", ports.getEnvironmentVariables().get("B"));
64 | assertEquals("1026", ports.getEnvironmentVariables().get("C"));
65 | assertEquals("1005", ports.getEnvironmentVariables().get("D"));
66 | assertEquals("3333", ports.getEnvironmentVariables().get("E"));
67 | }
68 |
69 | @Test
70 | public void willRejectIfFixedPortIsMissing() throws Exception {
71 | mesosConfig.getResources().getPorts().put("a", createPort("9999"));
72 |
73 | final OfferEvaluation ports = requirement.check("test", "taskId", createDummyOffer(builder -> builder.addResources(Protos.Resource.newBuilder()
74 | .setName("ports")
75 | .setType(Protos.Value.Type.RANGES)
76 | .setRanges(Protos.Value.Ranges.newBuilder().addRange(Protos.Value.Range.newBuilder().setBegin(1000).setEnd(2000)))
77 | )));
78 |
79 | assertFalse(ports.isValid());
80 | }
81 |
82 | @Test
83 | public void testwillMapANYPortToFirstUnprivilegedPort() throws Exception {
84 | mesosConfig.getResources().getPorts().put("first", createPort("ANY"));
85 |
86 | final OfferEvaluation ports = requirement.check("test", "taskId", createDummyOffer(builder -> builder.addResources(Protos.Resource.newBuilder()
87 | .setName("ports")
88 | .setType(Protos.Value.Type.RANGES)
89 | .setRanges(Protos.Value.Ranges.newBuilder().addRange(Protos.Value.Range.newBuilder().setBegin(1000).setEnd(2000)))
90 | )));
91 | assertTrue(ports.isValid());
92 | assertEquals(1025, ports.getResources().get(0).getRanges().getRange(0).getBegin());
93 |
94 | }
95 |
96 | @Test
97 | public void testwillMapFixedPortToCorrectPort() throws Exception {
98 | mesosConfig.getResources().getPorts().put("first", createPort("1024"));
99 |
100 | final OfferEvaluation ports = requirement.check("test", "taskId", createDummyOffer(builder -> builder.addResources(Protos.Resource.newBuilder()
101 | .setName("ports")
102 | .setType(Protos.Value.Type.RANGES)
103 | .setRanges(Protos.Value.Ranges.newBuilder().addRange(Protos.Value.Range.newBuilder().setBegin(1000).setEnd(2000)))
104 | )));
105 | assertTrue(ports.isValid());
106 | assertEquals(1024, ports.getResources().get(0).getRanges().getRange(0).getBegin());
107 |
108 | }
109 |
110 | }
--------------------------------------------------------------------------------