package io.eguan.vold.rest.resources;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import static com.sun.jersey.api.client.ClientResponse.Status.ACCEPTED;
import static com.sun.jersey.api.client.ClientResponse.Status.NOT_FOUND;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import io.eguan.vold.rest.generated.model.Device;
import io.eguan.vold.rest.generated.model.DeviceList;
import io.eguan.vold.rest.generated.model.ExecState;
import io.eguan.vold.rest.generated.model.Task;
import io.eguan.vold.rest.generated.model.TaskList;
import io.eguan.vold.rest.generated.model.VersionedVolumeRepository;
import io.eguan.vold.rest.generated.model.VersionedVolumeRepositoryList;
import io.eguan.vold.rest.generated.resources.VvrsResource;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.WebResource.Builder;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.core.util.MultivaluedMapImpl;
/**
* Abstract class providing a common REST client setup.
*
* @author oodrive
* @author pwehrle
* @author ebredzinski
*
*/
@RunWith(Parameterized.class)
public abstract class AbstractVvrsResourceTest extends AbstractResourceTest {
private static final String VVRS_RESOURCE_URI = SERVER_BASE_URI + "/"
+ VvrsResource.class.getAnnotation(Path.class).value();
static final String CREATE_VVR_PATH = "action/createVvr";
static final String START_VVR_PATH = "action/start";
static final String STOP_VVR_PATH = "action/stop";
static final String TASK_VVRS_PATH = VVRS_RESOURCE_URI + "/tasks";
/**
* The default task timeout in seconds.
*/
protected static final int DEFAULT_TASK_TIMEOUT_S = 25;
/**
* Common POJO replicator.
*
* @see JaxbPojoReplicator
*/
protected static final JaxbPojoReplicator<VersionedVolumeRepository> VVR_REPLICATOR = new JaxbPojoReplicator<>(
VersionedVolumeRepository.class);
private static Client client;
private static MultivaluedMapImpl mandatoryQueryParams;
private static MediaType contentType = MediaType.APPLICATION_XML_TYPE;
private static MediaType acceptType = MediaType.APPLICATION_XML_TYPE;
private static final int TASK_WAIT_MS = 100;
private static final int NOT_FOUND_RETRY_COUNT = 100;
private static final int NOT_FOUND_WAIT_DELAY_MS = 200;
/**
* Internal constructor with content and response type arguments.
*
* @param runContentType
* the content type to send in requests
* @param runAcceptType
* the accepted response type
*/
protected AbstractVvrsResourceTest(final MediaType runContentType, final MediaType runAcceptType) {
contentType = runContentType;
acceptType = runAcceptType;
}
/**
* Test parameter generator method.
*
* @return all test parameter combinations to test
*/
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] { { MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE },
{ MediaType.APPLICATION_XML_TYPE, MediaType.APPLICATION_XML_TYPE } });
}
/**
* Gets the root 'vvrs' resource.
*
* @return a valid {@link WebResource}
*/
protected final WebResource getVvrsResource() {
return client.resource(VVRS_RESOURCE_URI);
}
/**
* Gets the common vvrs task resource.
*
* @return a valid {@link WebResource}
*/
protected final WebResource getVvrsTasksResource() {
return client.resource(TASK_VVRS_PATH);
}
/**
* Sets up the Jersey client.
*/
@BeforeClass
public static final void setUpJerseyClient() {
final ClientConfig cfg = new DefaultClientConfig();
cfg.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
client = Client.create(cfg);
mandatoryQueryParams = new MultivaluedMapImpl();
mandatoryQueryParams.add("ownerId", Objects.requireNonNull(getOwnerUuid()));
}
/**
* Sets up and cleans the root vvrs resource.
*/
@Before
public final void setUpVvrsResource() {
clearVvrsResource(getOwnerUuid(), getVvrsResource());
}
/**
* Tears down leftover sub-resources of the root vvrs resource.
*/
@After
public final void tearDownVvrsResource() {
clearVvrsResource(getOwnerUuid(), getVvrsResource());
}
/**
* Prepares a request with common mandatory parameters and optional query parameters.
*
* @param targetResource
* the target {@link WebResource}
* @param additionalQueryParams
* optional query parameters
* @return a preconfigured
*/
protected static final Builder prebuildRequest(final WebResource targetResource,
final MultivaluedMap<String, String> additionalQueryParams) {
WebResource parameterizedResource = targetResource.queryParams(mandatoryQueryParams);
if (additionalQueryParams != null) {
parameterizedResource = parameterizedResource.queryParams(additionalQueryParams);
}
return parameterizedResource.accept(acceptType).type(contentType);
}
/**
* Extracts the task resource from the given response to a task-creating request.
*
* @param taskCreateResponse
* a {@link ClientResponse} received as response to a task-creating (POST) request
* @return a valid {@link WebResource} pointing to the created task
* @throws IllegalArgumentException
* if the provided response does not have the proper return code or its location header is empty
*/
protected static final WebResource getTaskResourceFromAcceptedResponse(final ClientResponse taskCreateResponse)
throws IllegalArgumentException {
final int responseStatus = taskCreateResponse.getStatus();
if (ACCEPTED.getStatusCode() != responseStatus) {
throw new IllegalArgumentException("Response status is not 'accepted'; responseStatus="
+ Status.fromStatusCode(responseStatus));
}
final List<String> locationHeaders = taskCreateResponse.getHeaders().get("location");
if (locationHeaders.isEmpty()) {
throw new IllegalArgumentException("No location headers found");
}
return client.resource(locationHeaders.get(0));
}
/**
* Utility method to wait for the result produced by a task.
*
* @param taskCreateResponse
* the {@link ClientResponse} pointing to the newly created task
* @param timeUnit
* a {@link TimeUnit} to measure the timeout in
* @param timeout
* a timeout after which to stop waiting for the result
* @return a {@link WebResource} pointing to the result
* @throws TimeoutException
* if the specified timeout is reached before the result could be obtained
* @throws IllegalStateException
* if the task fails or does not produce a result
*/
protected static final WebResource getResultFromTask(final ClientResponse taskCreateResponse,
final TimeUnit timeUnit, final long timeout) throws IllegalStateException, TimeoutException {
return getResultFromTask(taskCreateResponse, timeUnit, timeout, false);
}
/**
* Utility method to wait for the result produced by a task.
*
* @param taskCreateResponse
* the {@link ClientResponse} pointing to the newly created task
* @param timeUnit
* a {@link TimeUnit} to measure the timeout in
* @param timeout
* a timeout after which to stop waiting for the result
* @param waitForResult
* whether to wait with the same timeout until the resulting resource appears before returning
* @return a {@link WebResource} pointing to the result
* @throws TimeoutException
* if the specified timeout is reached before the result could be obtained
* @throws IllegalStateException
* if the task fails or does not produce a result
*/
protected static final WebResource getResultFromTask(final ClientResponse taskCreateResponse,
final TimeUnit timeUnit, final long timeout, final boolean waitForResult) throws IllegalStateException,
TimeoutException {
final WebResource taskRes = getTaskResourceFromAcceptedResponse(taskCreateResponse);
final long millisTimeout = timeUnit.toMillis(timeout) + System.currentTimeMillis();
String resultRef;
boolean resultReferenced = false;
ExecState state;
boolean keepWaiting;
do {
if (System.currentTimeMillis() > millisTimeout) {
throw new TimeoutException();
}
final Task task = prebuildRequest(taskRes, null).get(Task.class);
state = task.getState();
resultRef = task.getResultRef();
resultReferenced = (resultRef != null);
if (state == ExecState.FAILED) {
throw new IllegalStateException("Task failed; uuid=" + task.getUuid());
}
if ((state == ExecState.DONE) && !resultReferenced) {
throw new IllegalStateException("Task done without producing a result; uuid=" + task.getUuid());
}
keepWaiting = !resultReferenced || state == ExecState.IN_PROGRESS || state == ExecState.PENDING;
if (keepWaiting) {
try {
Thread.sleep(TASK_WAIT_MS);
}
catch (final InterruptedException e) {
throw new IllegalStateException(e);
}
}
} while (keepWaiting);
final WebResource result = client.resource(resultRef);
if (!waitForResult) {
return result;
}
ClientResponse checkResponse = prebuildRequest(result, null).get(ClientResponse.class);
final int statusOk = Status.OK.getStatusCode();
while (checkResponse.getStatus() != statusOk) {
if (System.currentTimeMillis() > millisTimeout) {
throw new TimeoutException();
}
try {
Thread.sleep(TASK_WAIT_MS);
}
catch (final InterruptedException e) {
throw new IllegalStateException(e);
}
checkResponse = prebuildRequest(result, null).get(ClientResponse.class);
}
return result;
}
/**
* Creates a VVR resource.
*
* @param vvrsResource
* the parent vvrs resource
* @param additionalQueryParams
* query parameters to include in the creation request
* @return a {@link WebResource} pointing to the result
* @throws TimeoutException
* if the creation task does not complete within {@value #DEFAULT_TASK_TIMEOUT_S} seconds
*/
protected static final WebResource createVvr(final WebResource vvrsResource,
final MultivaluedMap<String, String> additionalQueryParams) throws TimeoutException {
final ClientResponse createResponse = prebuildRequest(vvrsResource.path(CREATE_VVR_PATH), additionalQueryParams)
.post(ClientResponse.class, null);
return getResultFromTask(createResponse, TimeUnit.SECONDS, DEFAULT_TASK_TIMEOUT_S, true);
}
/**
* Starts the given VVR.
*
* @param vvrResource
* the target VVR resource
* @param additionalQueryParams
* query parameters to include in the start request
* @return a {@link WebResource} pointing to the started VVR
* @throws TimeoutException
* if the start task does not complete within {@value #DEFAULT_TASK_TIMEOUT_S} seconds
* @throws InterruptedException
* if interrupted while waiting for the VVR resource to be available
*/
protected static final WebResource startVvr(final WebResource vvrResource,
final MultivaluedMap<String, String> additionalQueryParams) throws TimeoutException, InterruptedException {
ClientResponse startResponse = prebuildRequest(vvrResource.path(START_VVR_PATH), additionalQueryParams).post(
ClientResponse.class, null);
int count = 0;
while (NOT_FOUND.getStatusCode() == startResponse.getStatus() && count < NOT_FOUND_RETRY_COUNT) {
Thread.sleep(NOT_FOUND_WAIT_DELAY_MS);
count++;
startResponse = prebuildRequest(vvrResource.path(START_VVR_PATH), additionalQueryParams).post(
ClientResponse.class, null);
}
final WebResource startedVvrRes = getResultFromTask(startResponse, TimeUnit.SECONDS, DEFAULT_TASK_TIMEOUT_S);
final VersionedVolumeRepository startedVvr = prebuildRequest(startedVvrRes, additionalQueryParams).get(
VersionedVolumeRepository.class);
assertTrue(startedVvr.isStarted());
return startedVvrRes;
}
/**
* Activates a given device.
*
* @param deviceResource
* the {@link WebResource} representing the target device
* @param readOnly
* whether to activate read-only
* @return a {@link WebResource} representing the active device
* @throws TimeoutException
* if the activation task does not complete within {@value #DEFAULT_TASK_TIMEOUT_S} seconds
*/
protected static final WebResource activateDevice(final WebResource deviceResource, final boolean readOnly)
throws TimeoutException {
final WebResource activateActionRes = deviceResource.path("action/activate");
final MultivaluedMapImpl activateParams = new MultivaluedMapImpl();
activateParams.add("readOnly", readOnly);
final ClientResponse activateResponse = prebuildRequest(activateActionRes, activateParams).post(
ClientResponse.class);
return getResultFromTask(activateResponse, TimeUnit.SECONDS, DEFAULT_TASK_TIMEOUT_S);
}
/**
* Deactivates a given device.
*
* @param deviceResource
* the {@link WebResource} representing the target device
* @return a {@link WebResource} representing the inactive device
* @throws TimeoutException
* if the deactivation task does not complete within {@value #DEFAULT_TASK_TIMEOUT_S} seconds
*/
protected static final WebResource deactivateDevice(final WebResource deviceResource) throws TimeoutException {
final WebResource deactivateActionRes = deviceResource.path("action/deactivate");
final ClientResponse deactivateResponse = prebuildRequest(deactivateActionRes, null).post(ClientResponse.class);
return getResultFromTask(deactivateResponse, TimeUnit.SECONDS, DEFAULT_TASK_TIMEOUT_S);
}
/**
* Check all the tasks locally saved are present in the server returned list.
*
* @param localTasks
* a {@link List} of local tasks
* @param serverTasks
* a {@link List} of server-side tasks
* @return <code>true</code> if all local tasks have been found in the server-side tasks
*/
protected static final boolean checkTasks(final List<Task> localTasks, final List<Task> serverTasks) {
// check that all the tasks locally saved are present in the server returned list
boolean localTaskfound = false;
for (final Task localTask : localTasks) {
for (final Task serverTask : serverTasks) {
if (localTask.getUuid().equals(serverTask.getUuid())) {
localTaskfound = true;
break;
}
}
}
return localTaskfound;
}
/**
* Gets the list of tasks from a tasks resource.
*
* @param tasksResource
* the target {@link WebResource}
* @return a {@link List} of {@link Task}s
*/
protected static final List<Task> getServerTasks(final WebResource tasksResource) {
final TaskList serverTaskList = prebuildRequest(tasksResource, null).get(TaskList.class);
assertNotNull(serverTaskList);
final List<Task> newTaskList = serverTaskList.getTasks();
assertNotNull(newTaskList);
return newTaskList;
}
/**
* Delete all sub-resources of the given vvrs resource.
*
* @param ownerId
* the owner ID associated to the target resource
* @param rootResource
* the target vvrs resource
*/
private static final void clearVvrsResource(final String ownerId, final WebResource rootResource) {
// clear all remaining vvr resources contained in the root vvrs resource
final VersionedVolumeRepositoryList vvrsToDelete = prebuildRequest(rootResource, null).get(
VersionedVolumeRepositoryList.class);
try {
for (final VersionedVolumeRepository currVvr : vvrsToDelete.getVersionedVolumeRepositories()) {
final WebResource resource = rootResource.path(currVvr.getUuid());
if (currVvr.isStarted()) {
deactivateAllDevices(resource);
final WebResource stoppedVvrRes = getResultFromTask(
prebuildRequest(resource.path("action/stop"), null).post(ClientResponse.class, null),
TimeUnit.SECONDS, DEFAULT_TASK_TIMEOUT_S);
final VersionedVolumeRepository stoppedVvr = prebuildRequest(stoppedVvrRes, null).get(
VersionedVolumeRepository.class);
assert !stoppedVvr.isStarted();
}
final ClientResponse deleteResponse = prebuildRequest(resource, null).delete(ClientResponse.class);
assertEquals(Status.ACCEPTED.getStatusCode(), deleteResponse.getStatus());
getResultFromTask(deleteResponse, TimeUnit.SECONDS, DEFAULT_TASK_TIMEOUT_S);
}
}
catch (final TimeoutException e) {
throw new IllegalStateException(e);
}
}
/**
* Deactivates all devices in a given VVR resource.
*
* @param vvrResource
* the target resource
* @throws TimeoutException
* if any of the deactivation tasks does not complete within {@value #DEFAULT_TASK_TIMEOUT_S} seconds
*/
private static final void deactivateAllDevices(final WebResource vvrResource) throws TimeoutException {
final WebResource devicesRes = vvrResource.path("devices");
for (final Device currDev : prebuildRequest(devicesRes, null).get(DeviceList.class).getDevices()) {
if (!currDev.isActive()) {
continue;
}
final WebResource deviceRes = devicesRes.path(currDev.getUuid());
deactivateDevice(deviceRes);
}
}
}