This repository has been archived by the owner on Nov 19, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NEXUS-8839: Maven Concurrency Hotspot ITs
These ITs does not enforce content validity, but instead, put sone load on server and watch does it "cracks" under pressure while varying the content type and content lengths and other parameters. Special consideration is made for Maven metadata too.
- Loading branch information
Showing
11 changed files
with
1,314 additions
and
0 deletions.
There are no files selected for viewing
105 changes: 105 additions & 0 deletions
105
...uite/src/test/java/org/sonatype/nexus/testsuite/maven/concurrency/MavenHostedSwarmIT.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* Sonatype Nexus (TM) Open Source Version | ||
* Copyright (c) 2008-present Sonatype, Inc. | ||
* All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions. | ||
* | ||
* This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0, | ||
* which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html. | ||
* | ||
* Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks | ||
* of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the | ||
* Eclipse Foundation. All other trademarks are the property of their respective owners. | ||
*/ | ||
package org.sonatype.nexus.testsuite.maven.concurrency; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.concurrent.Callable; | ||
|
||
import org.sonatype.nexus.repository.Repository; | ||
import org.sonatype.nexus.repository.config.Configuration; | ||
import org.sonatype.nexus.repository.maven.policy.VersionPolicy; | ||
import org.sonatype.nexus.testsuite.maven.Maven2Client; | ||
import org.sonatype.nexus.testsuite.maven.concurrency.generators.Generator; | ||
import org.sonatype.nexus.testsuite.maven.concurrency.generators.XmlGenerator; | ||
import org.sonatype.nexus.testsuite.maven.concurrency.generators.ZipGenerator; | ||
import org.sonatype.sisu.goodies.common.ByteSize; | ||
|
||
import com.google.common.base.Supplier; | ||
import com.google.common.base.Suppliers; | ||
import org.apache.http.HttpEntity; | ||
import org.apache.http.HttpResponse; | ||
import org.junit.Test; | ||
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; | ||
import org.ops4j.pax.exam.spi.reactors.PerClass; | ||
|
||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.sonatype.nexus.testsuite.maven.concurrency.generators.Generator.generatedEntity; | ||
|
||
/** | ||
* Maven hosted swarm: multiple clients asking, while others are deploying the SAME artifact from a hosted repository. | ||
*/ | ||
@ExamReactorStrategy(PerClass.class) | ||
public class MavenHostedSwarmIT | ||
extends MavenHotspotITSupport | ||
{ | ||
private static final String RELEASE_XML_ARTIFACT_PATH = "groupId/artifactId/1.0/artifactId-1.0.xml"; | ||
|
||
private static final String RELEASE_ZIP_ARTIFACT_PATH = "groupId/artifactId/1.0/artifactId-1.0.zip"; | ||
|
||
private void hostedSwarm(final String path, | ||
final Generator generator, | ||
final Supplier<ByteSize> lengthSupplier) throws Exception | ||
{ | ||
final Configuration configuration = hostedConfig(testName.getMethodName(), VersionPolicy.RELEASE); | ||
Repository repository = repositoryManager.create(configuration); | ||
final Maven2Client repositoryClient = createAdminMaven2Client(repository.getName()); | ||
|
||
// create clients | ||
final List<Callable<HttpResponse>> clients = new ArrayList<>(); | ||
for (int i = 0; i < 10; i++) { | ||
clients.add(new UriPut(repositoryClient, path, generator, lengthSupplier)); | ||
} | ||
for (int i = 0; i < 15; i++) { | ||
clients.add(new UriGet(repositoryClient, path)); | ||
} | ||
|
||
// put initial as we don't know will GET or PUT "win" | ||
final HttpEntity entity = generatedEntity(generator, lengthSupplier.get()); | ||
HttpResponse response = repositoryClient.put(path, entity); | ||
assertThat(response.getStatusLine().getStatusCode(), equalTo(201)); | ||
|
||
assertAllHttpResponseIs2xx(performSwarm(clients)); | ||
} | ||
|
||
@Test | ||
public void smallZip() throws Exception { | ||
hostedSwarm(RELEASE_ZIP_ARTIFACT_PATH, new ZipGenerator(), Suppliers.ofInstance(ByteSize.kiloBytes(1))); | ||
} | ||
|
||
@Test | ||
public void mediumZip() throws Exception { | ||
hostedSwarm(RELEASE_ZIP_ARTIFACT_PATH, new ZipGenerator(), Suppliers.ofInstance(ByteSize.kiloBytes(100))); | ||
} | ||
|
||
@Test | ||
public void largeZip() throws Exception { | ||
hostedSwarm(RELEASE_ZIP_ARTIFACT_PATH, new ZipGenerator(), Suppliers.ofInstance(ByteSize.megaBytes(5))); | ||
} | ||
|
||
@Test | ||
public void smallXml() throws Exception { | ||
hostedSwarm(RELEASE_XML_ARTIFACT_PATH, new XmlGenerator(), Suppliers.ofInstance(ByteSize.kiloBytes(1))); | ||
} | ||
|
||
@Test | ||
public void mediumXml() throws Exception { | ||
hostedSwarm(RELEASE_XML_ARTIFACT_PATH, new XmlGenerator(), Suppliers.ofInstance(ByteSize.kiloBytes(10))); | ||
} | ||
|
||
@Test | ||
public void largeXml() throws Exception { | ||
hostedSwarm(RELEASE_XML_ARTIFACT_PATH, new XmlGenerator(), Suppliers.ofInstance(ByteSize.kiloBytes(500))); | ||
} | ||
} |
260 changes: 260 additions & 0 deletions
260
...e/src/test/java/org/sonatype/nexus/testsuite/maven/concurrency/MavenHotspotITSupport.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
/* | ||
* Sonatype Nexus (TM) Open Source Version | ||
* Copyright (c) 2008-present Sonatype, Inc. | ||
* All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions. | ||
* | ||
* This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0, | ||
* which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html. | ||
* | ||
* Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks | ||
* of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the | ||
* Eclipse Foundation. All other trademarks are the property of their respective owners. | ||
*/ | ||
package org.sonatype.nexus.testsuite.maven.concurrency; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.Callable; | ||
import java.util.concurrent.CountDownLatch; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
import java.util.concurrent.Future; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import org.sonatype.nexus.log.LoggerLevel; | ||
import org.sonatype.nexus.repository.maven.MavenHostedFacet; | ||
import org.sonatype.nexus.testsuite.maven.Maven2Client; | ||
import org.sonatype.nexus.testsuite.maven.MavenITSupport; | ||
import org.sonatype.nexus.testsuite.maven.concurrency.generators.Generator; | ||
import org.sonatype.sisu.goodies.common.ByteSize; | ||
|
||
import com.google.common.base.Function; | ||
import com.google.common.base.Supplier; | ||
import com.google.common.base.Throwables; | ||
import com.google.common.collect.Iterables; | ||
import com.google.common.collect.Lists; | ||
import com.google.common.collect.Maps; | ||
import com.google.common.io.ByteStreams; | ||
import org.apache.http.HttpEntity; | ||
import org.apache.http.HttpResponse; | ||
import org.apache.http.util.EntityUtils; | ||
import org.hamcrest.BaseMatcher; | ||
import org.hamcrest.Description; | ||
import org.hamcrest.Matcher; | ||
import org.junit.Before; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
import static com.google.common.base.Preconditions.checkNotNull; | ||
import static com.google.common.base.Preconditions.checkState; | ||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.sonatype.nexus.testsuite.maven.concurrency.generators.Generator.generatedEntity; | ||
|
||
/** | ||
* Maven Concurrency Hotspot IT support. | ||
*/ | ||
public abstract class MavenHotspotITSupport | ||
extends MavenITSupport | ||
{ | ||
@Before | ||
public void setupMavenDebugStorage() { | ||
logManager.setLoggerLevel("org.sonatype.nexus.repository.storage", LoggerLevel.DEBUG); | ||
} | ||
|
||
protected <T> List<T> performSwarm(final List<Callable<T>> clients) throws Exception { | ||
final ExecutorService executorService = Executors | ||
.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); | ||
final CountDownLatch startLatch = new CountDownLatch(1); | ||
final CountDownLatch endLatch = new CountDownLatch(clients.size()); | ||
|
||
final Map<Integer, Future<T>> futures = Maps.newLinkedHashMap(); | ||
int i = 0; | ||
for (Callable<T> client : clients) { | ||
final Future<T> future = executorService.submit(new ControlledCallable(startLatch, endLatch, client)); | ||
futures.put(i++, future); | ||
} | ||
|
||
// let it loose and wait for them to finish | ||
startLatch.countDown(); | ||
endLatch.await(30, TimeUnit.SECONDS); | ||
executorService.shutdown(); | ||
executorService.awaitTermination(30, TimeUnit.SECONDS); | ||
|
||
return Lists.newArrayList( | ||
Iterables.transform(futures.values(), new Function<Future<T>, T>() | ||
{ | ||
@Override | ||
public T apply(final Future<T> input) { | ||
try { | ||
return input.get(); | ||
} | ||
catch (Exception e) { | ||
throw Throwables.propagate(e); | ||
} | ||
} | ||
}) | ||
); | ||
} | ||
|
||
private static class ControlledCallable<T> | ||
implements Callable<T> | ||
{ | ||
private final CountDownLatch startLatch; | ||
|
||
private final CountDownLatch endLatch; | ||
|
||
private final Callable<T> controlled; | ||
|
||
public ControlledCallable(final CountDownLatch startLatch, | ||
final CountDownLatch endLatch, | ||
final Callable<T> controlled) | ||
{ | ||
this.startLatch = startLatch; | ||
this.endLatch = endLatch; | ||
this.controlled = controlled; | ||
} | ||
|
||
@Override | ||
public T call() throws Exception { | ||
startLatch.await(); | ||
try { | ||
return controlled.call(); | ||
} | ||
finally { | ||
endLatch.countDown(); | ||
} | ||
} | ||
} | ||
|
||
// == clients | ||
|
||
protected void assertAllHttpResponse(final List<HttpResponse> responses, final Matcher<HttpResponse> matcher) { | ||
for (HttpResponse response : responses) { | ||
assertThat(response, matcher); | ||
} | ||
} | ||
|
||
protected void assertAllHttpResponseIs2xx(final List<HttpResponse> responses) { | ||
assertAllHttpResponse(responses, new BaseMatcher<HttpResponse>() | ||
{ | ||
@Override | ||
public boolean matches(final Object o) { | ||
if (o instanceof HttpResponse) { | ||
HttpResponse r = (HttpResponse) o; | ||
return r.getStatusLine().getStatusCode() >= 200 && r.getStatusLine().getStatusCode() <= 299; | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public void describeTo(final Description description) { | ||
description.appendText("HTTP 2xx response code"); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* A simple client performing a GET against single URL. | ||
*/ | ||
public static class UriGet | ||
implements Callable<HttpResponse> | ||
{ | ||
private final Maven2Client client; | ||
|
||
private final String uri; | ||
|
||
public UriGet(final Maven2Client client, final String uri) { | ||
this.client = checkNotNull(client); | ||
this.uri = checkNotNull(uri); | ||
} | ||
|
||
@Override | ||
public HttpResponse call() throws Exception { | ||
HttpResponse response = client.get(uri); | ||
checkState(response.getEntity() != null); | ||
// consume by actually reading the stream | ||
ByteStreams.copy(response.getEntity().getContent(), ByteStreams.nullOutputStream()); | ||
EntityUtils.consume(response.getEntity()); | ||
return response; | ||
} | ||
} | ||
|
||
/** | ||
* A simple client performing a PUT against single URL. | ||
*/ | ||
public static class UriPut | ||
implements Callable<HttpResponse> | ||
{ | ||
private final Maven2Client client; | ||
|
||
private final String uri; | ||
|
||
private final Generator generator; | ||
|
||
private final Supplier<ByteSize> lengthSupplier; | ||
|
||
public UriPut(final Maven2Client client, | ||
final String uri, | ||
final Generator generator, | ||
final Supplier<ByteSize> lengthSupplier) | ||
{ | ||
this.client = checkNotNull(client); | ||
this.uri = checkNotNull(uri); | ||
this.generator = checkNotNull(generator); | ||
this.lengthSupplier = checkNotNull(lengthSupplier); | ||
} | ||
|
||
@Override | ||
public HttpResponse call() throws Exception { | ||
final HttpEntity entity = generatedEntity(generator, lengthSupplier.get()); | ||
HttpResponse response = client.put(uri, entity); | ||
EntityUtils.consume(response.getEntity()); | ||
return response; | ||
} | ||
} | ||
|
||
/** | ||
* Repeat. | ||
*/ | ||
public static class Repeat<V> | ||
implements Callable<List<V>> | ||
{ | ||
private final int count; | ||
|
||
private final Callable<V> delegate; | ||
|
||
public Repeat(final int count, final Callable<V> delegate) { | ||
checkArgument(count > 0); | ||
this.count = count; | ||
this.delegate = checkNotNull(delegate); | ||
} | ||
|
||
@Override | ||
public List<V> call() throws Exception { | ||
final List<V> result = new ArrayList<>(count); | ||
for (int i = 0; i < count; i++) { | ||
result.add(delegate.call()); | ||
} | ||
return result; | ||
} | ||
} | ||
|
||
/** | ||
* A simple client rebuilding Maven2 metadata using blocking execution. | ||
*/ | ||
public static class RebuildMavenMetadata | ||
implements Callable<HttpResponse> | ||
{ | ||
private final MavenHostedFacet mavenHostedFacet; | ||
|
||
public RebuildMavenMetadata(final MavenHostedFacet mavenHostedFacet) { | ||
this.mavenHostedFacet = checkNotNull(mavenHostedFacet); | ||
} | ||
|
||
@Override | ||
public HttpResponse call() throws Exception { | ||
mavenHostedFacet.rebuildMetadata(null, null, null); | ||
return null; | ||
} | ||
} | ||
} |
Oops, something went wrong.