├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main └── java └── com └── amazonaws └── ecs └── sample └── ECSSchedulerDriver.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | target/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"). You 4 | may not use this file except in compliance with the License. A copy of 5 | the License is located at 6 | 7 | http://aws.amazon.com/apache2.0/ 8 | 9 | or in the "license" file accompanying this file. This file is 10 | distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 | ANY KIND, either express or implied. See the License for the specific 12 | language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amazon ECS Scheduler Driver 2 | 3 | The Amazon EC2 Container Service (Amazon ECS) Scheduler Driver is an initial proof of concept of how we could start integrating Apache Mesos with ECS. This proof of concept demonstrates how Mesos schedulers (frameworks) could schedule workloads on ECS. It demonstrates the potential for Amazon ECS to integrate with the Mesos ecosystem, which would allow you to use Mesos schedulers like Marathon and Chronos to launch tasks on Amazon ECS. This is an example of what can be done with Amazon ECS, and is not recommended for production use. We are working with the Mesos community to develop a more robust integration between Apache Mesos and Amazon ECS. 4 | 5 | ## Building 6 | Run `mvn package` in the root directory, a jar will be produced in the target/ directory. 7 | 8 | Alternatively, run `mvn install` to install the package to your local Maven repository. 9 | 10 | ## Integrating with a Mesos scheduler 11 | If you followed the instructions to `mvn install` above, you should be able to modify the dependencies of your 12 | project to depend on 13 | 14 | ``` 15 | com.amazonaws 16 | amazon-ecs-scheduler-driver 17 | 0.1 18 | ``` 19 | 20 | If you are planning to use the .jar file instead, you'll need to ensure it is on the classpath of your Scheduler 21 | and instead add an AWS SDK dependency to the scheduler. 22 | 23 | Finally, you'll need to modify the instantiation of the `MesosSchedulerDriver` in whichever scheduler you're using 24 | to instead instantiate the `ECSSchedulerDriver` instead. 25 | 26 | ## Using your Amazon ECS powered Mesos Scheduler 27 | The ECSSchedulerDriver will interpret the `command` typically given when scheduling jobs with Mesos and instead 28 | interpret this as your TaskDefinition family:revision. For example, if Chronos or Marathon are given the `command=sleep360:1`, 29 | a TaskDefinition which sleeps for 5 minutes and then exits, the ECSSchedulerDriver will issue commands to Amazon ECS 30 | to run that family:revision. 31 | 32 | The ECSSchedulerDriver expects the Task Definition to have been registered already. 33 | 34 | ### Declaring Resource Constraints(CPU, Memory, Ports) 35 | Mesos and Amazon ECS account for resources differently. With Mesos, CPU usage is a floating point number, so if you 36 | wanted to run a task which used half of a CPU, you'd ask for cpu=0.5. With Amazon ECS this is typically performed by 37 | asking for cpu=512 instead as Amazon ECS gives each cpu 1024 units rather than 1. With the ECSSchedulerDriver you 38 | should declare your constraints with the Mesos style of 0.5 meaning half a cpu, and the ECSSchedulerDriver will convert 39 | this for you. Be aware, the TaskDefinition resource constraints should be equivalent to what you place in your scheduler. 40 | For resources like memory, both systems count in megabytes, and for ports, you would declare the fixed ports that must 41 | be reserved. 42 | 43 | ## A Marathon Example 44 | After adding a dependency on the ECSSchedulerDriver and properly instantiating it instead of the MesosScheedulerDriver 45 | as stated above, you can begin starting your Marathon managed Amazon ECS tasks. An example Task Definition is below. 46 | 47 | ``` 48 | aws ecs describe-task-definition --task-definition nginx:2 49 | { 50 | "taskDefinition": { 51 | "taskDefinitionArn": "arn:aws:ecs:us-east-1:***:task-definition/nginx:2", 52 | "containerDefinitions": [ 53 | { 54 | "environment": [], 55 | "name": "nginx", 56 | "image": "nginx", 57 | "cpu": 100, 58 | "portMappings": [ 59 | { 60 | "containerPort": 80, 61 | "hostPort": 80 62 | } 63 | ], 64 | "memory": 100, 65 | "essential": true 66 | } 67 | ], 68 | "family": "nginx", 69 | "revision": 2 70 | } 71 | } 72 | ``` 73 | 74 | In order to run this command, you must use the new Docker portion of Marathon. The Marathon UI, doesn't seem to support 75 | this yet, so you'll use the command line and issue a command to the local Marathon. The command 76 | below will ask Marathon to keep 1 copy of the nginx:2 task running with matching resource constraints as what exists 77 | in the Task Definition. Once created, management of the scaling of this nginx task is done through the Marathon UI. 78 | 79 | ``` 80 | curl -i -H "Content-Type: application/json" -d ' 81 | { 82 | "id": "nginx", 83 | "cmd": "nginx:2", 84 | "cpus": 0.1, 85 | "mem": 100.0, 86 | "instances": 1, 87 | "container": { 88 | "type": "DOCKER", 89 | "docker": { 90 | "image": "ignore", 91 | "network": "BRIDGE", 92 | "portMappings": [ 93 | { 94 | "containerPort":80, 95 | "hostPort":80 96 | } 97 | ] 98 | } 99 | } 100 | } 101 | ' http://localhost:8080/v2/apps 102 | ``` 103 | 104 | ## Additional Configuration 105 | The following environment variables are available to configure your ECSSchedulerDriver. Because it uses the 106 | AWS SDK to communicate with Amazon ECS, any configuration for the SDK passed through key files or environment variables 107 | can also be used. 108 | 109 | | Environment Key | Example Value(s) | Description | Default Value | 110 | |:----------------|:----------------------------|:------------|:--------------| 111 | | `AWS_ECS_CLUSTER` | clusterName | The cluster this ECSSchedulerDriver should manage. | default | 112 | | `AWS_ECS_ENDPOINT` | https://ecs.us-east-1.amazonaws.com | The Amazon ECS endpoint to use. | https://ecs.us-east-1.amazonaws.com | 113 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 4.0.0 20 | 21 | com.amazonaws 22 | amazon-ecs-scheduler-driver 23 | 0.1 24 | 25 | 26 | 27 | 28 | org.apache.maven.plugins 29 | maven-compiler-plugin 30 | 3.2 31 | 32 | 1.6 33 | 1.6 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | com.amazonaws 42 | aws-java-sdk 43 | 1.9.16 44 | 45 | 46 | org.apache.mesos 47 | mesos 48 | 0.21.1-rc2 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/main/java/com/amazonaws/ecs/sample/ECSSchedulerDriver.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). You 5 | may not use this file except in compliance with the License. A copy of 6 | the License is located at 7 | 8 | http://aws.amazon.com/apache2.0/ 9 | 10 | or in the "license" file accompanying this file. This file is 11 | distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | ANY KIND, either express or implied. See the License for the specific 13 | language governing permissions and limitations under the License. 14 | */ 15 | 16 | package com.amazonaws.ecs.sample; 17 | 18 | import com.amazonaws.services.ecs.AmazonECSClient; 19 | import com.amazonaws.services.ecs.model.ContainerInstance; 20 | import com.amazonaws.services.ecs.model.DescribeContainerInstancesRequest; 21 | import com.amazonaws.services.ecs.model.DescribeContainerInstancesResult; 22 | import com.amazonaws.services.ecs.model.DescribeTasksRequest; 23 | import com.amazonaws.services.ecs.model.DescribeTasksResult; 24 | import com.amazonaws.services.ecs.model.Failure; 25 | import com.amazonaws.services.ecs.model.ListContainerInstancesRequest; 26 | import com.amazonaws.services.ecs.model.ListContainerInstancesResult; 27 | import com.amazonaws.services.ecs.model.Resource; 28 | import com.amazonaws.services.ecs.model.StartTaskRequest; 29 | import com.amazonaws.services.ecs.model.StartTaskResult; 30 | import com.amazonaws.services.ecs.model.StopTaskRequest; 31 | import com.amazonaws.services.ecs.model.StopTaskResult; 32 | import com.amazonaws.services.ecs.model.Task; 33 | import org.apache.mesos.Protos; 34 | import org.apache.mesos.Scheduler; 35 | import org.apache.mesos.SchedulerDriver; 36 | 37 | import java.util.ArrayList; 38 | import java.util.Collection; 39 | import java.util.Collections; 40 | import java.util.HashMap; 41 | import java.util.Iterator; 42 | import java.util.List; 43 | import java.util.Map; 44 | import java.util.UUID; 45 | import java.util.concurrent.Executors; 46 | import java.util.concurrent.ScheduledExecutorService; 47 | import java.util.concurrent.TimeUnit; 48 | 49 | public class ECSSchedulerDriver implements SchedulerDriver { 50 | private static final String MESOS_CPU = "cpus"; 51 | private static final String MESOS_MEM = "mem"; 52 | private static final String MESOS_PORTS = "ports"; 53 | private static final String MESOS_DISK = "disk"; 54 | 55 | private static final String AWS_ECS_CPU = "CPU"; 56 | private static final String AWS_ECS_MEM = "MEMORY"; 57 | private static final String AWS_ECS_PORTS = "PORTS"; 58 | 59 | private static final Integer AWS_ECS_MAX_PORT = 60000; 60 | private static final int OFFER_REFRESH_RATE = 60; 61 | 62 | // An object to use for synchronization. 63 | private final Object lock = new Object(); 64 | 65 | private final Scheduler scheduler; 66 | private final Map taskMap; 67 | private final AmazonECSClient client; 68 | private final String clusterName; 69 | private final Protos.MasterInfo masterInfo; 70 | private final ScheduledExecutorService executorService; 71 | 72 | private Protos.FrameworkInfo frameworkInfo; 73 | private Protos.Status status; 74 | 75 | public ECSSchedulerDriver(final Scheduler scheduler, 76 | final Protos.FrameworkInfo frameworkInfo, 77 | final String masterName) { 78 | //Probably would be better to have a builder for this class so that all of these could be injected properly 79 | this.status = Protos.Status.DRIVER_NOT_STARTED; 80 | this.scheduler = scheduler; 81 | if (frameworkInfo.getId().isInitialized()) { 82 | this.frameworkInfo = frameworkInfo; 83 | } else { 84 | this.frameworkInfo = Protos.FrameworkInfo.newBuilder(frameworkInfo) 85 | .setId(Protos.FrameworkID.newBuilder() 86 | .setValue(UUID.randomUUID().toString()) 87 | .build() 88 | ) 89 | .build(); 90 | } 91 | this.taskMap = new HashMap(); 92 | this.client = new AmazonECSClient(); 93 | String endpoint = System.getenv("AWS_ECS_ENDPOINT"); 94 | if (endpoint == null || "".equals(endpoint)) { 95 | endpoint = "https://ecs.us-east-1.amazonaws.com"; 96 | } 97 | client.setEndpoint(endpoint); 98 | String cluster = System.getenv("AWS_ECS_CLUSTER"); 99 | if (cluster == null || "".equals(cluster)) { 100 | cluster = "default"; 101 | } 102 | this.clusterName = cluster; 103 | this.masterInfo = Protos.MasterInfo.newBuilder() 104 | .setHostname(endpoint) 105 | .setId(endpoint + masterName + cluster) 106 | .setIp(0) 107 | .setPort(443) 108 | .build(); 109 | this.executorService = Executors.newSingleThreadScheduledExecutor(); 110 | } 111 | 112 | @Override 113 | public Protos.Status start() { 114 | synchronized (lock) { 115 | if (status != Protos.Status.DRIVER_NOT_STARTED) { 116 | return status; 117 | } 118 | 119 | scheduler.registered(this, frameworkInfo.getId(), masterInfo); 120 | executorService.scheduleWithFixedDelay(new ResourceGenerator(), OFFER_REFRESH_RATE, OFFER_REFRESH_RATE, TimeUnit.SECONDS); 121 | status = Protos.Status.DRIVER_RUNNING; 122 | lock.notifyAll(); 123 | return status; 124 | } 125 | } 126 | 127 | @Override 128 | public Protos.Status stop(boolean b) { 129 | synchronized (lock) { 130 | if (status != Protos.Status.DRIVER_RUNNING && status != Protos.Status.DRIVER_ABORTED) { 131 | return status; 132 | } 133 | executorService.shutdown(); 134 | status = Protos.Status.DRIVER_STOPPED; 135 | lock.notifyAll(); 136 | return status; 137 | } 138 | } 139 | 140 | @Override 141 | public Protos.Status stop() { 142 | return stop(false); 143 | } 144 | 145 | @Override 146 | public Protos.Status abort() { 147 | synchronized (lock) { 148 | if (status != Protos.Status.DRIVER_RUNNING) { 149 | return status; 150 | } 151 | 152 | status = Protos.Status.DRIVER_ABORTED; 153 | lock.notifyAll(); 154 | return status; 155 | } 156 | } 157 | 158 | @Override 159 | public Protos.Status join() { 160 | synchronized (lock) { 161 | if (status != Protos.Status.DRIVER_RUNNING) { 162 | return status; 163 | } 164 | 165 | while (status == Protos.Status.DRIVER_RUNNING) { 166 | try { 167 | lock.wait(); 168 | } catch (InterruptedException e) { 169 | // Just keep going. 170 | } 171 | } 172 | return status; 173 | } 174 | } 175 | 176 | @Override 177 | public Protos.Status run() { 178 | Protos.Status localStatus = start(); 179 | if (localStatus != Protos.Status.DRIVER_RUNNING) { 180 | return status; 181 | } else { 182 | return join(); 183 | } 184 | } 185 | 186 | @Override 187 | public Protos.Status requestResources(Collection collection) { 188 | // Noop, we always offer all resources, this could be improved. 189 | synchronized (lock) { 190 | return status; 191 | } 192 | } 193 | 194 | @Override 195 | public Protos.Status launchTasks(Collection offerIDs, Collection tasks, Protos.Filters filters) { 196 | // We completely ignore filters, this is an area for optimization but not needed since we expose full cluster state. 197 | // Declines(offerIds with no tasks) are also ignored as they're not needed with Amazon ECS state management. 198 | synchronized (this) { 199 | if (offerIDs.isEmpty()) { 200 | return status; 201 | } 202 | 203 | // The Amazon ECS apis only allow 10 container instances per StartTask call, so partition. 204 | final List containerInstanceIDs = new ArrayList(offerIDs.size()); 205 | for (final Protos.OfferID offerID : offerIDs) { 206 | containerInstanceIDs.add(offerID.getValue()); 207 | } 208 | final List> containerInstancePartitions = partition(containerInstanceIDs, 10); 209 | 210 | for (final Protos.TaskInfo taskInfo : tasks) { 211 | final StartTaskRequest request = new StartTaskRequest(); 212 | request.setCluster(clusterName); 213 | // Mesos's data model does not match with the Amazon ECS data model. 214 | // Amazon ECS interprets "command" to be the already-registered TaskDefinition. 215 | request.setTaskDefinition(taskInfo.getCommand().getValue()); 216 | for (final List containerInstances : containerInstancePartitions) { 217 | request.setContainerInstances(containerInstances); 218 | final StartTaskResult result = client.startTask(request); 219 | for (final Task ecsTask : result.getTasks()) { 220 | taskMap.put(taskInfo.getTaskId(), ecsTask.getTaskArn()); 221 | final Protos.TaskStatus taskStatus = Protos.TaskStatus.newBuilder() 222 | .setTaskId(taskInfo.getTaskId()) 223 | .setSlaveId(Protos.SlaveID.newBuilder().setValue(ecsTask.getContainerInstanceArn())) 224 | .setState(Protos.TaskState.TASK_RUNNING) 225 | .build(); 226 | scheduler.statusUpdate(this, taskStatus); 227 | } 228 | for (final Failure failure : result.getFailures()) { 229 | final Protos.TaskStatus taskStatus = Protos.TaskStatus.newBuilder() 230 | .setTaskId(taskInfo.getTaskId()) 231 | .setSlaveId(Protos.SlaveID.newBuilder().setValue(failure.getArn())) 232 | .setState(Protos.TaskState.TASK_FAILED) 233 | .build(); 234 | scheduler.statusUpdate(this, taskStatus); 235 | } 236 | } 237 | } 238 | return status; 239 | } 240 | } 241 | 242 | @Override 243 | public Protos.Status launchTasks(Collection offerIDs, Collection tasks) { 244 | return launchTasks(offerIDs, tasks, Protos.Filters.newBuilder().build()); 245 | } 246 | 247 | @Override 248 | public Protos.Status launchTasks(final Protos.OfferID offerID, Collection tasks, Protos.Filters filters) { 249 | return launchTasks(new ArrayList() {{ 250 | add(offerID); 251 | }}, tasks, filters); 252 | } 253 | 254 | @Override 255 | public Protos.Status launchTasks(Protos.OfferID offerID, Collection tasks) { 256 | return launchTasks(offerID, tasks, Protos.Filters.newBuilder().build()); 257 | } 258 | 259 | @Override 260 | public Protos.Status killTask(Protos.TaskID taskID) { 261 | synchronized (lock) { 262 | if (status != Protos.Status.DRIVER_RUNNING) { 263 | return status; 264 | } 265 | 266 | final String ecsTaskId = taskMap.get(taskID); 267 | if (ecsTaskId == null) { 268 | //TODO: Need to handle this WAY better, for now just do nothing? 269 | return status; 270 | } 271 | 272 | final StopTaskRequest request = new StopTaskRequest(); 273 | request.setCluster(clusterName); 274 | request.setTask(ecsTaskId); 275 | final StopTaskResult result = client.stopTask(request); 276 | final Protos.TaskStatus taskStatus = Protos.TaskStatus.newBuilder() 277 | .setTaskId(taskID) 278 | .setSlaveId(Protos.SlaveID.newBuilder().setValue(result.getTask().getContainerInstanceArn())) 279 | .setState(Protos.TaskState.TASK_KILLED) 280 | .build(); 281 | scheduler.statusUpdate(this, taskStatus); 282 | return status; 283 | } 284 | } 285 | 286 | @Override 287 | public Protos.Status declineOffer(final Protos.OfferID offerID, Protos.Filters filters) { 288 | return launchTasks(new ArrayList() {{ 289 | add(offerID); 290 | }}, new ArrayList(), filters); 291 | } 292 | 293 | @Override 294 | public Protos.Status declineOffer(Protos.OfferID offerID) { 295 | return declineOffer(offerID, Protos.Filters.newBuilder().build()); 296 | } 297 | 298 | @Override 299 | public Protos.Status reviveOffers() { 300 | // Deliberate no-op 301 | synchronized (lock) { 302 | return status; 303 | } 304 | } 305 | 306 | @Override 307 | public Protos.Status sendFrameworkMessage(Protos.ExecutorID executorID, Protos.SlaveID slaveID, byte[] bytes) { 308 | // Deliberate no-op 309 | synchronized (lock) { 310 | return status; 311 | } 312 | } 313 | 314 | @Override 315 | public Protos.Status reconcileTasks(Collection taskStatuses) { 316 | synchronized (lock) { 317 | if (status != Protos.Status.DRIVER_RUNNING) { 318 | return status; 319 | } 320 | 321 | final Map ecsTaskToStatus = new HashMap(taskStatuses.size()); 322 | 323 | final List taskIds = new ArrayList(taskStatuses.size()); 324 | for (final Protos.TaskStatus taskStatus : taskStatuses) { 325 | final String ecsTaskId = taskMap.get(taskStatus.getTaskId()); 326 | if (ecsTaskId == null) { 327 | //TODO: persist this properly so we don't get here. 328 | continue; 329 | } 330 | taskIds.add(ecsTaskId); 331 | ecsTaskToStatus.put(ecsTaskId, taskStatus); 332 | } 333 | // The Amazon ECS APIs only allow 100 tasks per DescribeTasks call, so partition. 334 | final List> taskIdsPartition = partition(taskIds, 100); 335 | 336 | for (final List tasks : taskIdsPartition) { 337 | final DescribeTasksRequest request = new DescribeTasksRequest(); 338 | request.setCluster(clusterName); 339 | request.setTasks(tasks); 340 | final DescribeTasksResult result = client.describeTasks(request); 341 | for (final Task task : result.getTasks()) { 342 | if ("STOPPED".equals(task.getLastStatus())) { 343 | final Protos.TaskStatus taskStatus = Protos.TaskStatus.newBuilder() 344 | .mergeFrom(ecsTaskToStatus.get(task.getTaskArn())) 345 | .setState(Protos.TaskState.TASK_FINISHED) 346 | .build(); 347 | scheduler.statusUpdate(this, taskStatus); 348 | } 349 | } 350 | } 351 | return status; 352 | } 353 | } 354 | 355 | private List> partition(final List toPartition, int perPartition) { 356 | final List> finalPartitions = new ArrayList>(); 357 | List partition = new ArrayList(); 358 | Iterator toPartitionIterator = toPartition.iterator(); 359 | for (int i = 0; i < toPartition.size(); i++) { 360 | if (!toPartitionIterator.hasNext()) { 361 | break; 362 | } 363 | 364 | if (i % perPartition == 0) { 365 | partition = new ArrayList(perPartition); 366 | finalPartitions.add(partition); 367 | } 368 | 369 | partition.add(toPartitionIterator.next()); 370 | } 371 | 372 | return finalPartitions; 373 | } 374 | 375 | private class ResourceGenerator implements Runnable { 376 | @Override 377 | public void run() { 378 | synchronized (lock) { 379 | try { 380 | final ListContainerInstancesRequest listRequest = new ListContainerInstancesRequest(); 381 | listRequest.setCluster(clusterName); 382 | 383 | final List instances = new ArrayList(); 384 | for (; ; ) { 385 | final ListContainerInstancesResult listResult = client.listContainerInstances(listRequest); 386 | final DescribeContainerInstancesRequest describeRequest = new DescribeContainerInstancesRequest(); 387 | describeRequest.setCluster(clusterName); 388 | describeRequest.setContainerInstances(listResult.getContainerInstanceArns()); 389 | final DescribeContainerInstancesResult describeResult = client.describeContainerInstances(describeRequest); 390 | instances.addAll(describeResult.getContainerInstances()); 391 | if (listResult.getNextToken() == null) { 392 | break; 393 | } 394 | listRequest.setNextToken(listResult.getNextToken()); 395 | } 396 | 397 | final List offers = new ArrayList(instances.size()); 398 | for (final ContainerInstance ci : instances) { 399 | if (!ci.isAgentConnected()) { 400 | // If the agent isn't connected StartTask won't work 401 | continue; 402 | } 403 | 404 | final Protos.Offer.Builder offerBuilder = Protos.Offer.newBuilder() 405 | .setHostname(ci.getContainerInstanceArn()) 406 | .setFrameworkId(frameworkInfo.getId()) 407 | .setId(Protos.OfferID.newBuilder().setValue(ci.getContainerInstanceArn()).build()) 408 | .setSlaveId(Protos.SlaveID.newBuilder().setValue(ci.getContainerInstanceArn()).build()); 409 | 410 | offerBuilder.addResources( 411 | Protos.Resource.newBuilder() 412 | .setType(Protos.Value.Type.SCALAR) 413 | .setName(MESOS_DISK) // Currently not supported in Amazon ECS 414 | .setRole("*") 415 | .setScalar( 416 | Protos.Value.Scalar.newBuilder() 417 | .setValue(0.0) 418 | .build() 419 | ) 420 | ); 421 | 422 | for (final Resource resource : ci.getRemainingResources()) { 423 | if (AWS_ECS_CPU.equals(resource.getName())) { 424 | offerBuilder.addResources( 425 | Protos.Resource.newBuilder() 426 | .setType(Protos.Value.Type.SCALAR) 427 | .setName(MESOS_CPU) 428 | .setRole("*") 429 | .setScalar( 430 | Protos.Value.Scalar.newBuilder() 431 | .setValue(resource.getIntegerValue().doubleValue() / 1024.0) // CPU is a normalized int, divide by 1024 to get to what Mesos expects 432 | .build() 433 | ) 434 | ); 435 | } else if (AWS_ECS_MEM.equals(resource.getName())) { 436 | offerBuilder.addResources( 437 | Protos.Resource.newBuilder() 438 | .setType(Protos.Value.Type.SCALAR) 439 | .setName(MESOS_MEM) 440 | .setRole("*") 441 | .setScalar( 442 | Protos.Value.Scalar.newBuilder() 443 | .setValue(resource.getIntegerValue().doubleValue()) 444 | .build() 445 | ) 446 | ); 447 | } else if (AWS_ECS_PORTS.equals(resource.getName())) { 448 | offerBuilder.addResources( 449 | Protos.Resource.newBuilder() 450 | .setType(Protos.Value.Type.RANGES) 451 | .setName(MESOS_PORTS) 452 | .setRole("*") 453 | .setRanges(portRanges(resource)) 454 | ); 455 | } 456 | } 457 | offers.add(offerBuilder.build()); 458 | } 459 | 460 | scheduler.resourceOffers(ECSSchedulerDriver.this, offers); 461 | } catch (Throwable e) { 462 | // Keep this thread running no matter what 463 | } 464 | } 465 | } 466 | 467 | private Protos.Value.Ranges portRanges(final Resource portResource) { 468 | final Protos.Value.Ranges.Builder rangeBuilder = Protos.Value.Ranges.newBuilder(); 469 | final List reservedPorts = new ArrayList(); 470 | //Bottom end of any port range is 0 + 1. We add it manually to get things started. 471 | reservedPorts.add(0); 472 | for (final String port : portResource.getStringSetValue()) { 473 | reservedPorts.add(Integer.valueOf(port)); 474 | } 475 | // We need the list of reserved ports sorted to properly turn them into the Mesos expected port ranges. 476 | Collections.sort(reservedPorts); 477 | for (int i = 0; i < reservedPorts.size(); i++) { 478 | Integer realStart = reservedPorts.get(i) + 1; 479 | // Don't allow port usage past the maximum 480 | Integer realEnd = AWS_ECS_MAX_PORT; 481 | if (i + 1 < reservedPorts.size()) { 482 | realEnd = Math.min(reservedPorts.get(i + 1) - 1, realEnd); 483 | } 484 | if (realEnd >= realStart) { 485 | rangeBuilder.addRange( 486 | Protos.Value.Range.newBuilder() 487 | .setBegin(realStart.longValue()) 488 | .setEnd(realEnd.longValue()) 489 | .build() 490 | ); 491 | } 492 | } 493 | return rangeBuilder.build(); 494 | } 495 | } 496 | } 497 | --------------------------------------------------------------------------------