/* * 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 com.groupon.mesos.scheduler; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static org.apache.mesos.Protos.Status.DRIVER_ABORTED; import java.io.IOException; import java.util.List; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.eventbus.Subscribe; import com.groupon.mesos.scheduler.SchedulerMessageEnvelope.ExecutorToFrameworkMessageEnvelope; import com.groupon.mesos.scheduler.SchedulerMessageEnvelope.FrameworkErrorMessageEnvelope; import com.groupon.mesos.scheduler.SchedulerMessageEnvelope.FrameworkRegisteredMessageEnvelope; import com.groupon.mesos.scheduler.SchedulerMessageEnvelope.FrameworkReregisteredMessageEnvelope; import com.groupon.mesos.scheduler.SchedulerMessageEnvelope.LostSlaveMessageEnvelope; import com.groupon.mesos.scheduler.SchedulerMessageEnvelope.RemoteMessageEnvelope; import com.groupon.mesos.scheduler.SchedulerMessageEnvelope.RescindResourceOfferMessageEnvelope; import com.groupon.mesos.scheduler.SchedulerMessageEnvelope.ResourceOffersMessageEnvelope; import com.groupon.mesos.scheduler.SchedulerMessageEnvelope.StatusUpdateMessageEnvelope; import com.groupon.mesos.util.Log; import com.groupon.mesos.util.ManagedEventBus; import com.groupon.mesos.util.UPID; import org.apache.mesos.Protos.FrameworkID; import org.apache.mesos.Protos.MasterInfo; import org.apache.mesos.Protos.Offer; import org.apache.mesos.Protos.SlaveID; import org.apache.mesos.Protos.TaskStatus; import org.apache.mesos.Scheduler; import org.apache.mesos.SchedulerDriver; import mesos.internal.Messages.ExecutorToFrameworkMessage; import mesos.internal.Messages.FrameworkErrorMessage; import mesos.internal.Messages.FrameworkRegisteredMessage; import mesos.internal.Messages.FrameworkReregisteredMessage; import mesos.internal.Messages.LostSlaveMessage; import mesos.internal.Messages.RescindResourceOfferMessage; import mesos.internal.Messages.ResourceOffersMessage; import mesos.internal.Messages.StatusUpdateAcknowledgementMessage; import mesos.internal.Messages.StatusUpdateMessage; /** * Local Message processing. Accepts messages from the outside and deliver them to the local scheduler. */ class LocalSchedulerMessageProcessor { private static final Log LOG = Log.getLog(LocalSchedulerMessageProcessor.class); private final SchedulerDriverContext context; private final ManagedEventBus eventBus; private final boolean implicitAcknowledgements; LocalSchedulerMessageProcessor(final SchedulerDriverContext context, final ManagedEventBus eventBus, final boolean implicitAcknowledgements) { this.context = checkNotNull(context, "context is null"); this.eventBus = checkNotNull(eventBus, "eventBus is null"); this.implicitAcknowledgements = implicitAcknowledgements; } @Subscribe public void frameworkRegistered(final FrameworkRegisteredMessageEnvelope envelope) { checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); final FrameworkRegisteredMessage frameworkRegisteredMessage = envelope.getMessage(); if (!masterIsValid(frameworkRegisteredMessage.getMasterInfo())) { return; } final FrameworkID frameworkId = frameworkRegisteredMessage.getFrameworkId(); context.connected(); context.setFailover(false); context.setFrameworkId(frameworkId); eventBus.post(new SchedulerCallback() { @Override public Runnable getCallback(final Scheduler scheduler, final SchedulerDriver schedulerDriver) { return new Runnable() { @Override public void run() { scheduler.registered(schedulerDriver, frameworkId, context.getMaster()); } }; } }); } @Subscribe public void frameworkReregistered(final FrameworkReregisteredMessageEnvelope envelope) { checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); final FrameworkReregisteredMessage frameworkReregisteredMessage = envelope.getMessage(); if (!masterIsValid(frameworkReregisteredMessage.getMasterInfo())) { return; } final FrameworkID frameworkId = frameworkReregisteredMessage.getFrameworkId(); checkState(frameworkId != null, "Received null framework reregistration message!"); checkState(frameworkId.equals(context.getFrameworkId()), "Received framework reregistration for %s but expected %s", frameworkId.getValue(), context.getFrameworkId().getValue()); context.connected(); context.setFailover(false); eventBus.post(new SchedulerCallback() { @Override public Runnable getCallback(final Scheduler scheduler, final SchedulerDriver schedulerDriver) { return new Runnable() { @Override public void run() { scheduler.reregistered(schedulerDriver, context.getMaster()); } }; } }); } @Subscribe public void frameworkResourceOffers(final ResourceOffersMessageEnvelope envelope) { checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); final UPID sender = envelope.getSender(); if (!driverIsConnected(sender)) { return; } final ResourceOffersMessage resourceOffersMessage = envelope.getMessage(); final List<Offer> offers = resourceOffersMessage.getOffersList(); final List<UPID> pids = ImmutableList.copyOf(Lists.transform(resourceOffersMessage.getPidsList(), UPID.getCreateFunction())); checkState(offers.size() == pids.size(), "Received %s offers but only %s pids!", offers.size(), pids.size()); int pidIndex = 0; for (final Offer offer : offers) { context.addOffer(offer.getId(), offer.getSlaveId(), pids.get(pidIndex++)); } eventBus.post(new SchedulerCallback() { @Override public Runnable getCallback(final Scheduler scheduler, final SchedulerDriver schedulerDriver) { return new Runnable() { @Override public void run() { scheduler.resourceOffers(schedulerDriver, resourceOffersMessage.getOffersList()); } }; } }); } @Subscribe public void frameworkRescindOffer(final RescindResourceOfferMessageEnvelope envelope) { checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); final UPID sender = envelope.getSender(); if (!driverIsConnected(sender)) { return; } final RescindResourceOfferMessage rescindResourceOfferMessage = envelope.getMessage(); context.removeAllOffers(rescindResourceOfferMessage.getOfferId()); eventBus.post(new SchedulerCallback() { @Override public Runnable getCallback(final Scheduler scheduler, final SchedulerDriver schedulerDriver) { return new Runnable() { @Override public void run() { scheduler.offerRescinded(schedulerDriver, rescindResourceOfferMessage.getOfferId()); } }; } }); } @Subscribe public void frameworkStatusUpdate(final StatusUpdateMessageEnvelope envelope) throws IOException { checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); final UPID sender = envelope.getSender(); if (!driverIsConnected(sender)) { return; } final StatusUpdateMessage statusUpdateMessage = envelope.getMessage(); final FrameworkID frameworkId = context.getFrameworkId(); final FrameworkID messageFrameworkId = statusUpdateMessage.getUpdate().getFrameworkId(); checkState(frameworkId.equals(messageFrameworkId), "Received Message for framework %s, but local id is %s", messageFrameworkId, frameworkId); final TaskStatus.Builder taskStatusBuilder = TaskStatus.newBuilder(statusUpdateMessage.getUpdate().getStatus()); final TaskStatus taskStatus; // If the update is driver-generated or master-generated, it does not require acknowledgement (from Mesos source code, sched.cpp). final Optional<UPID> pid = statusUpdateMessage.hasPid() ? Optional.of(UPID.create(statusUpdateMessage.getPid())) : Optional.<UPID>absent(); final boolean noAckRequired = envelope.getSender().equals(context.getDriverUPID()) || pid.isPresent() && pid.get().equals(context.getDriverUPID()); if (noAckRequired) { taskStatus = taskStatusBuilder.clearUuid().build(); } else { taskStatus = taskStatusBuilder.setUuid(statusUpdateMessage.getUpdate().getUuid()).build(); } eventBus.post(new SchedulerCallback() { @Override public Runnable getCallback(final Scheduler scheduler, final SchedulerDriver schedulerDriver) { return new Runnable() { @Override public void run() { scheduler.statusUpdate(schedulerDriver, taskStatus); } }; } }); if (implicitAcknowledgements && !noAckRequired) { final StatusUpdateAcknowledgementMessage statusUpdateAcknowledgementMessage = StatusUpdateAcknowledgementMessage.newBuilder() .setFrameworkId(frameworkId) .setSlaveId(statusUpdateMessage.getUpdate().getSlaveId()) .setTaskId(taskStatus.getTaskId()) .setUuid(statusUpdateMessage.getUpdate().getUuid()) .build(); eventBus.post(new RemoteMessageEnvelope(context.getDriverUPID(), context.getMasterUPID(), statusUpdateAcknowledgementMessage)); } } @Subscribe public void frameworkLostSlave(final LostSlaveMessageEnvelope envelope) { checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); final UPID sender = envelope.getSender(); if (!driverIsConnected(sender)) { return; } final LostSlaveMessage lostSlaveMessage = envelope.getMessage(); final SlaveID slaveId = lostSlaveMessage.getSlaveId(); context.removeSlave(slaveId); eventBus.post(new SchedulerCallback() { @Override public Runnable getCallback(final Scheduler scheduler, final SchedulerDriver schedulerDriver) { return new Runnable() { @Override public void run() { scheduler.slaveLost(schedulerDriver, slaveId); } }; } }); } @Subscribe public void frameworkFrameworkMessage(final ExecutorToFrameworkMessageEnvelope envelope) { checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); final UPID sender = envelope.getSender(); if (!driverIsConnected(sender)) { return; } final ExecutorToFrameworkMessage executorToFrameworkMessage = envelope.getMessage(); eventBus.post(new SchedulerCallback() { @Override public Runnable getCallback(final Scheduler scheduler, final SchedulerDriver schedulerDriver) { return new Runnable() { @Override public void run() { scheduler.frameworkMessage(schedulerDriver, executorToFrameworkMessage.getExecutorId(), executorToFrameworkMessage.getSlaveId(), executorToFrameworkMessage.getData().toByteArray()); } }; } }); } @Subscribe public void frameworkError(final FrameworkErrorMessageEnvelope envelope) { checkState(envelope.getRecipient().equals(context.getDriverUPID()), "Received a remote message for local delivery"); final UPID sender = envelope.getSender(); if (!driverIsConnected(sender)) { return; } final FrameworkErrorMessage frameworkErrorMessage = envelope.getMessage(); eventBus.post(new SchedulerCallback() { @Override public Runnable getCallback(final Scheduler scheduler, final SchedulerDriver schedulerDriver) { return new Runnable() { @Override public void run() { schedulerDriver.abort(); scheduler.error(schedulerDriver, frameworkErrorMessage.getMessage()); } }; } }); } private boolean masterIsValid(final MasterInfo masterInfo) { checkNotNull(masterInfo, "masterInfo is null"); if (context.isStateMachine(DRIVER_ABORTED)) { LOG.warn("driver is aborted!"); return false; } final MasterInfo currentMaster = context.getMaster(); if (currentMaster == null) { LOG.warn("Received registration from %s, but no master is leading, ignoring!", masterInfo.getId()); return false; } if (!masterInfo.equals(currentMaster)) { LOG.warn("Received registration from %s, leading master is %s, ignoring!", masterInfo, currentMaster); return false; } return true; } private boolean driverIsConnected(final UPID messageSender) { final MasterInfo master = context.connectedMaster(); if (master == null) { LOG.warn("Received message from %s, but no master is leading, ignoring!", messageSender); return false; } // Master PID may have changed in the context in the meantime. Don't rely on the context // to be up to date but resolve the MasterInfo that was retrieved earlier. final UPID masterUpid = UPID.create(master.getPid()); if (!masterUpid.equals(messageSender)) { LOG.warn("Received message from %s, leading master is %s, ignoring!", messageSender, masterUpid); return false; } return true; } }