/*
* 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.usergrid.rest;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.ConnectException;
import java.text.DecimalFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static com.codahale.metrics.MetricRegistry.name;
/**
* Tests that Usergrid will not allow creation of entities with duplicate names.
*
* Intended for use against a production-like cluster, not run during normal JUnit testing.
*
* Comment out the @Ignore annotation below and edit to add your target hosts.
*/
public class UniqueCatsIT {
private static final Logger logger = LoggerFactory.getLogger( UniqueCatsIT.class );
private static final AtomicInteger successCounter = new AtomicInteger( 0 );
private static final AtomicInteger errorCounter = new AtomicInteger( 0 );
private static final AtomicInteger dupCounter = new AtomicInteger( 0 );
@Test
@Ignore("Intended for use against prod-like cluster")
public void testDuplicatePrevention() throws Exception {
int numThreads = 20;
int poolSize = 20;
int numCats = 100;
Multimap<String, String> catsCreated = Multimaps.synchronizedMultimap( HashMultimap.create() );
Multimap<String, Map<String, Object>> dupsRejected = Multimaps.synchronizedMultimap( HashMultimap.create() );
ExecutorService execService = Executors.newFixedThreadPool( poolSize );
Client client = ClientBuilder.newClient();
final MetricRegistry metrics = new MetricRegistry();
final Timer responses = metrics.timer(name(UniqueCatsIT.class, "responses"));
long startTime = System.currentTimeMillis();
final AtomicBoolean failed = new AtomicBoolean(false);
String[] targetHosts = {"http://localhost:8080"};
for (int i = 0; i < numCats; i++) {
if ( failed.get() ) { break; }
String randomizer = RandomStringUtils.randomAlphanumeric( 8 );
// multiple threads simultaneously trying to create a cat with the same propertyName
for (int j = 0; j < numThreads; j++) {
if ( failed.get() ) { break; }
final String name = "uv_test_cat_" + randomizer;
final String host = targetHosts[ j % targetHosts.length ];
execService.submit( () -> {
Map<String, Object> form = new HashMap<String, Object>() {{
put("name", name);
}};
Timer.Context time = responses.time();
try {
WebTarget target = client.target( host ).path(
//"/test-organization/test-app/cats" );
"/dmjohnson/sandbox/cats" );
//logger.info("Posting cat {} to host {}", catname, host);
Response response = target.request()
//.post( Entity.entity( form, MediaType.APPLICATION_FORM_URLENCODED ));
.post( Entity.entity( form, MediaType.APPLICATION_JSON));
org.apache.usergrid.rest.test.resource.model.ApiResponse apiResponse = null;
String responseAsString = "";
if ( response.getStatus() >= 400 ) {
responseAsString = response.readEntity( String.class );
} else {
apiResponse = response.readEntity(
org.apache.usergrid.rest.test.resource.model.ApiResponse.class );
}
if ( response.getStatus() == 200 || response.getStatus() == 201 ) {
catsCreated.put( name, apiResponse.getEntity().getUuid().toString() );
successCounter.incrementAndGet();
} else if ( response.getStatus() == 400
&& responseAsString.contains("DuplicateUniquePropertyExistsException")) {
dupsRejected.put( name, form );
dupCounter.incrementAndGet();
} else {
logger.error("Cat creation failed status {} message {}",
response.getStatus(), responseAsString );
errorCounter.incrementAndGet();
}
} catch ( ProcessingException e ) {
errorCounter.incrementAndGet();
if ( e.getCause() instanceof ConnectException ) {
logger.error("Error connecting to " + host);
} else {
logger.error( "Error", e );
}
} catch ( Exception e ) {
errorCounter.incrementAndGet();
logger.error("Error", e);
}
time.stop();
} );
}
}
execService.shutdown();
try {
while (!execService.awaitTermination( 60, TimeUnit.SECONDS )) {
System.out.println( "Waiting..." );
}
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
logger.info( "Total time {}s", (endTime - startTime) / 1000 );
DecimalFormat format = new DecimalFormat("##.###");
logger.info( "Timed {} requests:\n" +
"mean rate {}/s\n" +
"min {}s\n" +
"max {}s\n" +
"mean {}s",
responses.getCount(),
format.format( responses.getMeanRate() ),
format.format( (double)responses.getSnapshot().getMin() / 1000000000 ),
format.format( (double)responses.getSnapshot().getMax() / 1000000000 ),
format.format( responses.getSnapshot().getMean() / 1000000000 )
);
logger.info( "Error count {} ratio = {}",
errorCounter.get(), (float) errorCounter.get() / (float) responses.getCount() );
logger.info( "Success count = {}", successCounter.get() );
logger.info( "Rejected dup count = {}", dupCounter.get() );
// for ( String catname : catsCreated.keys() ) {
// System.out.println( catname );
// Collection<Cat> cats = catsCreated.get( catname );
// for ( Cat cat : cats ) {
// System.out.println(" " + cat.getUuid() );
// }
// }
// int count = 0;
// for ( String catname : dupsRejected.keySet() ) {
// System.out.println( catname );
// Collection<Cat> cats = dupsRejected.get( catname );
// for ( Cat cat : cats ) {
// System.out.println(" " + (count++) + " rejected " + cat.getCatname() + ":" + cat.getUuid() );
// }
// }
int catCount = 0;
int catnamesWithDuplicates = 0;
for ( String name : catsCreated.keySet() ) {
//Collection<Map<String, String>> forms =
Collection<String> forms = catsCreated.get( name );
if ( forms.size() > 1 ) {
catnamesWithDuplicates++;
logger.info("Duplicate " + name);
}
catCount++;
}
Assert.assertEquals( 0, catnamesWithDuplicates );
Assert.assertEquals( 0, errorCounter.get() );
Assert.assertEquals( numCats, successCounter.get() );
Assert.assertEquals( numCats, catCount );
}
}