Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 15 additions & 18 deletions azure/services/async/async.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,14 @@ func CreateResource(ctx context.Context, scope FutureScope, client Creator, spec
// No long running operation is active, so create the resource.
scope.V(2).Info("creating resource", "service", serviceName, "resource", resourceName, "resourceGroup", rgName)
result, sdkFuture, err := client.CreateOrUpdateAsync(ctx, spec)
if err != nil {
if sdkFuture != nil {
future, err := converters.SDKToFuture(sdkFuture, infrav1.PutFuture, serviceName, resourceName, rgName)
if err != nil {
return nil, errors.Wrapf(err, "failed to create resource %s/%s (service: %s)", rgName, resourceName, serviceName)
}
scope.SetLongRunningOperationState(future)
return nil, azure.WithTransientError(azure.NewOperationNotDoneError(future), retryAfter(sdkFuture))
if sdkFuture != nil {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karuppiah7890 please take a look. I believe this addresses your comment in #1684 (comment). I think it makes sense to not require an error (that way this will work whether an error is returned or not). However, I still think we shouldn't silence the error returned by WaitForCompletionRef in the clients since eventually it might return a different error besides context deadline exceeded.

future, err := converters.SDKToFuture(sdkFuture, infrav1.PutFuture, serviceName, resourceName, rgName)
if err != nil {
return nil, errors.Wrapf(err, "failed to create resource %s/%s (service: %s)", rgName, resourceName, serviceName)
}

scope.SetLongRunningOperationState(future)
return nil, azure.WithTransientError(azure.NewOperationNotDoneError(future), retryAfter(sdkFuture))
} else if err != nil {
return nil, errors.Wrapf(err, "failed to create resource %s/%s (service: %s)", rgName, resourceName, serviceName)
}

Expand All @@ -120,19 +118,18 @@ func DeleteResource(ctx context.Context, scope FutureScope, client Deleter, spec
// No long running operation is active, so delete the resource.
scope.V(2).Info("deleting resource", "service", serviceName, "resource", resourceName, "resourceGroup", rgName)
sdkFuture, err := client.DeleteAsync(ctx, spec)
if err != nil {
if sdkFuture != nil {
future, err := converters.SDKToFuture(sdkFuture, infrav1.DeleteFuture, serviceName, resourceName, rgName)
if err != nil {
return errors.Wrapf(err, "failed to delete resource %s/%s (service: %s)", rgName, resourceName, serviceName)
}
scope.SetLongRunningOperationState(future)
return azure.WithTransientError(azure.NewOperationNotDoneError(future), retryAfter(sdkFuture))
} else if err != nil {
if azure.ResourceNotFound(err) {
// already deleted
return nil
} else if sdkFuture != nil {
future, err := converters.SDKToFuture(sdkFuture, infrav1.DeleteFuture, serviceName, resourceName, rgName)
if err != nil {
return errors.Wrapf(err, "failed to delete resource %s/%s (service: %s)", rgName, resourceName, serviceName)
}
scope.SetLongRunningOperationState(future)
return azure.WithTransientError(azure.NewOperationNotDoneError(future), retryAfter(sdkFuture))
}

return errors.Wrapf(err, "failed to delete resource %s/%s (service: %s)", rgName, resourceName, serviceName)
}

Expand Down
56 changes: 19 additions & 37 deletions azure/services/virtualmachines/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,29 @@ func (ac *AzureClient) CreateOrUpdateAsync(ctx context.Context, spec azure.Resou
ctx, _, done := tele.StartSpanWithLogger(ctx, "virtualmachines.AzureClient.CreateOrUpdate")
defer done()

vm, err := ac.vmParams(ctx, spec)
var existingVM interface{}

if existing, err := ac.Get(ctx, spec.ResourceGroupName(), spec.ResourceName()); err != nil && !azure.ResourceNotFound(err) {
return nil, nil, errors.Wrapf(err, "failed to get virtual machine %s in %s", spec.ResourceName(), spec.ResourceGroupName())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we handle errors that are not ResourceNotFound?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's a ResourceNotFound error, that means the VM doesn't exist, so we don't set existingVM. It's not an error we want to return, it's an expected error whenever we create the resource for the first time.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah, this used to be in a different form in virtualmachines.go. Perhaps I should be more in sync with the new code, but alas the code itself is not in sync anymore.

} else if err == nil {
existingVM = existing
}

params, err := spec.Parameters(existingVM)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to get desired parameters for virtual machine %s", spec.ResourceName())
} else if vm == nil {
// nothing to do here.
return nil, nil, nil
}

future, err := ac.virtualmachines.CreateOrUpdate(ctx, spec.ResourceGroupName(), spec.ResourceName(), *vm)
vm, ok := params.(compute.VirtualMachine)
if !ok {
if params == nil {
// nothing to do here.
return existingVM, nil, nil
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this addresses #1697 (comment)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this to line 86 and invoke spec.Parameters() can be only for new vms? wdyt?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this specific VM service we technically could because we never want to update existing VMs but this pattern works best for all services in general: we pass in the existing resource to spec.Parameters(existing) and that function determines if the resource needs to be created or updated based on the existing resource. For example, for NSGs we update the group the security rules are missing. IMO it's best to use the same pattern throughout all services so it's easier to refactor them later to have even more code in common.

}
return nil, nil, errors.Errorf("%T is not a compute.VirtualMachine", params)
}

future, err := ac.virtualmachines.CreateOrUpdate(ctx, spec.ResourceGroupName(), spec.ResourceName(), vm)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -186,35 +200,3 @@ func (ac *AzureClient) Result(ctx context.Context, futureData azureautorest.Futu

return result(ac.virtualmachines)
}

// vmParams creates a VirtualMachine object from the given spec.
func (ac *AzureClient) vmParams(ctx context.Context, spec azure.ResourceSpecGetter) (*compute.VirtualMachine, error) {
ctx, span := tele.Tracer().Start(ctx, "virtualmachines.AzureClient.vmParams")
defer span.End()

var params interface{}
var existing interface{}

if existingVM, err := ac.Get(ctx, spec.ResourceGroupName(), spec.ResourceName()); err != nil && !azure.ResourceNotFound(err) {
return nil, errors.Wrapf(err, "failed to get virtual machine %s in %s", spec.ResourceName(), spec.ResourceGroupName())
} else if err == nil {
// virtual machine already exists
existing = existingVM
}

params, err := spec.Parameters(existing)
if err != nil {
return nil, err
}

vm, ok := params.(compute.VirtualMachine)
if !ok {
if params == nil {
// nothing to do here.
return nil, nil
}
return nil, errors.Errorf("%T is not a compute.VirtualMachine", params)
}

return &vm, nil
}