/** * 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.mesos; import java.util.concurrent.Executors; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.net.InetAddresses; import com.google.protobuf.ByteString; import org.apache.aurora.common.base.ExceptionalSupplier; import org.apache.aurora.common.testing.easymock.EasyMockTest; import org.apache.aurora.common.util.BackoffHelper; import org.apache.aurora.scheduler.stats.CachedCounters; import org.apache.aurora.scheduler.storage.testing.StorageTestUtil; import org.apache.aurora.scheduler.testing.FakeStatsProvider; import org.apache.mesos.v1.Protos.AgentID; import org.apache.mesos.v1.Protos.ExecutorID; import org.apache.mesos.v1.Protos.FrameworkID; import org.apache.mesos.v1.Protos.FrameworkInfo; import org.apache.mesos.v1.Protos.MasterInfo; import org.apache.mesos.v1.Protos.Offer; import org.apache.mesos.v1.Protos.OfferID; import org.apache.mesos.v1.Protos.TaskID; import org.apache.mesos.v1.Protos.TaskState; import org.apache.mesos.v1.Protos.TaskStatus; import org.apache.mesos.v1.Protos.TaskStatus.Source; import org.apache.mesos.v1.scheduler.Mesos; import org.apache.mesos.v1.scheduler.Protos.Call; import org.apache.mesos.v1.scheduler.Protos.Event; import org.easymock.Capture; import org.junit.Before; import org.junit.Test; import static org.easymock.EasyMock.anyObject; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class VersionedMesosSchedulerImplTest extends EasyMockTest { private MesosCallbackHandler handler; private StorageTestUtil storageUtil; private Mesos driver; private FakeStatsProvider statsProvider; private FrameworkInfoFactory infoFactory; private BackoffHelper backoffHelper; private VersionedMesosSchedulerImpl scheduler; private static final String AGENT_HOST = "slave-hostname"; private static final AgentID AGENT_ID = AgentID.newBuilder().setValue("slave-id").build(); private static final String FRAMEWORK_ID = "framework-id"; private static final FrameworkID FRAMEWORK = FrameworkID.newBuilder().setValue(FRAMEWORK_ID).build(); private static final FrameworkInfo FRAMEWORK_INFO = FrameworkInfo.newBuilder() .setName("name") .setUser("user") .setCheckpoint(true) .setFailoverTimeout(1000) .build(); private static final String MASTER_ID = "master-id"; private static final MasterInfo MASTER = MasterInfo.newBuilder() .setId(MASTER_ID) .setIp(InetAddresses.coerceToInteger(InetAddresses.forString("1.2.3.4"))) //NOPMD .setPort(5050).build(); private static final OfferID OFFER_ID = OfferID.newBuilder().setValue("offer-id").build(); private static final Offer OFFER = Offer.newBuilder() .setFrameworkId(FRAMEWORK) .setAgentId(AGENT_ID) .setHostname(AGENT_HOST) .setId(OFFER_ID) .build(); private static final TaskStatus STATUS = TaskStatus.newBuilder() .setState(TaskState.TASK_RUNNING) .setSource(Source.SOURCE_AGENT) .setMessage("message") .setTimestamp(1D) .setTaskId(TaskID.newBuilder().setValue("task-id").build()) .build(); private static final ExecutorID EXECUTOR_ID = ExecutorID.newBuilder().setValue("executor-id").build(); private static final Event OFFER_EVENT = Event.newBuilder() .setType(Event.Type.OFFERS) .setOffers(Event.Offers.newBuilder() .addOffers(OFFER)) .build(); private static final Event SUBSCRIBED_EVENT = Event.newBuilder() .setType(Event.Type.SUBSCRIBED) .setSubscribed(Event.Subscribed.newBuilder() .setFrameworkId(FRAMEWORK) .setHeartbeatIntervalSeconds(15) .setMasterInfo(MASTER)) .build(); private static final Event UPDATE_EVENT = Event.newBuilder() .setType(Event.Type.UPDATE) .setUpdate(Event.Update.newBuilder() .setStatus(STATUS)) .build(); private static final Event RESCIND_EVENT = Event.newBuilder() .setType(Event.Type.RESCIND) .setRescind(Event.Rescind.newBuilder().setOfferId(OFFER_ID)) .build(); private static final Event MESSAGE_EVENT = Event.newBuilder() .setType(Event.Type.MESSAGE) .setMessage(Event.Message.newBuilder() .setAgentId(AGENT_ID) .setExecutorId(EXECUTOR_ID) .setData(ByteString.copyFromUtf8("message"))) .build(); private static final Event ERROR_EVENT = Event.newBuilder() .setType(Event.Type.ERROR) .setError(Event.Error.newBuilder().setMessage("Oh no!")) .build(); private static final Event FAILED_AGENT_EVENT = Event.newBuilder() .setType(Event.Type.FAILURE) .setFailure(Event.Failure.newBuilder().setAgentId(AGENT_ID)) .build(); @Before public void setUp() { handler = createMock(MesosCallbackHandler.class); storageUtil = new StorageTestUtil(this); driver = createMock(Mesos.class); statsProvider = new FakeStatsProvider(); infoFactory = createMock(FrameworkInfoFactory.class); backoffHelper = createMock(BackoffHelper.class); scheduler = new VersionedMesosSchedulerImpl( handler, new CachedCounters(statsProvider), statsProvider, storageUtil.storage, Executors.newSingleThreadExecutor(), backoffHelper, infoFactory); } @Test(timeout = 300000) public void testConnected() throws Exception { // Once the V1 driver has connected, we need to establish a subscription to get events expectFrameworkInfoRead(); Capture<Call> subscribeCapture = createCapture(); driver.send(capture(subscribeCapture)); expectLastCall().once(); Capture<ExceptionalSupplier<Boolean, RuntimeException>> supplierCapture = createCapture(); backoffHelper.doUntilSuccess(capture(supplierCapture)); expectLastCall().once(); control.replay(); scheduler.connected(driver); waitUntilCaptured(supplierCapture); assertTrue(supplierCapture.hasCaptured()); ExceptionalSupplier<Boolean, RuntimeException> supplier = supplierCapture.getValue(); // Make one connection attempt supplier.get(); assertTrue(subscribeCapture.hasCaptured()); Call subscribe = subscribeCapture.getValue(); assertEquals(subscribe.getType(), Call.Type.SUBSCRIBE); assertEquals(subscribe.getFrameworkId(), FRAMEWORK); assertEquals( subscribe.getSubscribe().getFrameworkInfo(), FRAMEWORK_INFO.toBuilder().setId(FRAMEWORK).build()); } @Test(timeout = 300000) public void testAttemptSubscriptionSuccessful() throws Exception { expectFrameworkInfoRead(); // Other tests already check if what we send is correct. driver.send(anyObject()); expectLastCall().once(); driver.send(anyObject()); expectLastCall().once(); Capture<ExceptionalSupplier<Boolean, RuntimeException>> supplierCapture = createCapture(); backoffHelper.doUntilSuccess(capture(supplierCapture)); expectLastCall().once(); handler.handleRegistration(FRAMEWORK, MASTER); control.replay(); scheduler.connected(driver); waitUntilCaptured(supplierCapture); assertTrue(supplierCapture.hasCaptured()); ExceptionalSupplier<Boolean, RuntimeException> supplier = supplierCapture.getValue(); // Each attempt should return false. assertFalse(supplier.get()); assertFalse(supplier.get()); // After the callback we should return true because it was successful. scheduler.received(driver, SUBSCRIBED_EVENT); assertTrue(supplier.get()); } @Test(timeout = 300000) public void testAttemptSubscriptionHaltsAfterDisconnection() throws Exception { storageUtil.expectOperations(); expect(storageUtil.schedulerStore.fetchFrameworkId()).andReturn(Optional.of(FRAMEWORK_ID)); expect(infoFactory.getFrameworkInfo()).andReturn(FRAMEWORK_INFO); // Other tests already check if what we send is correct. driver.send(anyObject()); expectLastCall().once(); Capture<ExceptionalSupplier<Boolean, RuntimeException>> supplierCapture = createCapture(); backoffHelper.doUntilSuccess(capture(supplierCapture)); expectLastCall().once(); handler.handleDisconnection(); control.replay(); scheduler.connected(driver); waitUntilCaptured(supplierCapture); assertTrue(supplierCapture.hasCaptured()); ExceptionalSupplier<Boolean, RuntimeException> supplier = supplierCapture.getValue(); assertFalse(supplier.get()); // After disconnection we should stop. scheduler.disconnected(driver); assertTrue(supplier.get()); } private static void waitUntilCaptured(Capture<?> capture) throws Exception { while (!capture.hasCaptured()) { Thread.sleep(1000); } } @Test public void testDisconnected() { handler.handleDisconnection(); control.replay(); scheduler.disconnected(driver); } @Test public void testSubscription() { handler.handleRegistration(FRAMEWORK, MASTER); control.replay(); scheduler.received(driver, SUBSCRIBED_EVENT); assertEquals(1L, statsProvider.getLongValue("mesos_scheduler_event_SUBSCRIBED")); } @Test public void testOffers() { handler.handleRegistration(FRAMEWORK, MASTER); handler.handleOffers(ImmutableList.of(OFFER)); control.replay(); scheduler.received(driver, SUBSCRIBED_EVENT); scheduler.received(driver, OFFER_EVENT); assertEquals(1L, statsProvider.getLongValue("mesos_scheduler_event_OFFERS")); } @Test public void testRescind() { handler.handleRescind(OFFER_ID); control.replay(); scheduler.received(driver, RESCIND_EVENT); assertEquals(1L, statsProvider.getLongValue("mesos_scheduler_event_RESCIND")); } @Test public void testUpdate() { handler.handleUpdate(STATUS); control.replay(); scheduler.received(driver, UPDATE_EVENT); assertEquals(1L, statsProvider.getLongValue("mesos_scheduler_event_UPDATE")); } @Test(expected = IllegalStateException.class) public void testBadOrdering() { // get an offer before the driver has registered control.replay(); scheduler.received(driver, OFFER_EVENT); } @Test public void testMessage() { handler.handleMessage(EXECUTOR_ID, AGENT_ID); control.replay(); scheduler.received(driver, MESSAGE_EVENT); assertEquals(1L, statsProvider.getLongValue("mesos_scheduler_event_MESSAGE")); } @Test public void testError() { handler.handleError("Oh no!"); control.replay(); scheduler.received(driver, ERROR_EVENT); assertEquals(1L, statsProvider.getLongValue("mesos_scheduler_event_ERROR")); } @Test public void testFailedAgent() { handler.handleLostAgent(AGENT_ID); control.replay(); scheduler.received(driver, FAILED_AGENT_EVENT); assertEquals(1L, statsProvider.getLongValue("mesos_scheduler_event_FAILURE")); } @Test(timeout = 300000) public void testSubscribeDisconnectSubscribeCycle() throws Exception { expectFrameworkInfoRead(); Capture<ExceptionalSupplier<Boolean, RuntimeException>> firstSubscribeAttempt = createCapture(); backoffHelper.doUntilSuccess(capture(firstSubscribeAttempt)); expectLastCall().once(); handler.handleRegistration(FRAMEWORK, MASTER); handler.handleDisconnection(); Capture<ExceptionalSupplier<Boolean, RuntimeException>> secondSubscribeAttempt = createCapture(); backoffHelper.doUntilSuccess(capture(secondSubscribeAttempt)); expectLastCall().once(); // Second subscribe should call the reregistration handler. handler.handleReregistration(MASTER); control.replay(); scheduler.connected(driver); waitUntilCaptured(firstSubscribeAttempt); assertTrue(firstSubscribeAttempt.hasCaptured()); scheduler.received(driver, SUBSCRIBED_EVENT); scheduler.disconnected(driver); scheduler.connected(driver); waitUntilCaptured(secondSubscribeAttempt); assertTrue(secondSubscribeAttempt.hasCaptured()); scheduler.received(driver, SUBSCRIBED_EVENT); } private void expectFrameworkInfoRead() { storageUtil.expectOperations(); expect(storageUtil.schedulerStore.fetchFrameworkId()) .andReturn(Optional.of(FRAMEWORK_ID)) .anyTimes(); expect(infoFactory.getFrameworkInfo()).andReturn(FRAMEWORK_INFO).anyTimes(); } }