/* * 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.addthis.hydra.job.web; import java.util.Collections; import com.addthis.basis.kv.KVPairs; import com.addthis.basis.util.LessStrings; import com.addthis.hydra.job.Job; import com.addthis.hydra.job.JobParameter; import com.addthis.hydra.job.JobTask; import com.addthis.hydra.job.alert.Group; import com.addthis.hydra.job.alert.GroupManager; import com.addthis.hydra.job.alert.JobAlertManager; import com.addthis.hydra.job.auth.PermissionsManager; import com.addthis.hydra.job.entity.JobCommand; import com.addthis.hydra.job.entity.JobCommandManager; import com.addthis.hydra.job.spawn.Spawn; import com.google.common.collect.Lists; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyCollectionOf; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class JobRequestHandlerImplTest { private Spawn spawn; private JobCommandManager jobCommandManager; private JobAlertManager jobAlertManager; private JobRequestHandlerImpl impl; private String username = "megatron"; private String token = "megatron"; private String sudo = null; private KVPairs kv; @Before public void setUp() { // mocks and stubs spawn = mock(Spawn.class); jobCommandManager = mock(JobCommandManager.class); jobAlertManager = mock(JobAlertManager.class); when(spawn.getJobCommandManager()).thenReturn(jobCommandManager); when(spawn.getJobAlertManager()).thenReturn(jobAlertManager); when(spawn.getPermissionsManager()).thenReturn(PermissionsManager.createManagerAllowAll()); when(jobCommandManager.getEntity("default-task")).thenReturn(new JobCommand()); impl = new JobRequestHandlerImpl(spawn); kv = new KVPairs(); } @Test public void createJob() throws Exception { // stub spawn calls Job job = new Job("new_job_id", "megatron"); when(spawn.createJob("megatron", -1, Collections.<String> emptyList(), "default", "default-task", false)).thenReturn(job); when(spawn.getJob("new_job_id")).thenReturn(job); kv.add("config", "my job config"); kv.add("command", "default-task"); assertSame("returned job", job, impl.createOrUpdateJob(kv, username, token, sudo, false)); // verify spawn calls verify(spawn).updateJob(job); verify(spawn).setJobConfig("new_job_id", "my job config"); verify(spawn).submitConfigUpdate("new_job_id", "megatron", null); } @Test public void createJob_MissingCommand() throws Exception { kv.add("config", "my job config"); callAndVerifyBadRequest(); verifyNoSpawnCreateJobCall(); } @Test public void createJob_InvalidCommand() throws Exception { kv.add("command", "bogus-task"); kv.add("config", "my job config"); callAndVerifyBadRequest(); verifyNoSpawnCreateJobCall(); } @Test public void createJob_MissingConfig() throws Exception { kv.add("command", "default-task"); callAndVerifyBadRequest(); verifyNoSpawnCreateJobCall(); } @Test public void createJob_ConfigTooLarge() throws Exception { kv.add("command", "default-task"); kv.add("config", LessStrings.repeat('x', 1_000_001)); callAndVerifyBadRequest(); verifyNoSpawnCreateJobCall(); } @Test public void updateJob() throws Exception { // stub spawn calls Job job = new Job("existing_job_id", "megatron"); job.setCommand("old-task"); when(spawn.getJob("existing_job_id")).thenReturn(job); kv.add("id", "existing_job_id"); kv.add("config", "my job config"); kv.add("command", "default-task"); assertSame("returned job", job, impl.createOrUpdateJob(kv, username, token, sudo, false)); assertEquals("command is updated", "default-task", job.getCommand()); verifyNoSpawnCreateJobCall(); // verify other spawn calls verify(spawn).updateJob(job); verify(spawn).setJobConfig("existing_job_id", "my job config"); verify(spawn).submitConfigUpdate("existing_job_id", "megatron", null); } @Test public void updateJob_InvalidJobId() throws Exception { kv.add("id", "bogus_job_id"); kv.add("config", "my job config"); kv.add("command", "default-task"); callAndVerifyBadRequest(); verify(spawn, never()).updateJob(any(Job.class)); } /** If command is not specified when updating a job, it should remain unchanged */ @Test public void updateJob_MissingCommand() throws Exception { // stub spawn calls Job job = new Job("existing_job_id", "megatron"); job.setCommand("old-task"); when(spawn.getJob("existing_job_id")).thenReturn(job); kv.add("id", "existing_job_id"); kv.add("config", "my job config"); assertSame("returned job", job, impl.createOrUpdateJob(kv, username, token, sudo, false)); assertEquals("command is unchanged", "old-task", job.getCommand()); } /** If specified command is invalid when updating a job, job should remain unchanged */ @Test public void updateJob_InvalidCommand() throws Exception { // stub spawn calls Job job = new Job("existing_job_id", "megatron"); job.setCommand("old-task"); when(spawn.getJob("existing_job_id")).thenReturn(job); kv.add("id", "existing_job_id"); kv.add("config", "my job config"); kv.add("command", "bogus-task"); callAndVerifyBadRequest(); assertEquals("command is unchanged", "old-task", job.getCommand()); verify(spawn, never()).updateJob(job); } /** If config is not specified when updating a job, it should remain unchanged */ @Test public void updateJob_MissingConfig() throws Exception { // stub spawn calls Job job = new Job("existing_job_id", "megatron"); when(spawn.getJob("existing_job_id")).thenReturn(job); when(spawn.getJobConfig("existing_job_id")).thenReturn("old job config"); kv.add("id", "existing_job_id"); assertSame("returned job", job, impl.createOrUpdateJob(kv, username, token, sudo, false)); verify(spawn, never()).setJobConfig(anyString(), anyString()); verify(spawn, never()).submitConfigUpdate(anyString(), anyString(), anyString()); } /** * Only job parameters in the job config should be added/updated; those that are not should be * removed/ignored */ @Test public void updateJobParameters() throws Exception { // stub spawn calls Job job = new Job("existing_job_id", "megatron"); job.setParameters(Lists.newArrayList( new JobParameter("start-date", "140908", null), new JobParameter("end-date", "140908", null))); when(spawn.getJob("existing_job_id")).thenReturn(job); kv.add("id", "existing_job_id"); // new job config removes 'end-date' and adds 'foo' kv.add("config", "%[start-date]% %[foo]%"); kv.add("command", "default-task"); kv.add("sp_start-date", "140908"); kv.add("sp_end-date", "140908"); // should be removed kv.add("sp_foo", "foo"); kv.add("sp_bar", "bar"); // should be ignored assertSame("returned job", job, impl.createOrUpdateJob(kv, username, token, sudo, false)); assertEquals("# of parameters", 2, job.getParameters().size()); JobParameter foo = null; JobParameter startDate = null; for (JobParameter jp : job.getParameters()) { if ("start-date".equals(jp.getName())) { startDate = jp; } else if ("foo".equals(jp.getName())) { foo = jp; } } assertNotNull("parameter 'start-date' found", startDate); assertEquals("parameter 'start-date' value", "140908", startDate.getValue()); assertNotNull("parameter 'foo' found", foo); assertEquals("parameter 'foo' value", "foo", foo.getValue()); } @Test public void updateJobParameters_OriginalWithDefaultValues() throws Exception { // stub spawn calls Job job = new Job("existing_job_id", "megatron"); job.setParameters(Lists.newArrayList( new JobParameter("start-date", null, "160712"), new JobParameter("end-date", null, "160712"))); when(spawn.getJob("existing_job_id")).thenReturn(job); kv.add("id", "existing_job_id"); kv.add("config", "%[start-date]% %[end-date]%"); // update end date, original had defaut value kv.add("sp_start-date", "160712"); kv.add("sp_end-date", "160714"); assertSame("returned job", job, impl.createOrUpdateJob(kv, username, token, sudo, false)); JobParameter endDate = null; JobParameter startDate = null; for (JobParameter jp : job.getParameters()) { if ("start-date".equals(jp.getName())) { startDate = jp; } else if ("end-date".equals(jp.getName())) { endDate = jp; } } assertNotNull("parameter 'start-date' found", startDate); assertEquals("parameter 'start-date' value", "160712", startDate.getValue()); assertNotNull("parameter 'end-date' found", endDate); assertEquals("parameter 'end-date' value", "160714", endDate.getValue()); } /** * "rp_" prefixed fields are parameters to clear */ @Test public void updateJobParameters_RemoveParameter() throws Exception { // stub spawn call. pretend job had two parameters Job job = new Job("existing_job_id", "megatron"); job.setParameters(Lists.newArrayList( new JobParameter("start-date", "140908", null), new JobParameter("end-date", "140908", null))); when(spawn.getJob("existing_job_id")).thenReturn(job); kv.add("id", "existing_job_id"); kv.add("command", "default-task"); // remove parameter end-date kv.add("config", "%[start-date]% %[end-date]%"); kv.add("rp_end-date", ""); assertSame("returned job", job, impl.createOrUpdateJob(kv, username, token, sudo, false)); assertEquals("# of parameters", 2, job.getParameters().size()); JobParameter startDate = null; JobParameter endDate = null; for (JobParameter jp : job.getParameters()) { if ("start-date".equals(jp.getName())) { startDate = jp; } else if ("end-date".equals(jp.getName())) { endDate = jp; } } assertNotNull("parameter 'start-date' found", startDate); assertEquals("parameter 'start-date' value", "140908", startDate.getValue()); assertNotNull("parameter 'end-date' found", endDate); assertNull("parameter 'end-date' cleared", endDate.getValue()); } @Test public void basicAlerts_enabled() throws Exception { Job job = new Job("existing_job_id", "megatron"); // make sure nodes are created job.addTask(new JobTask()); when(spawn.getJob("existing_job_id")).thenReturn(job); kv.add("id", "existing_job_id"); kv.add("command", "default-task"); kv.add("config", "%[start-date]% %[end-date]%"); kv.add("basicAlerts", "true"); kv.add("basicPages", "true"); impl.createOrUpdateJob(kv, username, token, sudo, false); verify(jobAlertManager).updateBasicAlerts(job, true, true); } @Test public void basicAlerts_disabled() throws Exception { Job job = new Job("existing_job_id", "megatron"); // make sure nodes are created job.addTask(new JobTask()); when(spawn.getJob("existing_job_id")).thenReturn(job); kv.add("id", "existing_job_id"); kv.add("command", "default-task"); kv.add("config", "%[start-date]% %[end-date]%"); kv.add("maxRuntime", 30); kv.add("rekickTimeout", 30); impl.createOrUpdateJob(kv, username, token, sudo, false); verify(jobAlertManager).updateBasicAlerts(job, false, false); } private void verifyNoSpawnCreateJobCall() throws Exception { verify(spawn, never()).createJob(anyString(), anyInt(), anyCollectionOf(String.class), anyString(), anyString(), anyBoolean()); } private void callAndVerifyBadRequest() throws Exception { try { impl.createOrUpdateJob(kv, username, token, sudo, false); fail("IllegalArgumentException expected but was not thrown"); } catch (IllegalArgumentException e) { // GOOD! } } }