TestApiServer.java
/*
* 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 static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.Sets;
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.ComponentState;
import org.apache.hadoop.yarn.service.api.records.Container;
import org.apache.hadoop.yarn.service.api.records.ContainerState;
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.api.records.ServiceStatus;
import org.apache.hadoop.yarn.service.conf.RestApiConstants;
import org.apache.hadoop.yarn.service.webapp.ApiServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Test case for ApiServer REST API.
*
*/
public class TestApiServer {
private ApiServer apiServer;
private HttpServletRequest request;
private ServiceClientTest mockServerClient;
private static final Logger LOG = LoggerFactory.getLogger(TestApiServer.class);
@BeforeEach
public void setup() {
request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getRemoteUser())
.thenReturn(System.getProperty("user.name"));
mockServerClient = new ServiceClientTest();
Configuration conf = new Configuration();
conf.set("yarn.api-service.service.client.class",
ServiceClientTest.class.getName());
apiServer = new ApiServer(conf);
apiServer.setServiceClient(mockServerClient);
}
@AfterEach
public void teardown() {
mockServerClient.forceStop();
}
@Test
void testPathAnnotation() {
assertNotNull(this.apiServer.getClass().getAnnotation(Path.class));
assertTrue(this.apiServer.getClass().isAnnotationPresent(Path.class),
"The controller has the annotation Path");
final Path path = this.apiServer.getClass()
.getAnnotation(Path.class);
assertEquals("/v1", path.value(), "The path has /v1 annotation");
}
@Test
void testGetVersion() {
final Response actual = apiServer.getVersion();
assertEquals(Response.ok().build().getStatus(),
actual.getStatus(),
"Version number is");
}
@Test
void testBadCreateService() {
Service service = new Service();
// Test for invalid argument
final Response actual = apiServer.createService(request, service);
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(),
"Create service is ");
}
@Test
void testGoodCreateService() throws Exception {
String json = "{\"auths\": "
+ "{\"https://index.docker.io/v1/\": "
+ "{\"auth\": \"foobarbaz\"},"
+ "\"registry.example.com\": "
+ "{\"auth\": \"bazbarfoo\"}}}";
File dockerTmpDir = new File("target", "docker-tmp");
FileUtils.deleteQuietly(dockerTmpDir);
dockerTmpDir.mkdirs();
String dockerConfig = dockerTmpDir + "/config.json";
BufferedWriter bw = new BufferedWriter(new FileWriter(dockerConfig));
bw.write(json);
bw.close();
Service service = ServiceClientTest.buildGoodService();
final Response actual = apiServer.createService(request, service);
assertEquals(Response.status(Status.ACCEPTED).build().getStatus(),
actual.getStatus(),
"Create service is ");
}
@Test
void testInternalServerErrorDockerClientConfigMissingCreateService() {
Service service = new Service();
service.setName("jenkins");
service.setVersion("v1");
service.setDockerClientConfig("/does/not/exist/config.json");
Artifact artifact = new Artifact();
artifact.setType(TypeEnum.DOCKER);
artifact.setId("jenkins:latest");
Resource resource = new Resource();
resource.setCpus(1);
resource.setMemory("2048");
List<Component> 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(request, service);
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(),
"Create service is ");
}
@Test
void testBadGetService() {
final String serviceName = "nonexistent-jenkins";
final Response actual = apiServer.getService(request, serviceName);
assertEquals(Response.status(Status.NOT_FOUND).build().getStatus(),
actual.getStatus(),
"Get service is ");
ServiceStatus serviceStatus = (ServiceStatus) actual.getEntity();
assertEquals(RestApiConstants.ERROR_CODE_APP_NAME_INVALID, serviceStatus.getCode(),
"Response code don't match");
assertEquals("Service " + serviceName + " not found", serviceStatus.getDiagnostics(),
"Response diagnostics don't match");
}
@Test
void testBadGetService2() {
final Response actual = apiServer.getService(request, null);
assertEquals(Response.status(Status.NOT_FOUND).build().getStatus(), actual.getStatus(),
"Get service is ");
ServiceStatus serviceStatus = (ServiceStatus) actual.getEntity();
assertEquals(RestApiConstants.ERROR_CODE_APP_NAME_INVALID, serviceStatus.getCode(),
"Response code don't match");
assertEquals("Service name cannot be null.", serviceStatus.getDiagnostics(),
"Response diagnostics don't match");
}
@Test
void testGoodGetService() {
final Response actual = apiServer.getService(request, "jenkins");
assertEquals(Response.status(Status.OK).build().getStatus(), actual.getStatus(),
"Get service is ");
}
@Test
void testBadDeleteService() {
final Response actual = apiServer.deleteService(request, "no-jenkins");
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(),
"Delete service is ");
}
@Test
void testBadDeleteService2() {
final Response actual = apiServer.deleteService(request, null);
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(),
"Delete service is ");
}
@Test
void testBadDeleteService3() {
final Response actual = apiServer.deleteService(request,
"jenkins-doesn't-exist");
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(),
"Delete service is ");
}
@Test
void testBadDeleteService4() {
final Response actual = apiServer.deleteService(request,
"jenkins-error-cleaning-registry");
assertEquals(Response.status(Status.INTERNAL_SERVER_ERROR).build().getStatus(),
actual.getStatus(),
"Delete service is ");
}
@Test
void testGoodDeleteService() {
final Response actual = apiServer.deleteService(request, "jenkins");
assertEquals(Response.status(Status.OK).build().getStatus(), actual.getStatus(),
"Delete service is ");
}
@Test
void testDeleteStoppedService() {
final Response actual = apiServer.deleteService(request, "jenkins-already-stopped");
assertEquals(Response.status(Status.OK).build().getStatus(), actual.getStatus(),
"Delete service is ");
}
@Test
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<Component> 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(request, "jenkins",
service);
assertEquals(Response.status(Status.OK).build().getStatus(), actual.getStatus(),
"update service is ");
}
@Test
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<Component> 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);
LOG.info("before stop");
final Response actual = apiServer.updateService(request, "no-jenkins", service);
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(), "flex service is ");
}
@Test
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<Component> 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(request, "jenkins",
service);
assertEquals(Response.status(Status.OK).build().getStatus(), actual.getStatus(),
"flex service is ");
}
@Test
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<Component> 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(request, "no-jenkins",
service);
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(),
"start service is ");
}
@Test
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<Component> 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(request, "jenkins",
service);
assertEquals(Response.status(Status.OK).build().getStatus(), actual.getStatus(),
"start service is ");
}
@Test
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<Component> 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);
LOG.info("before stop");
final Response actual = apiServer.updateService(request, "no-jenkins", service);
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(), "stop service is ");
}
@Test
void testGoodStopServices() {
Service service = new Service();
service.setState(ServiceState.STOPPED);
service.setName("jenkins");
LOG.info("before stop");
final Response actual = apiServer.updateService(request, "jenkins", service);
assertEquals(Response.status(Status.OK).build().getStatus(), actual.getStatus(),
"stop service is ");
}
@Test
void testBadSecondStopServices() {
Service service = new Service();
service.setState(ServiceState.STOPPED);
service.setName("jenkins-second-stop");
// simulates stop on an already stopped service
LOG.info("before second stop");
final Response actual = apiServer.updateService(request,
"jenkins-second-stop", service);
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(),
"stop service should have thrown 400 Bad Request: ");
ServiceStatus serviceStatus = (ServiceStatus) actual.getEntity();
assertEquals("Service jenkins-second-stop is already stopped",
serviceStatus.getDiagnostics(),
"Stop service should have failed with service already stopped");
}
@Test
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<Component> 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);
LOG.info("before stop");
final Response actual = apiServer.updateService(request, "no-jenkins", service);
assertEquals(Response.status(Status.BAD_REQUEST)
.build().getStatus(), actual.getStatus(), "update service is ");
}
@Test
void testUpdateComponent() {
Response actual = apiServer.updateComponent(request, "jenkins",
"jenkins-master", null);
ServiceStatus serviceStatus = (ServiceStatus) actual.getEntity();
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(),
"Update component should have failed with 400 bad request");
assertEquals("No component data provided", serviceStatus.getDiagnostics(),
"Update component should have failed with no data error");
Component comp = new Component();
actual = apiServer.updateComponent(request, "jenkins", "jenkins-master",
comp);
serviceStatus = (ServiceStatus) actual.getEntity();
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(),
"Update component should have failed with 400 bad request");
assertEquals("No container count provided", serviceStatus.getDiagnostics(),
"Update component should have failed with no count error");
comp.setNumberOfContainers(-1L);
actual = apiServer.updateComponent(request, "jenkins", "jenkins-master",
comp);
serviceStatus = (ServiceStatus) actual.getEntity();
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(),
"Update component should have failed with 400 bad request");
assertEquals("Invalid number of containers specified -1", serviceStatus.getDiagnostics(),
"Update component should have failed with no count error");
comp.setName("jenkins-slave");
comp.setNumberOfContainers(1L);
actual = apiServer.updateComponent(request, "jenkins", "jenkins-master",
comp);
serviceStatus = (ServiceStatus) actual.getEntity();
assertEquals(Response.status(Status.BAD_REQUEST).build().getStatus(),
actual.getStatus(),
"Update component should have failed with 400 bad request");
assertEquals(
"Component name in the request object (jenkins-slave) does not match "
+ "that in the URI path (jenkins-master)",
serviceStatus.getDiagnostics(),
"Update component should have failed with component name mismatch "
+ "error");
}
@Test
void testInitiateUpgrade() {
Service goodService = ServiceClientTest.buildLiveGoodService();
goodService.setVersion("v2");
goodService.setState(ServiceState.UPGRADING);
final Response actual = apiServer.updateService(request,
goodService.getName(), goodService);
assertEquals(Response.status(Status.ACCEPTED).build().getStatus(),
actual.getStatus(),
"Initiate upgrade is ");
}
@Test
void testUpgradeSingleInstance() {
Service goodService = ServiceClientTest.buildLiveGoodService();
Component comp = goodService.getComponents().iterator().next();
Container container = comp.getContainers().iterator().next();
container.setState(ContainerState.UPGRADING);
// To be able to upgrade, the service needs to be in UPGRADING
// and container state needs to be in NEEDS_UPGRADE.
Service serviceStatus = mockServerClient.getGoodServiceStatus();
serviceStatus.setState(ServiceState.UPGRADING);
Container liveContainer = serviceStatus.getComponents().iterator().next()
.getContainers().iterator().next();
liveContainer.setState(ContainerState.NEEDS_UPGRADE);
mockServerClient.setExpectedInstances(Sets.newHashSet(
liveContainer.getComponentInstanceName()));
final Response actual = apiServer.updateComponentInstance(request,
goodService.getName(), comp.getName(),
container.getComponentInstanceName(), container);
assertEquals(Response.status(Status.ACCEPTED).build().getStatus(),
actual.getStatus(),
"Instance upgrade is ");
}
@Test
void testUpgradeMultipleInstances() {
Service goodService = ServiceClientTest.buildLiveGoodService();
Component comp = goodService.getComponents().iterator().next();
comp.getContainers().forEach(container ->
container.setState(ContainerState.UPGRADING));
// To be able to upgrade, the service needs to be in UPGRADING
// and container state needs to be in NEEDS_UPGRADE.
Service serviceStatus = mockServerClient.getGoodServiceStatus();
serviceStatus.setState(ServiceState.UPGRADING);
Set<String> expectedInstances = new HashSet<>();
serviceStatus.getComponents().iterator().next().getContainers().forEach(
container -> {
container.setState(ContainerState.NEEDS_UPGRADE);
expectedInstances.add(container.getComponentInstanceName());
}
);
mockServerClient.setExpectedInstances(expectedInstances);
final Response actual = apiServer.updateComponentInstances(request,
goodService.getName(), comp.getContainers());
assertEquals(Response.status(Status.ACCEPTED).build().getStatus(),
actual.getStatus(),
"Instance upgrade is ");
}
@Test
void testUpgradeComponent() {
Service goodService = ServiceClientTest.buildLiveGoodService();
Component comp = goodService.getComponents().iterator().next();
comp.setState(ComponentState.UPGRADING);
// To be able to upgrade, the service needs to be in UPGRADING
// and component state needs to be in NEEDS_UPGRADE.
Service serviceStatus = mockServerClient.getGoodServiceStatus();
serviceStatus.setState(ServiceState.UPGRADING);
Component liveComp = serviceStatus.getComponent(comp.getName());
liveComp.setState(ComponentState.NEEDS_UPGRADE);
Set<String> expectedInstances = new HashSet<>();
liveComp.getContainers().forEach(container -> {
expectedInstances.add(container.getComponentInstanceName());
container.setState(ContainerState.NEEDS_UPGRADE);
});
mockServerClient.setExpectedInstances(expectedInstances);
final Response actual = apiServer.updateComponent(request,
goodService.getName(), comp.getName(), comp);
assertEquals(Response.status(Status.ACCEPTED).build().getStatus(),
actual.getStatus(),
"Component upgrade is ");
}
@Test
void testUpgradeMultipleComps() {
Service goodService = ServiceClientTest.buildLiveGoodService();
goodService.getComponents().forEach(comp ->
comp.setState(ComponentState.UPGRADING));
// To be able to upgrade, the live service needs to be in UPGRADING
// and component states needs to be in NEEDS_UPGRADE.
Service serviceStatus = mockServerClient.getGoodServiceStatus();
serviceStatus.setState(ServiceState.UPGRADING);
Set<String> expectedInstances = new HashSet<>();
serviceStatus.getComponents().forEach(liveComp -> {
liveComp.setState(ComponentState.NEEDS_UPGRADE);
liveComp.getContainers().forEach(liveContainer -> {
expectedInstances.add(liveContainer.getComponentInstanceName());
liveContainer.setState(ContainerState.NEEDS_UPGRADE);
});
});
mockServerClient.setExpectedInstances(expectedInstances);
final Response actual = apiServer.updateComponents(request,
goodService.getName(), goodService.getComponents());
assertEquals(Response.status(Status.ACCEPTED).build().getStatus(),
actual.getStatus(),
"Component upgrade is ");
}
}