commit 58e902bbf78329cadbfe8baa94dd60fb743fbc84 Author: eyang Date: Tue Sep 26 16:08:31 2017 -0400 YARN-7202. Unit test cases for API server. diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/pom.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/pom.xml index 74d9681..4490bfe 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/pom.xml +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/pom.xml @@ -41,7 +41,6 @@ - org.apache.maven.plugins maven-jar-plugin @@ -66,17 +65,6 @@ - - - org.apache.maven.plugins - maven-surefire-plugin - - - ${java.home} - - - - @@ -84,6 +72,13 @@ + + + junit + junit + test + + org.apache.hadoop hadoop-yarn-services-core @@ -99,6 +94,7 @@ org.apache.hadoop hadoop-common + test-jar org.slf4j @@ -116,5 +112,17 @@ javax.ws.rs jsr311-api + + org.powermock + powermock-module-junit4 + 1.5.6 + test + + + org.powermock + powermock-api-mockito + 1.5.6 + test + diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/webapp/ApiServer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/webapp/ApiServer.java index e8286ef..eec144a 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/webapp/ApiServer.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/webapp/ApiServer.java @@ -204,39 +204,90 @@ public Response updateComponent(@PathParam(SERVICE_NAME) String appName, @Produces({ MediaType.APPLICATION_JSON }) public Response updateService(@PathParam(SERVICE_NAME) String appName, Service updateServiceData) { + boolean hasErrors = false; + boolean hasBadRequest = false; + LOG.info("PUT: updateService for app = {} with data = {}", appName, updateServiceData); - // Ignore the app name provided in updateServiceData and always use appName - // path param + // Ignore the app name provided in updateServiceData and always use + // appName path param updateServiceData.setName(appName); + // If new lifetime value specified then update it + if (updateServiceData.getLifetime() != null + && updateServiceData.getLifetime() > 0) { + Response r = updateLifetime(appName, updateServiceData); + if (r.getStatus() == + Response.status(Status.BAD_REQUEST).build() + .getStatus()) { + hasBadRequest = true; + } else if (r.getStatus() == + Response.status(Status.INTERNAL_SERVER_ERROR).build() + .getStatus()) { + hasErrors = true; + } + } + + // flex a single component app + if (updateServiceData.getNumberOfContainers() != null && !ServiceApiUtil + .hasComponent(updateServiceData)) { + Component defaultComp = ServiceApiUtil + .createDefaultComponent(updateServiceData); + Response r = updateComponent(updateServiceData.getName(), + defaultComp.getName(), defaultComp); + if (r.getStatus() == + Response.status(Status.BAD_REQUEST).build() + .getStatus()) { + hasBadRequest = true; + } else if (r.getStatus() == + Response.status(Status.INTERNAL_SERVER_ERROR).build() + .getStatus()) { + hasErrors = true; + } + } + // For STOP the app should be running. If already stopped then this // operation will be a no-op. For START it should be in stopped state. // If already running then this operation will be a no-op. if (updateServiceData.getState() != null && updateServiceData.getState() == ServiceState.STOPPED) { - return stopService(appName, false); + Response r = stopService(appName, false); + if (r.getStatus() == + Response.status(Status.BAD_REQUEST).build() + .getStatus() || + r.getStatus() == + Response.status(Status.NOT_FOUND).build() + .getStatus()) { + hasBadRequest = true; + } else if (r.getStatus() == + Response.status(Status.INTERNAL_SERVER_ERROR).build() + .getStatus()) { + hasErrors = true; + } } // If a START is requested if (updateServiceData.getState() != null && updateServiceData.getState() == ServiceState.STARTED) { - return startService(appName); + Response r = startService(appName); + if (r.getStatus() == + Response.status(Status.BAD_REQUEST).build() + .getStatus()) { + hasBadRequest = true; + } else if (r.getStatus() == + Response.status(Status.INTERNAL_SERVER_ERROR).build() + .getStatus()) { + hasErrors = true; + } } - // If new lifetime value specified then update it - if (updateServiceData.getLifetime() != null - && updateServiceData.getLifetime() > 0) { - return updateLifetime(appName, updateServiceData); + if (hasErrors) { + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } - // flex a single component app - if (updateServiceData.getNumberOfContainers() != null && !ServiceApiUtil - .hasComponent(updateServiceData)) { - Component defaultComp = ServiceApiUtil.createDefaultComponent(updateServiceData); - return updateComponent(updateServiceData.getName(), defaultComp.getName(), - defaultComp); + if (hasBadRequest) { + return Response.status(Status.BAD_REQUEST).build(); } // If nothing happens consider it a no-op diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/src/test/java/org/apache/hadoop/yarn/service/ServiceClientTest.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/src/test/java/org/apache/hadoop/yarn/service/ServiceClientTest.java new file mode 100644 index 0000000..7f9459e --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/src/test/java/org/apache/hadoop/yarn/service/ServiceClientTest.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.hadoop.yarn.service; + +import java.io.IOException; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.yarn.api.records.ApplicationId; +import org.apache.hadoop.yarn.exceptions.ApplicationNotFoundException; +import org.apache.hadoop.yarn.exceptions.YarnException; +import org.apache.hadoop.yarn.service.api.records.Service; +import org.apache.hadoop.yarn.service.client.ServiceClient; +import org.apache.hadoop.yarn.service.utils.ServiceApiUtil; + +/** + * A mock version of ServiceClient - This class is design + * to simulate various error conditions that will happen + * when a consumer class calls ServiceClient. + */ +public class ServiceClientTest extends ServiceClient { + + private Configuration conf = new Configuration(); + + public ServiceClientTest() { + super(); + } + + @Override + public Configuration getConfig() { + return conf; + } + + @Override + public ApplicationId actionCreate(Service service) { + String serviceName = service.getName(); + ServiceApiUtil.validateNameFormat(serviceName, getConfig()); + return ApplicationId.newInstance(System.currentTimeMillis(), 1); + } + + @Override + public Service getStatus(String appName) { + if (appName == null) { + throw new NullPointerException(); + } + if (appName.equals("jenkins")) { + return new Service(); + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public int actionStart(String serviceName) + throws YarnException, IOException { + if (serviceName == null) { + throw new NullPointerException(); + } + if (serviceName.equals("jenkins")) { + return EXIT_SUCCESS; + } else { + throw new ApplicationNotFoundException(""); + } + } + + @Override + public int actionStop(String serviceName, boolean waitForAppStopped) + throws YarnException, IOException { + if (serviceName == null) { + throw new NullPointerException(); + } + if (serviceName.equals("jenkins")) { + return EXIT_SUCCESS; + } else { + throw new ApplicationNotFoundException(""); + } + } + + @Override + public int actionDestroy(String serviceName) { + if (serviceName == null) { + throw new NullPointerException(); + } + if (serviceName.equals("jenkins")) { + return EXIT_SUCCESS; + } else { + throw new IllegalArgumentException(); + } + } +} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/src/test/java/org/apache/hadoop/yarn/service/TestApiServer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/src/test/java/org/apache/hadoop/yarn/service/TestApiServer.java new file mode 100644 index 0000000..a6f0667 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services-api/src/test/java/org/apache/hadoop/yarn/service/TestApiServer.java @@ -0,0 +1,367 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.hadoop.yarn.service; + +import org.apache.hadoop.yarn.service.api.records.Artifact; +import org.apache.hadoop.yarn.service.api.records.Artifact.TypeEnum; +import org.apache.hadoop.yarn.service.api.records.Component; +import org.apache.hadoop.yarn.service.api.records.Resource; +import org.apache.hadoop.yarn.service.api.records.Service; +import org.apache.hadoop.yarn.service.api.records.ServiceState; +import org.apache.hadoop.yarn.service.client.ServiceClient; +import org.apache.hadoop.yarn.service.webapp.ApiServer; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.junit.Before; +import org.junit.Test; +import org.powermock.reflect.Whitebox; + +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.*; + +/** + * Test case for ApiServer REST API. + * + */ +public class TestApiServer { + private ApiServer apiServer; + private static ApiServer spy; + + @Before + public void setup() throws Exception { + ServiceClient mockServerClient = new ServiceClientTest(); + Whitebox.setInternalState(ApiServer.class, "SERVICE_CLIENT", + (ServiceClient) mockServerClient); + this.apiServer = new ApiServer(); + } + + @Test + public void testPathAnnotation() { + assertNotNull(this.apiServer.getClass().getAnnotation(Path.class)); + assertThat("The controller has the annotation Path", + this.apiServer.getClass().isAnnotationPresent(Path.class)); + final Path path = this.apiServer.getClass() + .getAnnotation(Path.class); + assertThat("The path is /ws/v1", path.value(), + is("/ws/v1")); + } + + @Test + public void testGetVersion() { + final Response actual = apiServer.getVersion(); + assertThat("Version number is", actual.getStatus(), + is(Response.ok().build().getStatus())); + } + + @Test + public void testBadCreateService() { + Service service = new Service(); + // Test for invalid argument + final Response actual = apiServer.createService(service); + assertThat("Create service is ", actual.getStatus(), + is(Response.status(Status.BAD_REQUEST).build().getStatus())); + } + + @Test + public void testGoodCreateService() { + Service service = new Service(); + service.setName("jenkins"); + Artifact artifact = new Artifact(); + artifact.setType(TypeEnum.DOCKER); + artifact.setId("jenkins:latest"); + Resource resource = new Resource(); + resource.setCpus(1); + resource.setMemory("2048"); + List components = new ArrayList(); + Component c = new Component(); + c.setName("jenkins"); + c.setNumberOfContainers(1L); + c.setArtifact(artifact); + c.setLaunchCommand(""); + c.setResource(resource); + components.add(c); + service.setComponents(components); + final Response actual = apiServer.createService(service); + assertThat("Create service is ", actual.getStatus(), + is(Response.status(Status.ACCEPTED).build().getStatus())); + } + + @Test + public void testBadGetService() { + final Response actual = apiServer.getService("no-jenkins"); + assertThat("Get service is ", actual.getStatus(), + is(Response.status(Status.NOT_FOUND).build().getStatus())); + } + + @Test + public void testBadGetService2() { + final Response actual = apiServer.getService(null); + assertThat("Get service is ", actual.getStatus(), + is(Response.status(Status.INTERNAL_SERVER_ERROR) + .build().getStatus())); + } + + @Test + public void testGoodGetService() { + final Response actual = apiServer.getService("jenkins"); + assertThat("Get service is ", actual.getStatus(), + is(Response.status(Status.OK).build().getStatus())); + } + + @Test + public void testBadDeleteService() { + final Response actual = apiServer.deleteService("no-jenkins"); + assertThat("Delete service is ", actual.getStatus(), + is(Response.status(Status.NOT_FOUND).build().getStatus())); + } + + @Test + public void testBadDeleteService2() { + final Response actual = apiServer.deleteService(null); + assertThat("Delete service is ", actual.getStatus(), + is(Response.status(Status.INTERNAL_SERVER_ERROR) + .build().getStatus())); + } + + @Test + public void testGoodDeleteService() { + final Response actual = apiServer.deleteService("jenkins"); + assertThat("Delete service is ", actual.getStatus(), + is(Response.status(Status.NO_CONTENT).build().getStatus())); + } + + @Test + public void testDecreaseContainerAndStop() { + Service service = new Service(); + service.setState(ServiceState.STOPPED); + service.setName("jenkins"); + Artifact artifact = new Artifact(); + artifact.setType(TypeEnum.DOCKER); + artifact.setId("jenkins:latest"); + Resource resource = new Resource(); + resource.setCpus(1); + resource.setMemory("2048"); + List components = new ArrayList(); + Component c = new Component(); + c.setName("jenkins"); + c.setNumberOfContainers(0L); + c.setArtifact(artifact); + c.setLaunchCommand(""); + c.setResource(resource); + components.add(c); + service.setComponents(components); + final Response actual = apiServer.updateService("jenkins", + service); + assertThat("update service is ", actual.getStatus(), + is(Response.status(Status.NO_CONTENT).build().getStatus())); + } + + @Test + public void testBadDecreaseContainerAndStop() { + Service service = new Service(); + service.setState(ServiceState.STOPPED); + service.setName("no-jenkins"); + Artifact artifact = new Artifact(); + artifact.setType(TypeEnum.DOCKER); + artifact.setId("jenkins:latest"); + Resource resource = new Resource(); + resource.setCpus(1); + resource.setMemory("2048"); + List components = new ArrayList(); + Component c = new Component(); + c.setName("no-jenkins"); + c.setNumberOfContainers(-1L); + c.setArtifact(artifact); + c.setLaunchCommand(""); + c.setResource(resource); + components.add(c); + service.setComponents(components); + System.out.println("before stop"); + final Response actual = apiServer.updateService("no-jenkins", + service); + assertThat("flex service is ", actual.getStatus(), + is(Response.status(Status.BAD_REQUEST).build().getStatus())); + } + + @Test + public void testIncreaseContainersAndStart() { + Service service = new Service(); + service.setState(ServiceState.STARTED); + service.setName("jenkins"); + Artifact artifact = new Artifact(); + artifact.setType(TypeEnum.DOCKER); + artifact.setId("jenkins:latest"); + Resource resource = new Resource(); + resource.setCpus(1); + resource.setMemory("2048"); + List components = new ArrayList(); + Component c = new Component(); + c.setName("jenkins"); + c.setNumberOfContainers(2L); + c.setArtifact(artifact); + c.setLaunchCommand(""); + c.setResource(resource); + components.add(c); + service.setComponents(components); + final Response actual = apiServer.updateService("jenkins", + service); + assertThat("flex service is ", actual.getStatus(), + is(Response.status(Status.NO_CONTENT).build().getStatus())); + } + + @Test + public void testBadStartServices() { + Service service = new Service(); + service.setState(ServiceState.STARTED); + service.setName("no-jenkins"); + Artifact artifact = new Artifact(); + artifact.setType(TypeEnum.DOCKER); + artifact.setId("jenkins:latest"); + Resource resource = new Resource(); + resource.setCpus(1); + resource.setMemory("2048"); + List components = new ArrayList(); + Component c = new Component(); + c.setName("jenkins"); + c.setNumberOfContainers(2L); + c.setArtifact(artifact); + c.setLaunchCommand(""); + c.setResource(resource); + components.add(c); + service.setComponents(components); + final Response actual = apiServer.updateService("no-jenkins", + service); + assertThat("start service is ", actual.getStatus(), + is(Response.status(Status.INTERNAL_SERVER_ERROR).build() + .getStatus())); + } + + @Test + public void testGoodStartServices() { + Service service = new Service(); + service.setState(ServiceState.STARTED); + service.setName("jenkins"); + Artifact artifact = new Artifact(); + artifact.setType(TypeEnum.DOCKER); + artifact.setId("jenkins:latest"); + Resource resource = new Resource(); + resource.setCpus(1); + resource.setMemory("2048"); + List components = new ArrayList(); + Component c = new Component(); + c.setName("jenkins"); + c.setNumberOfContainers(2L); + c.setArtifact(artifact); + c.setLaunchCommand(""); + c.setResource(resource); + components.add(c); + service.setComponents(components); + final Response actual = apiServer.updateService("jenkins", + service); + assertThat("start service is ", actual.getStatus(), + is(Response.status(Status.NO_CONTENT).build().getStatus())); + } + + @Test + public void testBadStopServices() { + Service service = new Service(); + service.setState(ServiceState.STOPPED); + service.setName("no-jenkins"); + Artifact artifact = new Artifact(); + artifact.setType(TypeEnum.DOCKER); + artifact.setId("jenkins:latest"); + Resource resource = new Resource(); + resource.setCpus(1); + resource.setMemory("2048"); + List components = new ArrayList(); + Component c = new Component(); + c.setName("no-jenkins"); + c.setNumberOfContainers(-1L); + c.setArtifact(artifact); + c.setLaunchCommand(""); + c.setResource(resource); + components.add(c); + service.setComponents(components); + System.out.println("before stop"); + final Response actual = apiServer.updateService("no-jenkins", + service); + assertThat("stop service is ", actual.getStatus(), + is(Response.status(Status.BAD_REQUEST).build().getStatus())); + } + + @Test + public void testGoodStopServices() { + Service service = new Service(); + service.setState(ServiceState.STARTED); + service.setName("jenkins"); + Artifact artifact = new Artifact(); + artifact.setType(TypeEnum.DOCKER); + artifact.setId("jenkins:latest"); + Resource resource = new Resource(); + resource.setCpus(1); + resource.setMemory("2048"); + List components = new ArrayList(); + Component c = new Component(); + c.setName("jenkins"); + c.setNumberOfContainers(-1L); + c.setArtifact(artifact); + c.setLaunchCommand(""); + c.setResource(resource); + components.add(c); + service.setComponents(components); + System.out.println("before stop"); + final Response actual = apiServer.updateService("jenkins", + service); + assertThat("stop service is ", actual.getStatus(), + is(Response.status(Status.NO_CONTENT).build().getStatus())); + } + + @Test + public void testUpdateService() { + Service service = new Service(); + service.setState(ServiceState.STARTED); + service.setName("no-jenkins"); + Artifact artifact = new Artifact(); + artifact.setType(TypeEnum.DOCKER); + artifact.setId("jenkins:latest"); + Resource resource = new Resource(); + resource.setCpus(1); + resource.setMemory("2048"); + List components = new ArrayList(); + Component c = new Component(); + c.setName("no-jenkins"); + c.setNumberOfContainers(-1L); + c.setArtifact(artifact); + c.setLaunchCommand(""); + c.setResource(resource); + components.add(c); + service.setComponents(components); + System.out.println("before stop"); + final Response actual = apiServer.updateService("no-jenkins", + service); + assertThat("update service is ", actual.getStatus(), + is(Response.status(Status.INTERNAL_SERVER_ERROR) + .build().getStatus())); + } +}