@Test public void testExecution() { expect(statsProvider.makeCounter(EXPLICIT_STAT_NAME)).andReturn(explicitRuns); expect(statsProvider.makeCounter(IMPLICIT_STAT_NAME)).andReturn(implicitRuns); clock = FakeScheduledExecutor.scheduleAtFixedRateExecutor(executorService, 2, 5); IScheduledTask task = makeTask("id1", TaskTestUtil.makeConfig(TaskTestUtil.JOB)); storageUtil.expectOperations(); storageUtil .expectTaskFetch(Query.unscoped().byStatus(Tasks.SLAVE_ASSIGNED_STATES), task) .times(5); driver.reconcileTasks(ImmutableSet.of(TASK_TO_PROTO.apply(task))); expectLastCall().times(5); driver.reconcileTasks(ImmutableSet.of()); expectLastCall().times(2); control.replay(); TaskReconciler reconciler = new TaskReconciler(SETTINGS, storageUtil.storage, driver, executorService, statsProvider); reconciler.startAsync().awaitRunning(); clock.advance(INITIAL_DELAY); assertEquals(1L, explicitRuns.get()); assertEquals(0L, implicitRuns.get()); clock.advance(SPREAD); assertEquals(1L, explicitRuns.get()); assertEquals(1L, implicitRuns.get()); clock.advance(EXPLICIT_SCHEDULE); assertEquals(2L, explicitRuns.get()); assertEquals(1L, implicitRuns.get()); clock.advance(IMPLICT_SCHEDULE); assertEquals(5L, explicitRuns.get()); assertEquals(2L, implicitRuns.get()); }
@VisibleForTesting SchedulerLifecycle( final NonVolatileStorage storage, final Lifecycle lifecycle, final Driver driver, final DelayedActions delayedActions, final ShutdownRegistry shutdownRegistry, StatsProvider statsProvider, final ServiceManagerIface schedulerActiveServiceManager) { requireNonNull(storage); requireNonNull(lifecycle); requireNonNull(driver); requireNonNull(delayedActions); requireNonNull(shutdownRegistry); statsProvider.makeGauge( REGISTERED_GAUGE, new Supplier<Integer>() { @Override public Integer get() { return registrationAcked.get() ? 1 : 0; } }); for (final State state : State.values()) { statsProvider.makeGauge( stateGaugeName(state), new Supplier<Integer>() { @Override public Integer get() { return (state == stateMachine.getState()) ? 1 : 0; } }); } shutdownRegistry.addAction( new ExceptionalCommand<TimeoutException>() { @Override public void execute() throws TimeoutException { stateMachine.transition(State.DEAD); schedulerActiveServiceManager.stopAsync(); schedulerActiveServiceManager.awaitStopped(5L, TimeUnit.SECONDS); } }); final Consumer<Transition<State>> prepareStorage = new Consumer<Transition<State>>() { @Override public void accept(Transition<State> transition) { storage.prepare(); stateMachine.transition(State.STORAGE_PREPARED); } }; final Consumer<Transition<State>> handleLeading = new Consumer<Transition<State>>() { @Override public void accept(Transition<State> transition) { LOG.info("Elected as leading scheduler!"); storage.start( stores -> { // If storage backfill operations are necessary, they can be done here. }); driver.startAsync().awaitRunning(); delayedActions.onRegistrationTimeout( () -> { if (!registrationAcked.get()) { LOG.error("Framework has not been registered within the tolerated delay."); stateMachine.transition(State.DEAD); } }); delayedActions.onAutoFailover( () -> { LOG.info("Triggering automatic failover."); stateMachine.transition(State.DEAD); }); } }; final Consumer<Transition<State>> handleRegistered = new Consumer<Transition<State>>() { @Override public void accept(Transition<State> transition) { registrationAcked.set(true); delayedActions.blockingDriverJoin( () -> { driver.blockUntilStopped(); LOG.info("Driver exited, terminating lifecycle."); stateMachine.transition(State.DEAD); }); // TODO(ksweeney): Extract leader advertisement to its own service. schedulerActiveServiceManager.startAsync().awaitHealthy(); try { leaderControl.get().advertise(); } catch (SingletonService.AdvertiseException | InterruptedException e) { LOG.error("Failed to advertise leader, shutting down."); throw new RuntimeException(e); } } }; final Consumer<Transition<State>> shutDown = new Consumer<Transition<State>>() { private final AtomicBoolean invoked = new AtomicBoolean(false); @Override public void accept(Transition<State> transition) { if (!invoked.compareAndSet(false, true)) { LOG.info("Shutdown already invoked, ignoring extra call."); return; } // TODO(wfarner): Consider using something like guava's Closer to abstractly tear down // resources here. try { LeaderControl control = leaderControl.get(); if (control != null) { try { control.leave(); } catch (SingletonService.LeaveException e) { LOG.warn("Failed to leave leadership: " + e, e); } } // TODO(wfarner): Re-evaluate tear-down ordering here. Should the top-level shutdown // be invoked first, or the underlying critical components? driver.stopAsync().awaitTerminated(); storage.stop(); } finally { lifecycle.shutdown(); } } }; stateMachine = StateMachine.<State>builder("SchedulerLifecycle") .initialState(State.IDLE) .logTransitions() .addState( dieOnError(Consumers.filter(NOT_DEAD, prepareStorage)), State.IDLE, State.PREPARING_STORAGE, State.DEAD) .addState(State.PREPARING_STORAGE, State.STORAGE_PREPARED, State.DEAD) .addState( dieOnError(Consumers.filter(NOT_DEAD, handleLeading)), State.STORAGE_PREPARED, State.LEADER_AWAITING_REGISTRATION, State.DEAD) .addState( dieOnError(Consumers.filter(NOT_DEAD, handleRegistered)), State.LEADER_AWAITING_REGISTRATION, State.ACTIVE, State.DEAD) .addState(State.ACTIVE, State.DEAD) .addState( State.DEAD, // Allow cycles in DEAD to prevent throwing and avoid the need for call-site // checking. State.DEAD) .onAnyTransition(Consumers.filter(IS_DEAD, shutDown)) .build(); this.leadershipListener = new SchedulerCandidateImpl(stateMachine, leaderControl); }