Skip to content

Commit

Permalink
mgmt doc on LRO beginCreate/beginDelete (#12899)
Browse files Browse the repository at this point in the history
* support beginCreate beginDelete for GenericResource

* use generic type

* doc (preview) for LRO

* Update README.md

* rename to getActivationResponse
  • Loading branch information
weidongxu-microsoft authored Jul 15, 2020
1 parent c15f392 commit 6704678
Show file tree
Hide file tree
Showing 16 changed files with 511 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1786,7 +1786,7 @@ public Accepted<VirtualMachine> beginCreate() {
inner -> new VirtualMachineImpl(inner.name(), inner, this.manager(),
this.storageManager, this.networkManager, this.authorizationManager));

reset(accepted.getAcceptedResult().getValue().inner());
reset(accepted.getActivationResponse().getValue().inner());
return accepted;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,17 +211,17 @@ public void canCreateVirtualMachineSyncPoll() throws Exception {
.withOSDiskName("javatest")
.withLicenseType("Windows_Server")
.beginCreate();
VirtualMachine createdVirtualMachine = acceptedVirtualMachine.getAcceptedResult().getValue();
VirtualMachine createdVirtualMachine = acceptedVirtualMachine.getActivationResponse().getValue();
Assertions.assertNotEquals("Succeeded", createdVirtualMachine.provisioningState());

LongRunningOperationStatus pollStatus = acceptedVirtualMachine.getAcceptedResult().getStatus();
int delayInMills = acceptedVirtualMachine.getAcceptedResult().getRetryAfter() == null
LongRunningOperationStatus pollStatus = acceptedVirtualMachine.getActivationResponse().getStatus();
int delayInMills = acceptedVirtualMachine.getActivationResponse().getRetryAfter() == null
? 0
: (int) acceptedVirtualMachine.getAcceptedResult().getRetryAfter().toMillis();
: (int) acceptedVirtualMachine.getActivationResponse().getRetryAfter().toMillis();
while (!pollStatus.isComplete()) {
SdkContext.sleep(delayInMills);

PollResponse<Void> pollResponse = acceptedVirtualMachine.getSyncPoller().poll();
PollResponse<?> pollResponse = acceptedVirtualMachine.getSyncPoller().poll();
pollStatus = pollResponse.getStatus();
delayInMills = pollResponse.getRetryAfter() == null
? 10000
Expand All @@ -234,15 +234,15 @@ public void canCreateVirtualMachineSyncPoll() throws Exception {
Accepted<Void> acceptedDelete = computeManager.virtualMachines()
.beginDeleteByResourceGroup(virtualMachine.resourceGroupName(), virtualMachine.name());

pollStatus = acceptedDelete.getAcceptedResult().getStatus();
delayInMills = acceptedDelete.getAcceptedResult().getRetryAfter() == null
pollStatus = acceptedDelete.getActivationResponse().getStatus();
delayInMills = acceptedDelete.getActivationResponse().getRetryAfter() == null
? 0
: (int) acceptedDelete.getAcceptedResult().getRetryAfter().toMillis();
: (int) acceptedDelete.getActivationResponse().getRetryAfter().toMillis();

while (!pollStatus.isComplete()) {
SdkContext.sleep(delayInMills);

PollResponse<Void> pollResponse = acceptedDelete.getSyncPoller().poll();
PollResponse<?> pollResponse = acceptedDelete.getSyncPoller().poll();
pollStatus = pollResponse.getStatus();
delayInMills = pollResponse.getRetryAfter() == null
? 10000
Expand Down
3 changes: 3 additions & 0 deletions sdk/management/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ The key concepts of Azure Management Libraries includes:
- Integration with Azure role-based access control.
- Asynchronous operations with [Reactor][reactor]. (Preview)
- Configurable client, e.g. configuring HTTP client, retries, logging, etc.
- [API design][design]
- [API design (preview)][design_preview]

### Service features

Expand Down Expand Up @@ -325,4 +327,5 @@ If you would like to become an active contributor to this project please follow
[authenticate]: docs/AUTH.md
[sample]: docs/SAMPLE.md
[design]: docs/DESIGN.md
[design_preview]: docs/DESIGN_PREVIEW.md
[reactor]: https://projectreactor.io/
54 changes: 54 additions & 0 deletions sdk/management/docs/DESIGN_PREVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Design for Azure Management Libraries for Java (Preview)

## Fluent interface

### Fine control over long-running operation

Resource provision takes time, a typical solution adopted by Azure services is the [long-running operation (LRO)][lro].

Fluent interface does the polling operations in background, and only returns the final result.

Azure Management Libraries supports fine control over the polling for certain important resources, via `Accepted` and `SyncPoller` class. Method verb is `beginCreate` and `beginDelete`.

`Accepted` class provides following functionalities:
- `ActivationResponse` via `getActivationResponse` method provides the response of the first activation operation. Note that though it wraps a resource instance, some action on this resource instance will not work, since it is not provisioned yet.
- `SyncPoller` via `getSyncPoller` method provides the control of the polling operations. `SyncPoller.poll` can be called at desired time.
- Resource instance via `getFinalResult` method, after completion of the polling operations. The method will throw `ManagementException` if polling failed and resource cannot be provisioned.

Here is sample code for Deployment.

```java
// begin provision
Accepted<Deployment> acceptedDeployment = azure.deployments()
.define(name)
...
.beginCreate();
Deployment provisioningDeployment = acceptedDeployment.getAcceptedResult().getValue();

LongRunningOperationStatus pollStatus = acceptedDeployment.getAcceptedResult().getStatus();
int delayInMills = acceptedDeployment.getAcceptedResult().getRetryAfter() == null
? 0
: (int) acceptedDeployment.getAcceptedResult().getRetryAfter().toMillis();
while (!pollStatus.isComplete()) {
Thread.sleep(delayInMills);

// poll
PollResponse<?> pollResponse = acceptedDeployment.getSyncPoller().poll();
pollStatus = pollResponse.getStatus();
delayInMills = pollResponse.getRetryAfter() == null
? DEFAULT_DELAY_IN_MILLIS
: (int) pollResponse.getRetryAfter().toMillis();
}
// pollStatus == LongRunningOperationStatus.SUCCESSFULLY_COMPLETED, if successful

// final result
Deployment deployment = acceptedDeployment.getFinalResult();
```

Supported Azure resources:
- `delete` for `ResourceGroup`
- `create` for `Deployment`
- `create` and `delete` for `GenericResource`
- `create` and `delete` for `VirtualMachine`

[lro]: https://docs.microsoft.com/en-us/azure/architecture/patterns/async-request-reply
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
public interface Accepted<T> {

/**
* Gets the accepted result of LRO.
* Gets the activation response of LRO.
*
* @return the accepted result
* @return the activation response
*/
ActivationResponse<T> getAcceptedResult();
ActivationResponse<T> getActivationResponse();

/**
* Gets the {@link SyncPoller} of LRO.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public AcceptedImpl(Response<Flux<ByteBuffer>> activationResponse,
}

@Override
public ActivationResponse<T> getAcceptedResult() {
public ActivationResponse<T> getActivationResponse() {
try {
T value = wrapOperation.apply(serializerAdapter.deserialize(
new String(getResponse(), StandardCharsets.UTF_8),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,11 @@ protected <T extends Indexable> T taskResult(String key) {
}
}

@SuppressWarnings("unchecked")
protected Function<InnerModelT, FluentModelT> innerToFluentMap(final FluentModelImplT fluentModelImplT) {
return new Function<InnerModelT, FluentModelT>() {
@Override
public FluentModelT apply(InnerModelT innerModel) {
fluentModelImplT.setInner(innerModel);
return (FluentModelT) fluentModelImplT;
}
return innerModel -> {
fluentModelImplT.setInner(innerModel);
return (FluentModelT) fluentModelImplT;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ public Accepted<Deployment> beginCreate() {
DeploymentExtendedInner.class,
inner -> new DeploymentImpl(inner, inner.name(), resourceManager));

setInner(accepted.getAcceptedResult().getValue().inner());
setInner(accepted.getActivationResponse().getValue().inner());
return accepted;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ public void deleteById(String id) {
deleteByIdAsync(id).block();
}


@Override
public Mono<Void> deleteByIdAsync(String id) {
return deleteByResourceGroupAsync(ResourceUtils.groupFromResourceId(id), ResourceUtils.nameFromResourceId(id));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

package com.azure.resourcemanager.resources.implementation;

import com.azure.core.http.rest.Response;
import com.azure.core.util.logging.ClientLogger;
import com.azure.resourcemanager.resources.ResourceManager;
import com.azure.resourcemanager.resources.fluentcore.model.Accepted;
import com.azure.resourcemanager.resources.fluentcore.model.implementation.AcceptedImpl;
import com.azure.resourcemanager.resources.models.GenericResource;
import com.azure.resourcemanager.resources.models.Plan;
import com.azure.resourcemanager.resources.fluentcore.arm.ResourceUtils;
Expand All @@ -12,8 +16,11 @@
import com.azure.resourcemanager.resources.fluent.inner.GenericResourceInner;
import com.azure.resourcemanager.resources.ResourceManagementClient;
import com.azure.resourcemanager.resources.fluent.ResourcesClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.ByteBuffer;

/**
* The implementation for GenericResource and its nested interfaces.
*/
Expand All @@ -28,6 +35,9 @@ final class GenericResourceImpl
GenericResource.Definition,
GenericResource.UpdateStages.WithApiVersion,
GenericResource.Update {

private final ClientLogger logger = new ClientLogger(GenericResourceImpl.class);

private String resourceProviderNamespace;
private String parentResourcePath;
private String resourceType;
Expand Down Expand Up @@ -140,33 +150,44 @@ public GenericResourceImpl withApiVersion(String apiVersion) {
return this;
}

// CreateUpdateTaskGroup.ResourceCreator implementation
@Override
public Mono<GenericResource> createResourceAsync() {
final GenericResourceImpl self = this;
Mono<String> observable = null;
if (apiVersion != null) {
observable = Mono.just(apiVersion);
public Accepted<GenericResource> beginCreate() {
String apiVersion = this.getApiVersionAsync().block();

String name = this.name();
if (!isInCreateMode()) {
name = ResourceUtils.nameFromResourceId(inner().id());
}

Response<Flux<ByteBuffer>> activationResponse = this.manager().inner().getResources()
.createOrUpdateWithResponseAsync(
resourceGroupName(),
resourceProviderNamespace,
parentResourcePath(),
resourceType,
name,
apiVersion,
inner()).block();
if (activationResponse == null) {
throw logger.logExceptionAsError(new NullPointerException());
} else {
final ResourceManagementClient serviceClient = this.manager().inner();
observable = this.manager().providers().getByNameAsync(resourceProviderNamespace)
.flatMap(provider -> {
String id;
if (!isInCreateMode()) {
id = inner().id();
} else {
id = ResourceUtils.constructResourceId(
serviceClient.getSubscriptionId(),
resourceGroupName(),
resourceProviderNamespace(),
resourceType(),
this.name(),
parentResourcePath());
}
self.apiVersion = ResourceUtils.defaultApiVersion(id, provider);
return Mono.just(self.apiVersion);
});
Accepted<GenericResource> accepted = new AcceptedImpl<GenericResourceInner, GenericResource>(
activationResponse,
this.manager().inner().getSerializerAdapter(),
this.manager().inner().getHttpPipeline(),
GenericResourceInner.class,
GenericResourceInner.class,
inner -> new GenericResourceImpl(inner.id(), inner, this.manager()));

setInner(accepted.getActivationResponse().getValue().inner());
return accepted;
}
}

// CreateUpdateTaskGroup.ResourceCreator implementation
@Override
public Mono<GenericResource> createResourceAsync() {
Mono<String> observable = this.getApiVersionAsync();
final ResourcesClient resourceClient = this.manager().inner().getResources();
return observable
.flatMap(api -> {
Expand All @@ -183,7 +204,34 @@ public Mono<GenericResource> createResourceAsync() {
api,
inner())
.subscribeOn(SdkContext.getReactorScheduler())
.map(innerToFluentMap(self));
.map(innerToFluentMap(this));
});
}

private Mono<String> getApiVersionAsync() {
Mono<String> apiVersion;
if (this.apiVersion != null) {
apiVersion = Mono.just(this.apiVersion);
} else {
final ResourceManagementClient serviceClient = this.manager().inner();
apiVersion = this.manager().providers().getByNameAsync(resourceProviderNamespace)
.flatMap(provider -> {
String id;
if (!isInCreateMode()) {
id = inner().id();
} else {
id = ResourceUtils.constructResourceId(
serviceClient.getSubscriptionId(),
resourceGroupName(),
resourceProviderNamespace(),
resourceType(),
this.name(),
parentResourcePath());
}
this.apiVersion = ResourceUtils.defaultApiVersion(id, provider);
return Mono.just(this.apiVersion);
});
}
return apiVersion;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@

import com.azure.core.http.rest.PagedFlux;
import com.azure.core.http.rest.PagedIterable;
import com.azure.core.http.rest.Response;
import com.azure.core.util.logging.ClientLogger;
import com.azure.resourcemanager.resources.ResourceManager;
import com.azure.resourcemanager.resources.fluentcore.model.Accepted;
import com.azure.resourcemanager.resources.fluentcore.model.implementation.AcceptedImpl;
import com.azure.resourcemanager.resources.models.GenericResource;
import com.azure.resourcemanager.resources.models.GenericResources;
import com.azure.resourcemanager.resources.models.ResourceGroup;
Expand All @@ -17,9 +20,12 @@
import com.azure.resourcemanager.resources.fluentcore.utils.Utils;
import com.azure.resourcemanager.resources.fluent.inner.GenericResourceInner;
import com.azure.resourcemanager.resources.fluent.ResourcesClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.function.Function;

/**
* Implementation of the {@link GenericResources}.
Expand Down Expand Up @@ -179,7 +185,6 @@ public Mono<Void> deleteAsync(String resourceGroupName, String resourceProviderN
parentResourcePath, resourceType, resourceName, apiVersion);
}


@Override
protected GenericResourceImpl wrapModel(String id) {
return new GenericResourceImpl(id, new GenericResourceInner(), this.manager())
Expand Down Expand Up @@ -222,6 +227,24 @@ public Mono<Void> deleteByIdAsync(final String id) {
.flatMap(apiVersion -> inner.deleteByIdAsync(id, apiVersion));
}

@Override
public Accepted<Void> beginDeleteById(String id) {
String apiVersion = getApiVersionFromId(id).block();

Response<Flux<ByteBuffer>> activationResponse = this.inner()
.deleteByIdWithResponseAsync(id, apiVersion).block();
if (activationResponse == null) {
throw logger.logExceptionAsError(new NullPointerException());
} else {
return new AcceptedImpl<Void, Void>(activationResponse,
manager().inner().getSerializerAdapter(),
manager().inner().getHttpPipeline(),
Void.class,
Void.class,
Function.identity());
}
}

private Mono<String> getApiVersionFromId(final String id) {
return this.manager().providers().getByNameAsync(ResourceUtils.resourceProviderFromResourceId(id))
.map(provider -> ResourceUtils.defaultApiVersion(id, provider));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.azure.core.annotation.Fluent;
import com.azure.resourcemanager.resources.fluentcore.arm.models.GroupableResource;
import com.azure.resourcemanager.resources.fluentcore.arm.models.Resource;
import com.azure.resourcemanager.resources.fluentcore.model.Accepted;
import com.azure.resourcemanager.resources.fluentcore.model.Appliable;
import com.azure.resourcemanager.resources.fluentcore.model.Creatable;
import com.azure.resourcemanager.resources.fluentcore.model.Refreshable;
Expand Down Expand Up @@ -181,6 +182,13 @@ interface WithCreate extends
* @return the next stage of generic resource definition
*/
WithCreate withProperties(Object properties);

/**
* Begins creating the Azure resource.
*
* @return the accepted create operation
*/
Accepted<GenericResource> beginCreate();
}
}

Expand Down
Loading

0 comments on commit 6704678

Please sign in to comment.