/** * 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.http; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.ServletContextListener; import javax.ws.rs.core.MediaType; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableSet; import com.google.common.net.HostAndPort; import com.google.common.util.concurrent.RateLimiter; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.util.Modules; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.api.json.JSONConfiguration; import org.apache.aurora.GuavaUtils.ServiceManagerIface; import org.apache.aurora.common.quantity.Amount; import org.apache.aurora.common.quantity.Time; import org.apache.aurora.common.stats.StatsProvider; import org.apache.aurora.common.testing.easymock.EasyMockTest; import org.apache.aurora.common.thrift.Endpoint; import org.apache.aurora.common.thrift.ServiceInstance; import org.apache.aurora.common.util.BackoffStrategy; import org.apache.aurora.gen.ServerInfo; import org.apache.aurora.scheduler.AppStartup; import org.apache.aurora.scheduler.SchedulerServicesModule; import org.apache.aurora.scheduler.TierManager; import org.apache.aurora.scheduler.app.LifecycleModule; import org.apache.aurora.scheduler.app.ServiceGroupMonitor; import org.apache.aurora.scheduler.async.AsyncModule; import org.apache.aurora.scheduler.cron.CronJobManager; import org.apache.aurora.scheduler.http.api.GsonMessageBodyHandler; import org.apache.aurora.scheduler.offers.OfferManager; import org.apache.aurora.scheduler.scheduling.RescheduleCalculator; import org.apache.aurora.scheduler.scheduling.TaskGroups; import org.apache.aurora.scheduler.scheduling.TaskGroups.TaskGroupsSettings; import org.apache.aurora.scheduler.scheduling.TaskScheduler; import org.apache.aurora.scheduler.state.LockManager; import org.apache.aurora.scheduler.stats.StatsModule; import org.apache.aurora.scheduler.storage.Storage; import org.apache.aurora.scheduler.storage.entities.IServerInfo; import org.apache.aurora.scheduler.storage.testing.StorageTestUtil; import org.apache.aurora.scheduler.testing.FakeStatsProvider; import org.junit.Before; import static org.apache.aurora.scheduler.http.JettyServerModule.makeServletContextListener; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; import static org.junit.Assert.assertNotNull; /** * TODO(wfarner): Break apart ServletModule so test setup isn't so involved. * TODO(wfarner): Come up with an approach for these tests that doesn't require starting an actual * HTTP server for each test case. * */ public abstract class AbstractJettyTest extends EasyMockTest { private Injector injector; protected StorageTestUtil storage; protected HostAndPort httpServer; private AtomicReference<ImmutableSet<ServiceInstance>> schedulers; /** * Subclasses should override with a module that configures the servlets they are testing. * * @return A module used in the creation of the servlet container's child injector. */ protected Module getChildServletModule() { return Modules.EMPTY_MODULE; } @Before public void setUpBase() throws Exception { storage = new StorageTestUtil(this); ServiceGroupMonitor serviceGroupMonitor = createMock(ServiceGroupMonitor.class); injector = Guice.createInjector( new StatsModule(), new LifecycleModule(), new SchedulerServicesModule(), new AsyncModule(), new AbstractModule() { <T> T bindMock(Class<T> clazz) { T mock = createMock(clazz); bind(clazz).toInstance(mock); return mock; } @Override protected void configure() { bind(StatsProvider.class).toInstance(new FakeStatsProvider()); bind(Storage.class).toInstance(storage.storage); bind(IServerInfo.class).toInstance(IServerInfo.build(new ServerInfo() .setClusterName("unittest") .setStatsUrlPrefix("none"))); bind(TaskGroupsSettings.class).toInstance( new TaskGroupsSettings( Amount.of(1L, Time.MILLISECONDS), bindMock(BackoffStrategy.class), RateLimiter.create(1000), 5)); bind(ServiceGroupMonitor.class).toInstance(serviceGroupMonitor); bindMock(CronJobManager.class); bindMock(LockManager.class); bindMock(OfferManager.class); bindMock(RescheduleCalculator.class); bindMock(TaskScheduler.class); bindMock(TierManager.class); bindMock(Thread.UncaughtExceptionHandler.class); bindMock(TaskGroups.TaskGroupBatchWorker.class); bind(ServletContextListener.class).toProvider(() -> { return makeServletContextListener(injector, getChildServletModule()); }); } }, new JettyServerModule(false)); schedulers = new AtomicReference<>(ImmutableSet.of()); serviceGroupMonitor.start(); expectLastCall(); expect(serviceGroupMonitor.get()).andAnswer(schedulers::get).anyTimes(); } protected void setLeadingScheduler(String host, int port) { schedulers.set( ImmutableSet.of(new ServiceInstance().setServiceEndpoint(new Endpoint(host, port)))); } protected void unsetLeadingSchduler() { schedulers.set(ImmutableSet.of()); } protected void replayAndStart() { control.replay(); try { ServiceManagerIface service = injector.getInstance(Key.get(ServiceManagerIface.class, AppStartup.class)); service.startAsync().awaitHealthy(); addTearDown(() -> { service.stopAsync().awaitStopped(5L, TimeUnit.SECONDS); }); } catch (Exception e) { throw Throwables.propagate(e); } httpServer = injector.getInstance(HttpService.class).getAddress(); // By default we'll set this instance to be the leader. setLeadingScheduler(httpServer.getHostText(), httpServer.getPort()); } protected String makeUrl(String path) { return String.format("http://%s:%s%s", httpServer.getHostText(), httpServer.getPort(), path); } protected WebResource.Builder getPlainRequestBuilder(String path) { assertNotNull("HTTP server must be started first", httpServer); Client client = Client.create(new DefaultClientConfig()); return client.resource(makeUrl(path)).getRequestBuilder(); } protected WebResource.Builder getRequestBuilder(String path) { assertNotNull("HTTP server must be started first", httpServer); ClientConfig config = new DefaultClientConfig(); config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE); config.getClasses().add(GsonMessageBodyHandler.class); Client client = Client.create(config); // Disable redirects so we can unit test them. client.setFollowRedirects(false); return client.resource(makeUrl(path)).getRequestBuilder().accept(MediaType.APPLICATION_JSON); } }