Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify DELETE request handling for clusters and node pools #763

Merged
merged 2 commits into from
Oct 29, 2024
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
29 changes: 15 additions & 14 deletions frontend/pkg/frontend/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -561,13 +561,14 @@ func (f *Frontend) ArmResourceDelete(writer http.ResponseWriter, request *http.R

f.logger.Info(fmt.Sprintf("%s: ArmResourceDelete", versionedInterface))

doc, err := f.dbClient.GetResourceDoc(ctx, resourceID)
if err != nil {
if errors.Is(err, database.ErrNotFound) {
resourceDoc, cloudError := f.DeleteResource(ctx, resourceID)
if cloudError != nil {
// For resource not found errors on deletion, ARM requires
// us to simply return 204 No Content and no response body.
if cloudError.StatusCode == http.StatusNotFound {
writer.WriteHeader(http.StatusNoContent)
} else {
f.logger.Error(err.Error())
arm.WriteInternalServerError(writer)
arm.WriteCloudError(writer, cloudError)
}
return
}
Expand All @@ -576,22 +577,22 @@ func (f *Frontend) ArmResourceDelete(writer http.ResponseWriter, request *http.R

// CheckForProvisioningStateConflict does not log conflict errors
// but does log unexpected errors like database failures.
cloudError := f.CheckForProvisioningStateConflict(ctx, operationRequest, doc)
cloudError = f.CheckForProvisioningStateConflict(ctx, operationRequest, resourceDoc)
if cloudError != nil {
arm.WriteCloudError(writer, cloudError)
return
}

err = f.clusterServiceClient.DeleteCSCluster(ctx, doc.InternalID)
err = f.clusterServiceClient.DeleteCSCluster(ctx, resourceDoc.InternalID)
if err != nil {
f.logger.Error(fmt.Sprintf("failed to delete cluster %s: %v", resourceID, err))
arm.WriteInternalServerError(writer)
return
}

// Deletion is underway; mark any active operation as canceled.
if doc.ActiveOperationID != "" {
updated, err := f.dbClient.UpdateOperationDoc(ctx, doc.ActiveOperationID, func(updateDoc *database.OperationDocument) bool {
if resourceDoc.ActiveOperationID != "" {
updated, err := f.dbClient.UpdateOperationDoc(ctx, resourceDoc.ActiveOperationID, func(updateDoc *database.OperationDocument) bool {
if updateDoc.Status != arm.ProvisioningStateCanceled {
updateDoc.LastTransitionTime = time.Now()
updateDoc.Status = arm.ProvisioningStateCanceled
Expand All @@ -605,20 +606,20 @@ func (f *Frontend) ArmResourceDelete(writer http.ResponseWriter, request *http.R
return
}
if updated {
f.logger.Info(fmt.Sprintf("canceled operation '%s'", doc.ActiveOperationID))
f.logger.Info(fmt.Sprintf("canceled operation '%s'", resourceDoc.ActiveOperationID))
}
}

operationDoc, err := f.StartOperation(writer, request, doc, operationRequest)
operationDoc, err := f.StartOperation(writer, request, resourceDoc, operationRequest)
if err != nil {
f.logger.Error(fmt.Sprintf("failed to write operation document: %v", err))
arm.WriteInternalServerError(writer)
return
}

updated, err := f.dbClient.UpdateResourceDoc(ctx, resourceID, func(doc *database.ResourceDocument) bool {
doc.ActiveOperationID = operationDoc.ID
doc.ProvisioningState = operationDoc.Status
updated, err := f.dbClient.UpdateResourceDoc(ctx, resourceID, func(updateDoc *database.ResourceDocument) bool {
updateDoc.ActiveOperationID = operationDoc.ID
updateDoc.ProvisioningState = operationDoc.Status
return true
})
if err != nil {
Expand Down
35 changes: 35 additions & 0 deletions frontend/pkg/frontend/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,41 @@ func (f *Frontend) CheckForProvisioningStateConflict(ctx context.Context, operat
return nil
}

func (f *Frontend) DeleteResource(ctx context.Context, resourceID *arm.ResourceID) (*database.ResourceDocument, *arm.CloudError) {
doc, err := f.dbClient.GetResourceDoc(ctx, resourceID)
if err != nil {
if errors.Is(err, database.ErrNotFound) {
return nil, arm.NewResourceNotFoundError(resourceID)
} else {
f.logger.Error(err.Error())
return nil, arm.NewInternalServerError()
}
}

switch doc.InternalID.Kind() {
case cmv1.ClusterKind:
err = f.clusterServiceClient.DeleteCSCluster(ctx, doc.InternalID)

case cmv1.NodePoolKind:
err = f.clusterServiceClient.DeleteCSNodePool(ctx, doc.InternalID)

default:
f.logger.Error(fmt.Sprintf("unsupported Cluster Service path: %s", doc.InternalID))
return nil, arm.NewInternalServerError()
}

if err != nil {
var ocmError *ocmerrors.Error
if errors.As(err, &ocmError) && ocmError.Status() == http.StatusNotFound {
return nil, arm.NewResourceNotFoundError(resourceID)
}
f.logger.Error(err.Error())
return nil, arm.NewInternalServerError()
}

return doc, nil
}

func (f *Frontend) MarshalResource(ctx context.Context, resourceID *arm.ResourceID, versionedInterface api.Version) ([]byte, *arm.CloudError) {
var responseBody []byte

Expand Down
93 changes: 0 additions & 93 deletions frontend/pkg/frontend/node_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"fmt"
"maps"
"net/http"
"time"

cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"

Expand Down Expand Up @@ -260,98 +259,6 @@ func (f *Frontend) CreateOrUpdateNodePool(writer http.ResponseWriter, request *h
}
}

func (f *Frontend) DeleteNodePool(writer http.ResponseWriter, request *http.Request) {
ctx := request.Context()

versionedInterface, err := VersionFromContext(ctx)
if err != nil {
f.logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}

resourceID, err := ResourceIDFromContext(ctx)
if err != nil {
f.logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}

f.logger.Info(fmt.Sprintf("%s: DeleteNodePool", versionedInterface))

doc, err := f.dbClient.GetResourceDoc(ctx, resourceID)
if err != nil {
if errors.Is(err, database.ErrNotFound) {
writer.WriteHeader(http.StatusNoContent)
} else {
f.logger.Error(err.Error())
arm.WriteInternalServerError(writer)
}
return
}

operationRequest := database.OperationRequestDelete

// CheckForProvisioningStateConflict does not log conflict errors
// but does log unexpected errors like database failures.
cloudError := f.CheckForProvisioningStateConflict(ctx, operationRequest, doc)
if cloudError != nil {
arm.WriteCloudError(writer, cloudError)
return
}

err = f.clusterServiceClient.DeleteCSNodePool(ctx, doc.InternalID)
if err != nil {
f.logger.Error(fmt.Sprintf("failed to delete node pool %s: %v", resourceID, err))
arm.WriteInternalServerError(writer)
return
}

// Deletion is underway; mark any active operation as canceled.
if doc.ActiveOperationID != "" {
updated, err := f.dbClient.UpdateOperationDoc(ctx, doc.ActiveOperationID, func(updateDoc *database.OperationDocument) bool {
if updateDoc.Status != arm.ProvisioningStateCanceled {
updateDoc.LastTransitionTime = time.Now()
updateDoc.Status = arm.ProvisioningStateCanceled
return true
}
return false
})
if err != nil {
f.logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}
if updated {
f.logger.Info(fmt.Sprintf("canceled operation '%s'", doc.ActiveOperationID))
}
}

operationDoc, err := f.StartOperation(writer, request, doc, operationRequest)
if err != nil {
f.logger.Error(fmt.Sprintf("failed to write operation document: %v", err))
arm.WriteInternalServerError(writer)
return
}

updated, err := f.dbClient.UpdateResourceDoc(ctx, resourceID, func(doc *database.ResourceDocument) bool {
doc.ActiveOperationID = operationDoc.ID
doc.ProvisioningState = operationDoc.Status
return true
})
if err != nil {
f.logger.Error(err.Error())
arm.WriteInternalServerError(writer)
return
}
if updated {
f.logger.Info(fmt.Sprintf("document updated for %s", resourceID))
}

writer.WriteHeader(http.StatusAccepted)
}

// marshalCSNodePool renders a CS NodePool object in JSON format, applying
// the necessary conversions for the API version of the request.
func marshalCSNodePool(csNodePool *cmv1.NodePool, doc *database.ResourceDocument, versionedInterface api.Version) ([]byte, error) {
hcpNodePool := ConvertCStoNodePool(doc.Key, csNodePool)
Expand Down
2 changes: 1 addition & 1 deletion frontend/pkg/frontend/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (f *Frontend) routes() *MiddlewareMux {
postMuxMiddleware.HandlerFunc(f.CreateOrUpdateNodePool))
mux.Handle(
MuxPattern(http.MethodDelete, PatternSubscriptions, PatternResourceGroups, PatternProviders, PatternClusters, PatternNodePools),
postMuxMiddleware.HandlerFunc(f.DeleteNodePool))
postMuxMiddleware.HandlerFunc(f.ArmResourceDelete))

// Operation endpoints
postMuxMiddleware = NewMiddleware(
Expand Down
Loading