Skip to content

Commit

Permalink
handle various lag scenarios (removal and orphan) while CTs attached
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismarget-j committed Apr 26, 2024
1 parent 98b37dd commit 0285d98
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 20 deletions.
183 changes: 166 additions & 17 deletions apstra/blueprint/datacenter_generic_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"regexp"
"sort"

"github.com/Juniper/terraform-provider-apstra/apstra/constants"

"github.com/Juniper/apstra-go-sdk/apstra"
apiversions "github.com/Juniper/terraform-provider-apstra/apstra/api_versions"
"github.com/Juniper/terraform-provider-apstra/apstra/design"
Expand Down Expand Up @@ -521,29 +523,50 @@ func (o *DatacenterGenericSystem) deleteLinksFromSystem(ctx context.Context, lin
return
}

var ace apstra.ClientErr
var pendingDiags diag.Diagnostics

err := bp.DeleteLinksFromSystem(ctx, linkIdsToDelete)
if err == nil {
return // success!
} else {
pendingDiags.AddError(
fmt.Sprintf("failed deleting links %v from generic system %s", linkIdsToDelete, o.Id),
err.Error())
if !(errors.As(err, &ace) && ace.Type() == apstra.ErrCtAssignedToLink && ace.Detail() != nil && o.ClearCtsOnDestroy.ValueBool()) {
// we cannot handle the error
diags.Append(pendingDiags...)
return
}
}

// we got here because some links have CTs attached.
var ace apstra.ClientErr

// see if we can handle this error...
if !errors.As(err, &ace) || ace.Type() != apstra.ErrCtAssignedToLink || ace.Detail() == nil {
// cannot handle error
diags.AddError("failed while deleting Links from Generic System", err.Error())
return
}

// the error detail has to be the correct type...
detail, ok := ace.Detail().(apstra.ErrCtAssignedToLinkDetail)
if !ok {
diags.AddError(
constants.ErrProviderBug+fmt.Sprintf(" - ErrCtAssignedToLink has unexpected detail type: %T", detail),
err.Error(),
)
return
}

// see if the user could have avoided this problem...
if !o.ClearCtsOnDestroy.ValueBool() {
diags.AddWarning(
fmt.Sprintf("Cannot delete links with Connectivity Templates assigned: %v", detail.LinkIds),
"You can set 'clear_cts_on_destroy = true' to override this behavior",
)
return
}

// prep an error diagnostic in case we can't figure this out
var pendingDiags diag.Diagnostics
pendingDiags.AddError(
fmt.Sprintf("failed deleting links %v from generic system %s", linkIdsToDelete, o.Id),
err.Error())

// we got here because some links have CTs attached.
// try to clear the connectivity templates from the problem links
o.ClearConnectivityTemplatesFromLinks(ctx, ace.Detail().(apstra.ErrCtAssignedToLinkDetail).LinkIds, bp, diags)
if diags.HasError() {
diags.Append(pendingDiags...)
diags.Append(pendingDiags...) // throw the pending diagnostic on the pile and give up
return
}

Expand All @@ -552,7 +575,7 @@ func (o *DatacenterGenericSystem) deleteLinksFromSystem(ctx context.Context, lin
if err != nil {
diags.AddError("failed second attempt to delete links after attempting to handle the link deletion error",
err.Error())
diags.Append(pendingDiags...)
diags.Append(pendingDiags...) // throw the pending diagnostic on the pile and give up
return
}
}
Expand Down Expand Up @@ -582,16 +605,142 @@ func (o *DatacenterGenericSystem) updateLinkParams(ctx context.Context, planLink
if len(request) != 0 {
err := bp.SetLinkLagParams(ctx, &request)
if err != nil {
diags.AddError("failed updating generic system link parameters", err.Error()) // collect all errors
// we may be able to figure this out...
var pendingDiags diag.Diagnostics
pendingDiags.AddError("failed updating generic system link parameters", err.Error())

var ace apstra.ClientErr
if !errors.As(err, &ace) || ace.Type() != apstra.ErrLagHasAssignedStructrues || ace.Detail() == nil {
diags.Append(pendingDiags...) // cannot handle error
return
}

detail, ok := ace.Detail().(apstra.ErrLagHasAssignedStructuresDetail)
if !ok || len(detail.GroupLabels) == 0 {
diags.Append(pendingDiags...) // cannot handle error
return
}

var lagIds []apstra.ObjectId
for _, groupLabel := range detail.GroupLabels {
lagId, err := lagLinkIdFromGsIdAndGroupLabel(ctx, bp, apstra.ObjectId(o.Id.ValueString()), groupLabel)
if err != nil {
// return both errors
diags.Append(pendingDiags...)
diags.AddError("failed to determine upstream switch LAG port ID", err.Error())
continue
}

lagIds = append(lagIds, lagId)
}

if !o.ClearCtsOnDestroy.ValueBool() {
diags.Append(pendingDiags...) // cannot handle error
diags.AddWarning(
fmt.Sprintf("Cannot orphan LAGs with Connectivity Templates assigned: %v", lagIds),
"You can set 'clear_cts_on_destroy = true' to override this behavior",
)
return
}

o.ClearConnectivityTemplatesFromLinks(ctx, lagIds, bp, diags)

// try again...
err = bp.SetLinkLagParams(ctx, &request)
if err != nil {
diags.AddError("failed updating generic system LAG parameters after clearing CTs", err.Error()) // cannot handle error
return
}
}
}

// one at a time, check/update each link transform ID
for i, link := range planLinks {
link.updateTransformId(ctx, stateLinks[i], bp, diags) // collect all errors
link.updateTransformId(ctx, stateLinks[i], bp, diags)
}
}

func lagLinkIdFromGsIdAndGroupLabel(ctx context.Context, bp *apstra.TwoStageL3ClosClient, gsId apstra.ObjectId, groupLabel string) (apstra.ObjectId, error) {
query := new(apstra.PathQuery).SetBlueprintId(bp.Id()).SetClient(bp.Client()).
Node([]apstra.QEEAttribute{{Key: "id", Value: apstra.QEStringVal(gsId.String())}}).
Out([]apstra.QEEAttribute{apstra.RelationshipTypeHostedInterfaces.QEEAttribute()}).
Node([]apstra.QEEAttribute{
apstra.NodeTypeInterface.QEEAttribute(),
{Key: "if_type", Value: apstra.QEStringVal("port_channel")},
}).
Out([]apstra.QEEAttribute{apstra.RelationshipTypeLink.QEEAttribute()}).
Node([]apstra.QEEAttribute{
apstra.NodeTypeLink.QEEAttribute(),
{Key: "group_label", Value: apstra.QEStringVal(groupLabel)},
{Key: "link_type", Value: apstra.QEStringVal("aggregate_link")},
{Key: "name", Value: apstra.QEStringVal("n_link")},
})

var result struct {
Items []struct {
Link struct {
Id apstra.ObjectId `json:"id"`
} `json:"n_link"`
} `json:"items"`
}

if err := query.Do(ctx, &result); err != nil {
return "", err
}

switch len(result.Items) {
case 0:
return "", fmt.Errorf("query failed to find LAG link ID for system %q group label %q - %s", gsId, groupLabel, query.String())
case 1:
return result.Items[0].Link.Id, nil
default:
return "", fmt.Errorf("query found multiple find LAG link IDs for system %q group label %q - %s", gsId, groupLabel, query.String())
}
}

//func switchLagIdFromGsIdAndGroupLabel(ctx context.Context, bp *apstra.TwoStageL3ClosClient, gsId apstra.ObjectId, groupLabel string) (apstra.ObjectId, error) {
// query := new(apstra.PathQuery).SetBlueprintId(bp.Id()).SetClient(bp.Client()).
// Node([]apstra.QEEAttribute{{Key: "id", Value: apstra.QEStringVal(gsId.String())}}).
// Out([]apstra.QEEAttribute{apstra.RelationshipTypeHostedInterfaces.QEEAttribute()}).
// Node([]apstra.QEEAttribute{
// apstra.NodeTypeInterface.QEEAttribute(),
// {Key: "if_type", Value: apstra.QEStringVal("port_channel")},
// }).
// Out([]apstra.QEEAttribute{apstra.RelationshipTypeLink.QEEAttribute()}).
// Node([]apstra.QEEAttribute{
// apstra.NodeTypeLink.QEEAttribute(),
// {Key: "group_label", Value: apstra.QEStringVal(groupLabel)},
// {Key: "link_type", Value: apstra.QEStringVal("aggregate_link")},
// }).
// In([]apstra.QEEAttribute{apstra.RelationshipTypeLink.QEEAttribute()}).
// Node([]apstra.QEEAttribute{
// apstra.NodeTypeInterface.QEEAttribute(),
// {Key: "if_type", Value: apstra.QEStringVal("port_channel")},
// {Key: "name", Value: apstra.QEStringVal("n_application_point")},
// })
//
// var result struct {
// Items []struct {
// ApplicationPoint struct {
// Id apstra.ObjectId `json:"id"`
// } `json:"n_application_point"`
// } `json:"items"`
// }
//
// if err := query.Do(ctx, &result); err != nil {
// return "", err
// }
//
// switch len(result.Items) {
// case 0:
// return "", fmt.Errorf("query failed to find upstream interface ID for system %q group label %q - %s", gsId, groupLabel, query.String())
// case 1:
// return result.Items[0].ApplicationPoint.Id, nil
// default:
// return "", fmt.Errorf("query found multiple find upstream interface IDs for system %q group label %q - %s", gsId, groupLabel, query.String())
// }
//}

// linkIds performs the graph queries necessary to return the link IDs which
// connect this Generic System (o) to the systems+interfaces specified by links.
func (o *DatacenterGenericSystem) linkIds(ctx context.Context, links []*DatacenterGenericSystemLink, bp *apstra.TwoStageL3ClosClient, diags *diag.Diagnostics) []apstra.ObjectId {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ toolchain go1.21.1

require (
github.com/IBM/netaddr v1.5.0
github.com/Juniper/apstra-go-sdk v0.0.0-20240422203629-497c16d7c591
github.com/Juniper/apstra-go-sdk v0.0.0-20240426215444-33c962e3c277
github.com/chrismarget-j/go-licenses v0.0.0-20240224210557-f22f3e06d3d4
github.com/google/go-cmp v0.6.0
github.com/goreleaser/goreleaser v1.23.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/IBM/netaddr v1.5.0 h1:IJlFZe1+nFs09TeMB/HOP4+xBnX2iM/xgiDOgZgTJq0=
github.com/IBM/netaddr v1.5.0/go.mod h1:DDBPeYgbFzoXHjSz9Jwk7K8wmWV4+a/Kv0LqRnb8we4=
github.com/Juniper/apstra-go-sdk v0.0.0-20240422203629-497c16d7c591 h1:lsYejC4zGdzv+c4dWihuVCNKVtHrFU3DshAmjGWAuPk=
github.com/Juniper/apstra-go-sdk v0.0.0-20240422203629-497c16d7c591/go.mod h1:Xwj3X8v/jRZWv28o6vQAqD4lz2JmzaSYLZ2ch1SS89w=
github.com/Juniper/apstra-go-sdk v0.0.0-20240426215444-33c962e3c277 h1:2h6VCKACnTqVFmYI0OgC77CvhLd9nTMdK1NDwUDA4ks=
github.com/Juniper/apstra-go-sdk v0.0.0-20240426215444-33c962e3c277/go.mod h1:Xwj3X8v/jRZWv28o6vQAqD4lz2JmzaSYLZ2ch1SS89w=
github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0=
github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
Expand Down

0 comments on commit 0285d98

Please sign in to comment.