diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt index c1831a693f93..a3d821fca8fd 100644 --- a/eng/versioning/version_client.txt +++ b/eng/versioning/version_client.txt @@ -55,6 +55,7 @@ com.azure:azure-core;1.14.1;1.15.0-beta.1 com.azure:azure-core-amqp;2.0.3;2.1.0-beta.1 com.azure:azure-core-amqp-experimental;1.0.0-beta.1;1.0.0-beta.1 com.azure:azure-core-experimental;1.0.0-beta.11;1.0.0-beta.12 +com.azure:azure-core-experimental-perf;1.0.0-beta.1;1.0.0-beta.1 com.azure:azure-core-http-jdk-httpclient;1.0.0-beta.1;1.0.0-beta.1 com.azure:azure-core-http-netty;1.9.0;1.10.0-beta.1 com.azure:azure-core-http-okhttp;1.6.0;1.7.0-beta.1 diff --git a/sdk/core/azure-core-experimental-perf/CHANGELOG.md b/sdk/core/azure-core-experimental-perf/CHANGELOG.md new file mode 100644 index 000000000000..4144f75694a0 --- /dev/null +++ b/sdk/core/azure-core-experimental-perf/CHANGELOG.md @@ -0,0 +1,3 @@ +# Release History + +## 1.0.0-beta.1 (Unreleased) diff --git a/sdk/core/azure-core-experimental-perf/README.md b/sdk/core/azure-core-experimental-perf/README.md new file mode 100644 index 000000000000..9a0e06050f6d --- /dev/null +++ b/sdk/core/azure-core-experimental-perf/README.md @@ -0,0 +1,34 @@ +# Azure Core Experimental Performance test client library for Java + +Represents Performance tests for Azure Core SDK for Java. + +## Getting started + +### Prerequisites + +- Java Development Kit (JDK) with version 8 or above + +### Adding the package to your product + + +## Key concepts + + +## Examples + +## Troubleshooting + +## Next steps + +## Contributing + +If you would like to become an active contributor to this project please follow the instructions provided in [Microsoft +Azure Projects Contribution Guidelines](http://azure.github.io/guidelines.html). + +1. Fork it +1. Create your feature branch (`git checkout -b my-new-feature`) +1. Commit your changes (`git commit -am 'Add some feature'`) +1. Push to the branch (`git push origin my-new-feature`) +1. Create new Pull Request + +![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-java%2Fsdk%2Fcore%2Fexperimental-perf-test%2FREADME.png) diff --git a/sdk/core/azure-core-experimental-perf/pom.xml b/sdk/core/azure-core-experimental-perf/pom.xml new file mode 100644 index 000000000000..5cb5a0b43aec --- /dev/null +++ b/sdk/core/azure-core-experimental-perf/pom.xml @@ -0,0 +1,64 @@ + + + + com.azure + azure-client-sdk-parent + 1.7.0 + ../../parents/azure-client-sdk-parent + + 4.0.0 + + azure-core-experimental-perf + 1.0.0-beta.1 + + + + + com.azure + azure-core + 1.15.0-beta.1 + + + com.azure + perf-test-core + 1.0.0-beta.1 + + + com.azure + azure-core-experimental + 1.0.0-beta.12 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.2.0 + + + package + + single + + + + + + com.azure.core.perf.App + + + + + jar-with-dependencies + + + + + + + + diff --git a/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/ARMChallengeAuthenticationPolicyTest.java b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/ARMChallengeAuthenticationPolicyTest.java new file mode 100644 index 000000000000..1340e992d91e --- /dev/null +++ b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/ARMChallengeAuthenticationPolicyTest.java @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.perf; + +import com.azure.core.credential.AccessToken; +import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRequestContext; +import com.azure.core.experimental.http.policy.ARMChallengeAuthenticationPolicy; +import com.azure.core.experimental.http.policy.BearerTokenAuthenticationChallengePolicy; +import com.azure.core.experimental.implementation.AzureEnvironment; +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.HttpPipelineBuilder; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.rest.RestProxy; +import com.azure.core.perf.core.MockHttpClient; +import com.azure.core.perf.core.MyRestProxyService; +import com.azure.core.perf.models.MockHttpResponse; +import com.azure.core.util.CoreUtils; +import com.azure.core.util.logging.ClientLogger; +import com.azure.perf.test.core.PerfStressOptions; +import com.azure.perf.test.core.PerfStressTest; +import reactor.core.publisher.Mono; + +import java.time.OffsetDateTime; +import java.util.HashMap; + +/** + * The Performance Test class for Bearer Token Authentication Challenge Policy. + */ +public class ARMChallengeAuthenticationPolicyTest extends PerfStressTest { + private static final String CLAIMS_ACCESS_TOKEN = "CLAIMS-ACCESS-TOKEN"; + private static final String ACCESS_TOKEN = "ACCESS-TOKEN"; + private static final String AUTHORIZATION = "Authorization"; + private static final String APPLICATION_JSON = "application/json"; + private static final String CONTENT_TYPE = "Content-Type"; + private static final String MOCK_CHALLENGE = "Bearer authorization_uri=" + + "\"https://login.windows-ppe.net/\", error=\"invalid_token\"," + + " error_description=\"User session has been revoked\"," + + " claims=\"eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTYwMzc0MjgwMCJ9fX0=\""; + + private final ClientLogger logger = new ClientLogger(ARMChallengeAuthenticationPolicyTest.class); + private final MockHttpClient mockHTTPClient; + private final MyRestProxyService service; + private boolean trigger = false; + + /** + * Creates an instance of the ARMChallengeAuthenticationPolicyTest class. + * @param options the command line options to run the performance test. + */ + public ARMChallengeAuthenticationPolicyTest(PerfStressOptions options) { + super(options); + mockHTTPClient = new MockHttpClient((httpRequest) -> { + String bearerToken = httpRequest.getHeaders().getValue(AUTHORIZATION); + if (bearerToken.contains(CLAIMS_ACCESS_TOKEN)) { + return createMockSuccessResponse(httpRequest, APPLICATION_JSON); + } else { + return createMockClaimsResponse(httpRequest, "application/json"); + } + }); + final HttpPipeline pipeline = new HttpPipelineBuilder() + .policies(new ARMChallengeAuthenticationPolicy(new TokenCredential() { + @Override + public Mono getToken(TokenRequestContext request) { + return Mono.defer(() -> { + AccessToken token; + if (!CoreUtils.isNullOrEmpty(request.getClaims()) && request.getClaims().length() > 1) { + token = new AccessToken(CLAIMS_ACCESS_TOKEN, OffsetDateTime.now().plusDays(1)); + } else { + token = new AccessToken(ACCESS_TOKEN, OffsetDateTime.now().plusDays(1)); + } + return Mono.just(token); + }); + } + }, new AzureEnvironment(new HashMap<>()), "Dummy-Scope")) + .httpClient(mockHTTPClient) + .build(); + + service = RestProxy.create(MyRestProxyService.class, pipeline); + } + + @Override + public void run() { + throw logger.logExceptionAsError(new UnsupportedOperationException()); + } + + @Override + public Mono runAsync() { + if (!trigger) { + trigger = true; + return service.listSubscriptions().then(); + } else { + return Mono.empty(); + } + + } + + private HttpResponse createMockClaimsResponse(HttpRequest httpRequest, String contentType) { + HttpHeaders headers = new HttpHeaders().set(CONTENT_TYPE, contentType); + headers.set(BearerTokenAuthenticationChallengePolicy.WWW_AUTHENTICATE, MOCK_CHALLENGE); + HttpResponse res = new MockHttpResponse(httpRequest, 401, headers); + return res; + } + + private HttpResponse createMockSuccessResponse(HttpRequest httpRequest, String contentType) { + HttpHeaders headers = new HttpHeaders().set(CONTENT_TYPE, contentType); + HttpResponse res = new MockHttpResponse(httpRequest, 200, headers); + return res; + } +} diff --git a/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/App.java b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/App.java new file mode 100644 index 000000000000..907c38d9a696 --- /dev/null +++ b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/App.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.perf; + +import com.azure.perf.test.core.PerfStressProgram; + +/** + * Runs the Storage performance test. + * + *

To run from command line. Package the project into a jar with dependencies via mvn clean package. + * Then run the program via java -jar 'compiled-jar-with-dependencies-path'

+ * + *

To run from IDE, set all the required environment variables in IntelliJ via Run -> EditConfigurations + * section. + * Then run the App's main method via IDE.

+ */ +public class App { + + /** + * The main method for the performance testing package. + * @param args the command line arguments. + * @throws RuntimeException if the performance tests implementation classes cannot be found. + */ + public static void main(String[] args) { + Class[] testClasses; + + try { + testClasses = new Class[]{ + Class.forName("com.azure.core.perf.ARMChallengeAuthenticationPolicyTest") + }; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + + PerfStressProgram.run(testClasses, args); + } +} diff --git a/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/core/MockHttpClient.java b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/core/MockHttpClient.java new file mode 100644 index 000000000000..e2824f3f7809 --- /dev/null +++ b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/core/MockHttpClient.java @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.perf.core; + +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import reactor.core.publisher.Mono; + +import java.util.function.Function; + +/** + * The Mock Http Client to send recorded responses back to the Http Pipeline. + */ +public class MockHttpClient implements HttpClient { + private final Function responseSupplier; + + /** + * Creates an instance of the {@link MockHttpClient} + * + * @param responseSupplier the supplier to supply the {@link HttpResponse} + */ + public MockHttpClient(Function responseSupplier) { + this.responseSupplier = responseSupplier; + } + + @Override + public Mono send(HttpRequest request) { + return Mono.just(responseSupplier.apply(request)); + } +} diff --git a/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/core/MyRestProxyService.java b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/core/MyRestProxyService.java new file mode 100644 index 000000000000..3aeb6b27f1ee --- /dev/null +++ b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/core/MyRestProxyService.java @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.perf.core; + +import com.azure.core.annotation.Get; +import com.azure.core.annotation.Host; + +import com.azure.core.annotation.ServiceInterface; +import reactor.core.publisher.Mono; + + +/** + * Mock Rest Proxy Service for Performance Testing. + */ +@Host("https://unused") +@ServiceInterface(name = "MyMockService") +public interface MyRestProxyService { + + /** + * List all the subscriptions + * @return A {@link Mono} containing Void. + */ + @Get("ListSubscriptions") + Mono listSubscriptions(); +} diff --git a/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/core/package-info.java b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/core/package-info.java new file mode 100644 index 000000000000..ba911ea58024 --- /dev/null +++ b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/core/package-info.java @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * Core package of azure-core-experimental-perf. + */ +package com.azure.core.perf.core; diff --git a/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/models/MockHttpResponse.java b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/models/MockHttpResponse.java new file mode 100644 index 000000000000..011ec524ff50 --- /dev/null +++ b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/models/MockHttpResponse.java @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.perf.models; + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import com.azure.core.util.serializer.JacksonAdapter; +import com.azure.core.util.serializer.SerializerAdapter; +import com.azure.core.util.serializer.SerializerEncoding; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * Represents a Mock {@link HttpResponse} for performance testing. + */ +public class MockHttpResponse extends HttpResponse { + private static final SerializerAdapter SERIALIZER = new JacksonAdapter(); + + private final int statusCode; + + private final HttpHeaders headers; + + private final byte[] bodyBytes; + + /** + * Creates an instance of {@link MockHttpResponse} + * + * @param request The Incoming request + * @param statusCode The response status code + * @param headers The headers of the response + * @param bodyBytes the body of the response + */ + public MockHttpResponse(HttpRequest request, int statusCode, HttpHeaders headers, byte[] bodyBytes) { + super(request); + this.statusCode = statusCode; + this.headers = headers; + this.bodyBytes = bodyBytes; + } + + /** + * Creates an instance of {@link MockHttpResponse} + * + * @param request The Incoming request + * @param statusCode The response status code + */ + public MockHttpResponse(HttpRequest request, int statusCode) { + this(request, statusCode, new HttpHeaders(), new byte[0]); + } + + /** + * Creates an instance of {@link MockHttpResponse} + * + * @param request The Incoming request + * @param statusCode The response status code + * @param headers The headers of the response + */ + public MockHttpResponse(HttpRequest request, int statusCode, HttpHeaders headers) { + this(request, statusCode, headers, new byte[0]); + } + + /** + * Creates an instance of {@link MockHttpResponse} + * + * @param request The Incoming request + * @param statusCode The response status code + * @param headers The headers of the response + * @param serializable the serializable body data + */ + public MockHttpResponse(HttpRequest request, int statusCode, HttpHeaders headers, Object serializable) { + this(request, statusCode, headers, serialize(serializable)); + } + + /** + * Creates an instance of {@link MockHttpResponse} + * + * @param request The Incoming request + * @param statusCode The response status code + * @param serializable the serializable body data + */ + public MockHttpResponse(HttpRequest request, int statusCode, Object serializable) { + this(request, statusCode, new HttpHeaders(), serialize(serializable)); + } + + private static byte[] serialize(Object serializable) { + byte[] result = null; + try { + final String serializedString = SERIALIZER.serialize(serializable, SerializerEncoding.JSON); + result = serializedString == null ? null : serializedString.getBytes(StandardCharsets.UTF_8); + } catch (IOException e) { + e.printStackTrace(); + } + return result; + } + + @Override + public int getStatusCode() { + return statusCode; + } + + @Override + public String getHeaderValue(String name) { + return headers.getValue(name); + } + + @Override + public HttpHeaders getHeaders() { + return new HttpHeaders(headers); + } + + @Override + public Mono getBodyAsByteArray() { + if (bodyBytes == null) { + return Mono.empty(); + } else { + return Mono.just(bodyBytes); + } + } + + @Override + public Flux getBody() { + if (bodyBytes == null) { + return Flux.empty(); + } else { + return Flux.just(ByteBuffer.wrap(bodyBytes)); + } + } + + @Override + public Mono getBodyAsString() { + return getBodyAsString(StandardCharsets.UTF_8); + } + + @Override + public Mono getBodyAsString(Charset charset) { + if (bodyBytes == null) { + return Mono.empty(); + } else { + return Mono.just(new String(bodyBytes, charset)); + } + } +} diff --git a/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/models/package-info.java b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/models/package-info.java new file mode 100644 index 000000000000..9685e18b91a1 --- /dev/null +++ b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/models/package-info.java @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * Models package of azure-core-experimental-perf. + */ +package com.azure.core.perf.models; diff --git a/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/package-info.java b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/package-info.java new file mode 100644 index 000000000000..f79b92072f23 --- /dev/null +++ b/sdk/core/azure-core-experimental-perf/src/main/java/com/azure/core/perf/package-info.java @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * Performance Test package of azure-core-experimental-perf. + */ +package com.azure.core.perf; diff --git a/sdk/core/pom.xml b/sdk/core/pom.xml index 31612387f1ca..ac47db15467c 100644 --- a/sdk/core/pom.xml +++ b/sdk/core/pom.xml @@ -123,6 +123,7 @@ azure-core-amqp azure-core-amqp-experimental azure-core-experimental + azure-core-experimental-perf azure-core-http-jdk-httpclient azure-core-http-netty azure-core-http-okhttp diff --git a/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/ReadCache.java b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/ReadCache.java index e3a87ea9fe68..db7b7ff0b4d8 100644 --- a/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/ReadCache.java +++ b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/ReadCache.java @@ -16,6 +16,7 @@ public ReadCache(PerfStressOptions options) { super(options); credential = new SharedTokenCacheCredentialBuilder() .clientId(CLI_CLIENT_ID) + .tokenCachePersistenceOptions(TOKEN_CACHE_PERSISTENCE_OPTIONS) .build(); } diff --git a/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/core/ServiceTest.java b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/core/ServiceTest.java index 6b2ac25f18e4..031d0d181bb8 100644 --- a/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/core/ServiceTest.java +++ b/sdk/identity/azure-identity-perf/src/main/java/com/azure/identity/perf/core/ServiceTest.java @@ -6,6 +6,7 @@ import com.azure.core.credential.TokenRequestContext; import com.azure.identity.InteractiveBrowserCredential; import com.azure.identity.InteractiveBrowserCredentialBuilder; +import com.azure.identity.TokenCachePersistenceOptions; import com.azure.perf.test.core.PerfStressOptions; import com.azure.perf.test.core.PerfStressTest; import reactor.core.publisher.Mono; @@ -14,9 +15,12 @@ public abstract class ServiceTest extends Pe protected static final String CLI_CLIENT_ID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; protected static final TokenRequestContext ARM_TOKEN_REQUEST_CONTEXT = new TokenRequestContext() .addScopes("https://management.azure.com/.default"); + protected static final TokenCachePersistenceOptions TOKEN_CACHE_PERSISTENCE_OPTIONS = + new TokenCachePersistenceOptions().setName("azure-identity-perf-test"); private InteractiveBrowserCredential interactiveBrowserCredential = new InteractiveBrowserCredentialBuilder() .port(8765) + .tokenCachePersistenceOptions(TOKEN_CACHE_PERSISTENCE_OPTIONS) .clientId(CLI_CLIENT_ID) .build();