/*
* Copyright 2013-2015 EMC Corporation. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
* or in the "license" file accompanying this file. This file 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.emc.ecs.sync.rest;
import com.emc.ecs.sync.config.SyncConfig;
import com.emc.ecs.sync.config.SyncOptions;
import com.emc.ecs.sync.config.storage.TestConfig;
import com.emc.ecs.sync.test.DelayFilter;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.net.httpserver.HttpServer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.core.UriBuilder;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.LogManager;
public class RestServerTest {
private static final Logger log = LoggerFactory.getLogger(RestServerTest.class);
private static final String HOST = "localhost";
private static final int PORT = 9200;
private static URI endpoint = UriBuilder.fromUri("http://" + HOST).port(PORT).build();
private RestServer restServer;
private Client client;
@Before
public void startRestServer() {
// first, hush up the JDK logger (why does this default to INFO??)
LogManager.getLogManager().getLogger("").setLevel(Level.WARNING);
restServer = new RestServer(endpoint.getHost(), endpoint.getPort());
restServer.start();
}
@After
public void stopRestServer() {
if (restServer != null) restServer.stop(0);
restServer = null;
}
@Before
public void createClient() throws Exception {
ClientConfig cc = new DefaultClientConfig();
cc.getSingletons().add(new PluginResolver());
client = Client.create(cc);
}
@Test
public void testAutoPort() throws Exception {
List<HttpServer> httpServers = new ArrayList<>();
List<RestServer> restServers = new ArrayList<>();
try {
// one port is already used by restServer
for (int i = 1; i <= 3; i++) {
httpServers.add(HttpServer.create(new InetSocketAddress(HOST, PORT + i), 0));
}
// this should leave one port available within the max 5 bind attempts
// the following should succeed
RestServer restServer = new RestServer(HOST, PORT);
restServer.setAutoPortEnabled(true);
restServer.start();
restServers.add(restServer);
// now we have used 5 ports, so the max bind attempts should be reached
// the following should fail
try {
restServer = new RestServer(HOST, PORT);
restServer.setAutoPortEnabled(true);
restServer.start();
restServers.add(restServer);
Assert.fail();
} catch (Exception e) {
Assert.assertEquals("Exceeded maximum bind attempts", e.getCause().getMessage());
}
} finally {
for (HttpServer httpServer : httpServers) {
httpServer.stop(0);
}
for (RestServer restServer : restServers) {
restServer.stop(0);
}
}
}
@Test
public void testServerStartup() throws Exception {
Assert.assertNotNull(client.resource(endpoint).path("/job").get(JobList.class));
}
@Test
public void testJobNotFound() throws Exception {
try {
client.resource(endpoint).path("/job/1").get(JobControl.class);
Assert.fail("server should return a 404");
} catch (UniformInterfaceException e) {
Assert.assertEquals(404, e.getResponse().getStatus());
}
}
@Test
public void testGetJob() throws Exception {
SyncConfig syncConfig = new SyncConfig();
syncConfig.setSource(new TestConfig().withObjectCount(10).withMaxSize(10240).withDiscardData(false));
syncConfig.setTarget(new TestConfig().withReadData(true).withDiscardData(false));
// create sync job
ClientResponse response = client.resource(endpoint).path("/job").put(ClientResponse.class, syncConfig);
String jobId = response.getHeaders().getFirst("x-emc-job-id");
try {
Assert.assertEquals(response.getEntity(String.class), 201, response.getStatus());
response.close(); // must close all responses
// get job
SyncConfig syncConfig2 = client.resource(endpoint).path("/job/" + jobId).get(SyncConfig.class);
Assert.assertNotNull(syncConfig2);
Assert.assertEquals(syncConfig, syncConfig2);
// wait for job to complete
while (!client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class).getStatus().isFinalState()) {
Thread.sleep(1000);
}
} finally {
// delete job
response = client.resource(endpoint).path("/job/" + jobId).delete(ClientResponse.class);
Assert.assertEquals(response.getEntity(String.class), 200, response.getStatus());
response.close(); // must close all responses
}
}
@Test
public void testListJobs() throws Exception {
SyncConfig syncConfig = new SyncConfig();
syncConfig.setSource(new TestConfig().withObjectCount(10).withMaxSize(10240).withDiscardData(false));
syncConfig.setTarget(new TestConfig().withReadData(true).withDiscardData(false));
// create 3 sync jobs
ClientResponse response = client.resource(endpoint).path("/job").put(ClientResponse.class, syncConfig);
Assert.assertEquals(response.getEntity(String.class), 201, response.getStatus());
String jobId1 = response.getHeaders().getFirst("x-emc-job-id");
response.close(); // must close all responses
response = client.resource(endpoint).path("/job").put(ClientResponse.class, syncConfig);
Assert.assertEquals(response.getEntity(String.class), 201, response.getStatus());
String jobId2 = response.getHeaders().getFirst("x-emc-job-id");
response.close(); // must close all responses
response = client.resource(endpoint).path("/job").put(ClientResponse.class, syncConfig);
Assert.assertEquals(response.getEntity(String.class), 201, response.getStatus());
String jobId3 = response.getHeaders().getFirst("x-emc-job-id");
response.close(); // must close all responses
// get job list
JobList jobList = client.resource(endpoint).path("/job").get(JobList.class);
Assert.assertNotNull(jobList);
Assert.assertEquals(3, jobList.getJobs().size());
Assert.assertEquals(new Integer(jobId1), jobList.getJobs().get(0).getJobId());
Assert.assertEquals(new Integer(jobId2), jobList.getJobs().get(1).getJobId());
Assert.assertEquals(new Integer(jobId3), jobList.getJobs().get(2).getJobId());
// wait for jobs to complete
while (!client.resource(endpoint).path("/job/" + jobId1 + "/control").get(JobControl.class).getStatus().isFinalState()
&& !client.resource(endpoint).path("/job/" + jobId2 + "/control").get(JobControl.class).getStatus().isFinalState()
&& !client.resource(endpoint).path("/job/" + jobId3 + "/control").get(JobControl.class).getStatus().isFinalState()) {
Thread.sleep(1000);
}
// delete jobs
response = client.resource(endpoint).path("/job/" + jobId1).delete(ClientResponse.class);
Assert.assertEquals(response.getEntity(String.class), 200, response.getStatus());
response.close(); // must close all responses
response = client.resource(endpoint).path("/job/" + jobId2).delete(ClientResponse.class);
Assert.assertEquals(response.getEntity(String.class), 200, response.getStatus());
response.close(); // must close all responses
response = client.resource(endpoint).path("/job/" + jobId3).delete(ClientResponse.class);
Assert.assertEquals(response.getEntity(String.class), 200, response.getStatus());
response.close(); // must close all responses
}
@Test
public void testCreateDelete() throws Exception {
SyncConfig syncConfig = new SyncConfig();
syncConfig.setSource(new TestConfig().withObjectCount(10).withMaxSize(10240).withDiscardData(false));
syncConfig.setTarget(new TestConfig().withReadData(true).withDiscardData(false));
// create sync job
ClientResponse response = client.resource(endpoint).path("/job").put(ClientResponse.class, syncConfig);
String jobIdStr = response.getHeaders().getFirst("x-emc-job-id");
try {
Assert.assertEquals(response.getEntity(String.class), 201, response.getStatus());
response.close(); // must close all responses
Assert.assertNotNull(jobIdStr);
int jobId = Integer.parseInt(jobIdStr);
// wait for job to complete
while (!client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class).getStatus().isFinalState()) {
Thread.sleep(1000);
}
// get status (should be complete)
JobControl jobControl = client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class);
Assert.assertNotNull(jobControl);
Assert.assertEquals(JobControlStatus.Complete, jobControl.getStatus());
} finally {
// delete job
response = client.resource(endpoint).path("/job/" + jobIdStr).delete(ClientResponse.class);
Assert.assertEquals(response.getEntity(String.class), 200, response.getStatus());
response.close(); // must close all responses
}
}
@Test
public void testPauseResume() throws Exception {
try {
SyncConfig syncConfig = new SyncConfig();
syncConfig.setSource(new TestConfig().withObjectCount(10).withMaxSize(10240).withDiscardData(false));
syncConfig.setTarget(new TestConfig().withReadData(true).withDiscardData(false));
syncConfig.setFilters(Collections.singletonList(new DelayFilter.DelayConfig().withDelayMs(100)));
syncConfig.withOptions(new SyncOptions().withThreadCount(2));
// create sync job
ClientResponse response = client.resource(endpoint).path("/job").put(ClientResponse.class, syncConfig);
String jobId = response.getHeaders().getFirst("x-emc-job-id");
Assert.assertEquals(response.getEntity(String.class), 201, response.getStatus());
response.close(); // must close all responses
// wait for sync to start
while (client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class).getStatus() == JobControlStatus.Initializing) {
Thread.sleep(200);
}
// pause job
client.resource(endpoint).path("/job/" + jobId + "/control").post(new JobControl(JobControlStatus.Paused, 0));
// wait a tick
Thread.sleep(1000);
// get control status
JobControl jobControl = client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class);
Assert.assertNotNull(jobControl);
Assert.assertEquals(JobControlStatus.Paused, jobControl.getStatus());
Assert.assertEquals(2, jobControl.getThreadCount());
// get baseline for progress comparison to make sure we're really paused
SyncProgress progress = client.resource(endpoint).path("/job/" + jobId + "/progress").get(SyncProgress.class);
// wait a tick
Thread.sleep(1000);
// compare against baseline to make sure nothing's changed
SyncProgress progress2 = client.resource(endpoint).path("/job/" + jobId + "/progress").get(SyncProgress.class);
Assert.assertEquals(progress.getObjectsComplete(), progress2.getObjectsComplete());
Assert.assertEquals(progress.getBytesComplete(), progress2.getBytesComplete());
// resume
client.resource(endpoint).path("/job/" + jobId + "/control").post(new JobControl(JobControlStatus.Running, 0));
// get control status
jobControl = client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class);
Assert.assertNotNull(jobControl);
Assert.assertEquals(JobControlStatus.Running, jobControl.getStatus());
Assert.assertEquals(2, jobControl.getThreadCount());
// bump threads to speed up completion
client.resource(endpoint).path("/job/" + jobId + "/control").post(new JobControl(JobControlStatus.Running, 32));
// wait for job to complete
while (!client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class).getStatus().isFinalState()) {
Thread.sleep(1000);
}
// get control status
jobControl = client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class);
Assert.assertNotNull(jobControl);
Assert.assertEquals(JobControlStatus.Complete, jobControl.getStatus());
progress2 = client.resource(endpoint).path("/job/" + jobId + "/progress").get(SyncProgress.class);
Assert.assertTrue(progress.getObjectsComplete() < progress2.getObjectsComplete());
Assert.assertTrue(progress.getBytesComplete() < progress2.getBytesComplete());
// delete job
response = client.resource(endpoint).path("/job/" + jobId).delete(ClientResponse.class);
Assert.assertEquals(response.getEntity(String.class), 200, response.getStatus());
response.close(); // must close all responses
} catch (UniformInterfaceException e) {
log.error(e.getResponse().getEntity(String.class));
throw e;
}
}
@Test
public void testChangeThreadCount() throws Exception {
try {
SyncConfig syncConfig = new SyncConfig();
syncConfig.setSource(new TestConfig().withObjectCount(130).withMaxSize(10240).withDiscardData(false));
syncConfig.setTarget(new TestConfig().withReadData(true).withDiscardData(false));
syncConfig.setFilters(Collections.singletonList(new DelayFilter.DelayConfig().withDelayMs(100)));
syncConfig.withOptions(new SyncOptions().withThreadCount(1));
// create sync job
ClientResponse response = client.resource(endpoint).path("/job").put(ClientResponse.class, syncConfig);
String jobId = response.getHeaders().getFirst("x-emc-job-id");
try {
Assert.assertEquals(response.getEntity(String.class), 201, response.getStatus());
response.close(); // must close all responses
// wait a tick
Thread.sleep(1000);
// 1 threads can process max 10 objects in 1 second with 100ms delay and min 5
// so 5 < # < 10
SyncProgress progress = client.resource(endpoint).path("/job/" + jobId + "/progress").get(SyncProgress.class);
long totalCount = progress.getObjectsComplete();
Assert.assertTrue(totalCount >= 5);
Assert.assertTrue(totalCount <= 10);
// up threads to 10
client.resource(endpoint).path("/job/" + jobId + "/control").post(new JobControl(JobControlStatus.Running, 10));
// wait a tick
Thread.sleep(1000);
// 10 threads can process max 100 objects in 1 second with 100ms delay and min 60
// (totalCount already processed)
progress = client.resource(endpoint).path("/job/" + jobId + "/progress").get(SyncProgress.class);
Assert.assertTrue(progress.getObjectsComplete() >= 60 + totalCount);
Assert.assertTrue(progress.getObjectsComplete() <= 100 + totalCount);
// lower threads to 2
client.resource(endpoint).path("/job/" + jobId + "/control").post(new JobControl(JobControlStatus.Running, 2));
Thread.sleep(300);
totalCount = client.resource(endpoint).path("/job/" + jobId + "/progress").get(SyncProgress.class).getObjectsComplete();
// wait a tick
Thread.sleep(1000);
// 2 threads can process max 20 objects in 1 second with 100ms delay and min 15
// (totalCount already processed)
progress = client.resource(endpoint).path("/job/" + jobId + "/progress").get(SyncProgress.class);
Assert.assertTrue(progress.getObjectsComplete() >= 15 + totalCount);
Assert.assertTrue(progress.getObjectsComplete() <= 20 + totalCount);
// bump threads to speed up completion
client.resource(endpoint).path("/job/" + jobId + "/control").post(new JobControl(JobControlStatus.Running, 32));
// wait for job to complete
while (!client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class).getStatus().isFinalState()) {
Thread.sleep(1000);
}
JobControl jobControl = client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class);
Assert.assertEquals(JobControlStatus.Complete, jobControl.getStatus());
} finally {
// delete job
response = client.resource(endpoint).path("/job/" + jobId).delete(ClientResponse.class);
Assert.assertEquals(response.getEntity(String.class), 200, response.getStatus());
response.close(); // must close all responses
}
} catch (UniformInterfaceException e) {
log.error(e.getResponse().getEntity(String.class));
throw e;
}
}
@Test
public void testProgress() throws Exception {
try {
int threads = 16;
SyncConfig syncConfig = new SyncConfig();
syncConfig.setSource(new TestConfig().withObjectCount(80).withMaxSize(10240).withMaxDepth(4).withDiscardData(false));
syncConfig.setTarget(new TestConfig().withReadData(true).withDiscardData(false));
syncConfig.setFilters(Collections.singletonList(new DelayFilter.DelayConfig().withDelayMs(100)));
syncConfig.withOptions(new SyncOptions().withThreadCount(threads));
// create sync job
ClientResponse response = client.resource(endpoint).path("/job").put(ClientResponse.class, syncConfig);
String jobId = response.getHeaders().getFirst("x-emc-job-id");
Assert.assertEquals(response.getEntity(String.class), 201, response.getStatus());
response.close(); // must close all responses
// wait for sync to start
while (client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class).getStatus() == JobControlStatus.Initializing) {
Thread.sleep(500);
}
Thread.sleep(500);
SyncProgress progress = client.resource(endpoint).path("/job/" + jobId + "/progress").get(SyncProgress.class);
Assert.assertEquals(JobControlStatus.Running, progress.getStatus());
Assert.assertTrue(progress.getTotalObjectsExpected() > 100);
Assert.assertTrue(progress.getTotalBytesExpected() > 100 * 5120);
Assert.assertTrue(progress.getObjectsComplete() > 0);
Assert.assertTrue(progress.getBytesComplete() > 0);
Assert.assertEquals(0, progress.getObjectsFailed());
Assert.assertEquals(progress.getActiveQueryTasks(), 0);
Assert.assertTrue(Math.abs(progress.getActiveSyncTasks() - threads) < 2);
Assert.assertTrue(progress.getRuntimeMs() > 500);
// bump threads to speed up completion
client.resource(endpoint).path("/job/" + jobId + "/control").post(new JobControl(JobControlStatus.Running, 32));
// wait for job to complete
while (!client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class).getStatus().isFinalState()) {
Thread.sleep(1000);
}
JobControl jobControl = client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class);
Assert.assertEquals(JobControlStatus.Complete, jobControl.getStatus());
// delete job
response = client.resource(endpoint).path("/job/" + jobId).delete(ClientResponse.class);
Assert.assertEquals(response.getEntity(String.class), 200, response.getStatus());
response.close(); // must close all responses
} catch (UniformInterfaceException e) {
log.error(e.getResponse().getEntity(String.class));
throw e;
}
}
@Test
public void testTerminate() throws Exception {
try {
int threads = 4;
SyncConfig syncConfig = new SyncConfig();
syncConfig.setSource(new TestConfig().withObjectCount(100).withMaxSize(10240).withDiscardData(false));
syncConfig.setTarget(new TestConfig().withReadData(true).withDiscardData(false));
syncConfig.setFilters(Collections.singletonList(new DelayFilter.DelayConfig().withDelayMs(100)));
syncConfig.withOptions(new SyncOptions().withThreadCount(threads));
// create sync job
ClientResponse response = client.resource(endpoint).path("/job").put(ClientResponse.class, syncConfig);
String jobId = response.getHeaders().getFirst("x-emc-job-id");
try {
Assert.assertEquals(response.getEntity(String.class), 201, response.getStatus());
response.close(); // must close all responses
// wait for sync to start
while (client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class).getStatus() == JobControlStatus.Initializing) {
Thread.sleep(500);
}
// stop the job
client.resource(endpoint).path("/job/" + jobId + "/control").post(new JobControl(JobControlStatus.Stopped, 4));
// wait a tick
Thread.sleep(1000);
// active tasks should clear out after 1 second and the queue should be cleared, so the job should terminate
JobControl jobControl = client.resource(endpoint).path("/job/" + jobId + "/control").get(JobControl.class);
Assert.assertEquals(JobControlStatus.Stopped, jobControl.getStatus());
} finally {
// delete job
response = client.resource(endpoint).path("/job/" + jobId).delete(ClientResponse.class);
Assert.assertEquals(response.getEntity(String.class), 200, response.getStatus());
response.close(); // must close all responses
}
} catch (UniformInterfaceException e) {
log.error(e.getResponse().getEntity(String.class));
throw e;
}
}
}