/** * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.aurora.scheduler.app.local; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.eventbus.EventBus; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import org.apache.aurora.scheduler.app.local.simulator.events.Started; import org.apache.aurora.scheduler.mesos.DriverFactory; import org.apache.mesos.Protos.ExecutorID; import org.apache.mesos.Protos.Filters; import org.apache.mesos.Protos.FrameworkID; import org.apache.mesos.Protos.MasterInfo; import org.apache.mesos.Protos.Offer; import org.apache.mesos.Protos.OfferID; import org.apache.mesos.Protos.Request; import org.apache.mesos.Protos.SlaveID; import org.apache.mesos.Protos.Status; import org.apache.mesos.Protos.TaskID; import org.apache.mesos.Protos.TaskInfo; import org.apache.mesos.Protos.TaskState; import org.apache.mesos.Protos.TaskStatus; import org.apache.mesos.Scheduler; import org.apache.mesos.SchedulerDriver; import org.apache.mesos.v1.Protos; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static java.util.Objects.requireNonNull; /** * A simulated master for use in scheduler testing. */ @SuppressWarnings("deprecation") public class FakeMaster implements SchedulerDriver, DriverFactory { private static final Logger LOG = LoggerFactory.getLogger(FakeMaster.class); private final Map<TaskID, Task> activeTasks = Collections.synchronizedMap(Maps.newHashMap()); private final Map<OfferID, Offer> idleOffers = Collections.synchronizedMap(Maps.newHashMap()); private final Map<OfferID, Offer> sentOffers = Collections.synchronizedMap(Maps.newHashMap()); private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private final CountDownLatch stopped = new CountDownLatch(1); private final SettableFuture<Scheduler> schedulerFuture = SettableFuture.create(); private final EventBus eventBus; @Inject FakeMaster(EventBus eventBus) { this.eventBus = requireNonNull(eventBus); } public void addResources(Iterable<Offer> offers) { assertNotStopped(); synchronized (idleOffers) { for (Offer offer : offers) { checkState(!idleOffers.containsKey(offer.getId()), "Duplicate offer id " + offer.getId()); idleOffers.put(offer.getId(), offer); } } } public void changeState(TaskID task, TaskState state) { assertNotStopped(); checkState(activeTasks.containsKey(task), "Task " + task + " does not exist."); Futures.getUnchecked(schedulerFuture).statusUpdate(this, TaskStatus.newBuilder() .setTaskId(task) .setState(state) .build()); } @Override public SchedulerDriver create( Scheduler scheduler, Optional<Protos.Credential> credentials, Protos.FrameworkInfo frameworkInfo, String master) { schedulerFuture.set(scheduler); return this; } @Override public Status start() { assertNotStopped(); Futures.getUnchecked(schedulerFuture).registered(this, FrameworkID.newBuilder().setValue("local").build(), MasterInfo.getDefaultInstance()); eventBus.post(new Started()); executor.scheduleAtFixedRate( () -> { List<Offer> allOffers; synchronized (sentOffers) { synchronized (idleOffers) { sentOffers.putAll(idleOffers); allOffers = ImmutableList.copyOf(idleOffers.values()); idleOffers.clear(); } } if (allOffers.isEmpty()) { LOG.info("All offers consumed, suppressing offer cycle."); } else { Futures.getUnchecked(schedulerFuture).resourceOffers(this, allOffers); } }, 1, 5, TimeUnit.SECONDS); return Status.DRIVER_RUNNING; } @Override public Status stop(boolean failover) { return stop(); } @Override public Status stop() { stopped.countDown(); MoreExecutors.shutdownAndAwaitTermination(executor, 1, TimeUnit.SECONDS); return Status.DRIVER_RUNNING; } @Override public Status abort() { throw new UnsupportedOperationException(); } @Override public Status join() { try { stopped.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return Status.DRIVER_RUNNING; } @Override public Status run() { assertNotStopped(); return Status.DRIVER_RUNNING; } @Override public Status requestResources(Collection<Request> requests) { throw new UnsupportedOperationException(); } private void assertNotStopped() { Preconditions.checkState(stopped.getCount() == 1, "Driver is stopped."); } private void checkState(boolean assertion, String failureMessage) { if (!assertion) { Futures.getUnchecked(schedulerFuture).error(this, failureMessage); stop(); throw new IllegalStateException(failureMessage); } } @Override public Status launchTasks( Collection<OfferID> offerIds, Collection<TaskInfo> tasks, Filters filters) { throw new UnsupportedOperationException(); } @Override public Status launchTasks(Collection<OfferID> offerIds, Collection<TaskInfo> tasks) { assertNotStopped(); OfferID id = Iterables.getOnlyElement(offerIds); Offer offer = sentOffers.remove(id); checkState(offer != null, "Offer " + id + " is invalid."); final TaskInfo task = Iterables.getOnlyElement(tasks); synchronized (activeTasks) { checkState( !activeTasks.containsKey(task.getTaskId()), "Task " + task.getTaskId() + " already exists."); activeTasks.put(task.getTaskId(), new Task(offer, task)); } executor.schedule( () -> Futures.getUnchecked(schedulerFuture).statusUpdate( this, TaskStatus.newBuilder() .setTaskId(task.getTaskId()) .setState(TaskState.TASK_RUNNING) .build()), 1, TimeUnit.SECONDS); return Status.DRIVER_RUNNING; } @Override public Status launchTasks(OfferID offerId, Collection<TaskInfo> tasks, Filters filters) { throw new UnsupportedOperationException(); } @Override public Status launchTasks(OfferID offerId, Collection<TaskInfo> tasks) { throw new UnsupportedOperationException(); } @Override public Status killTask(TaskID taskId) { assertNotStopped(); Task task = activeTasks.remove(taskId); checkState(task != null, "Task " + taskId + " not found."); idleOffers.put(task.getOffer().getId(), task.getOffer()); Futures.getUnchecked(schedulerFuture).statusUpdate(this, TaskStatus.newBuilder() .setTaskId(taskId) .setState(TaskState.TASK_FINISHED) .build()); return Status.DRIVER_RUNNING; } @Override public Status declineOffer(OfferID offerId, Filters filters) { assertNotStopped(); throw new UnsupportedOperationException(); } @Override public Status declineOffer(OfferID offerId) { assertNotStopped(); return null; } @Override public Status reviveOffers() { assertNotStopped(); throw new UnsupportedOperationException(); } @Override public Status acknowledgeStatusUpdate(TaskStatus status) { assertNotStopped(); throw new UnsupportedOperationException(); } @Override public Status sendFrameworkMessage(ExecutorID executorId, SlaveID slaveId, byte[] data) { throw new UnsupportedOperationException(); } @Override public Status reconcileTasks(Collection<TaskStatus> statuses) { throw new UnsupportedOperationException(); } @Override public Status acceptOffers( Collection<OfferID> offerIds, Collection<Offer.Operation> operations, Filters filters) { throw new UnsupportedOperationException(); } @Override public Status suppressOffers() { throw new UnsupportedOperationException(); } private static final class Task { private final Offer offer; private final TaskInfo taskInfo; private Task(Offer offer, TaskInfo taskInfo) { this.offer = offer; this.taskInfo = taskInfo; } Offer getOffer() { return offer; } TaskInfo getTask() { return taskInfo; } } }