From 5db89538eac5254cae6f836109d8b1006e53c87b Mon Sep 17 00:00:00 2001 From: Dave Setzke Date: Wed, 17 May 2023 13:50:32 -0600 Subject: [PATCH 1/3] Update to go 1.20 --- README.md | 2 +- go.mod | 11 +++++++++-- go.sum | 2 ++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9de896f..647f348 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Tests can be executed for this library by running `make integration` to run all * port-integ * vxc-integ -In order to run theses tests valid user Credentials will need to be provided as per the Credentials section below. +In order to run these tests valid user Credentials will need to be provided as per the Credentials section below. ### Credentials For the purposes of testing Megaport Credentials can be passed to the integration tests by setting the following environment variables: diff --git a/go.mod b/go.mod index f47d4cf..cf6dff7 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,17 @@ module github.com/megaport/megaportgo -go 1.13 +go 1.20 require ( github.com/lithammer/fuzzysearch v1.1.5 - github.com/stretchr/objx v0.4.0 // indirect github.com/stretchr/testify v1.7.2 github.com/xlzd/gotp v0.0.0-20220110052318-fab697c03c2c ) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.4.0 // indirect + golang.org/x/text v0.3.8 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index ad6d5a5..d6d44ed 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/xlzd/gotp v0.0.0-20220110052318-fab697c03c2c h1:LZpKQbMSngtN4ycCtogkx github.com/xlzd/gotp v0.0.0-20220110052318-fab697c03c2c/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From c03ecf92a26cd8cf85f55b1ca5108135d8e2dd0d Mon Sep 17 00:00:00 2001 From: Dave Setzke Date: Wed, 17 May 2023 13:50:42 -0600 Subject: [PATCH 2/3] Add context to VXC WaitFor* functions Using a context allows for the caller to specify the timeouts it is willing to accept, and also pass through user cancellation to better manage resources. Corrects a couple godocs and go fmt / compiler warnings --- service/vxc/partner.go | 12 +-- service/vxc/vxc.go | 184 ++++++++++++++++++++++++++++------------- 2 files changed, 132 insertions(+), 64 deletions(-) diff --git a/service/vxc/partner.go b/service/vxc/partner.go index 96bc6db..5a01750 100644 --- a/service/vxc/partner.go +++ b/service/vxc/partner.go @@ -17,7 +17,7 @@ package vxc import ( "encoding/json" "errors" - "io/ioutil" + "io" "strings" "github.com/megaport/megaportgo/mega_err" @@ -33,14 +33,14 @@ const PARTNER_AWS string = "AWS" func (v *VXC) LookupPartnerPorts(key string, portSpeed int, partner string, requestedProductID string) (string, error) { lookupUrl := "/v2/secure/" + strings.ToLower(partner) + "/" + key response, resErr := v.Config.MakeAPICall("GET", lookupUrl, nil) - defer response.Body.Close() isErr, compiledErr := v.Config.IsErrorResponse(response, &resErr, 200) if isErr { return "", compiledErr } + defer func(Body io.ReadCloser) { _ = Body.Close() }(response.Body) - body, fileErr := ioutil.ReadAll(response.Body) + body, fileErr := io.ReadAll(response.Body) if fileErr != nil { return "", fileErr @@ -68,7 +68,7 @@ func (v *VXC) LookupPartnerPorts(key string, portSpeed int, partner string, requ return "", errors.New(mega_err.ERR_NO_AVAILABLE_VXC_PORTS) } -// BuyAWSVXC buys an AWS VXC. +// BuyPartnerVXC buys a partner VXC. func (v *VXC) BuyPartnerVXC( portUID string, vxcName string, @@ -109,8 +109,8 @@ func (v *VXC) BuyPartnerVXC( return orderInfo.Data[0].TechnicalServiceUID, nil } -// BuyPartnerVXC performs Step 2 of the partner port purchase process. These are for partners that require some kind -// of partner pairing key (e.g. GCP, Azure). +// MarshallPartnerConfig performs Step 2 of the partner port purchase process. These are for partners that require +// some kind of partner pairing key (e.g. GCP, Azure). func (v *VXC) MarshallPartnerConfig( key string, partner string, diff --git a/service/vxc/vxc.go b/service/vxc/vxc.go index 2861ada..4f74f6f 100644 --- a/service/vxc/vxc.go +++ b/service/vxc/vxc.go @@ -15,10 +15,11 @@ package vxc import ( + "context" "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "strings" "time" @@ -84,14 +85,14 @@ func (v *VXC) BuyVXC( func (v *VXC) GetVXCDetails(id string) (types.VXC, error) { url := "/v2/product/" + id response, err := v.Config.MakeAPICall("GET", url, nil) - defer response.Body.Close() if err != nil { return types.VXC{}, err } - body, fileErr := ioutil.ReadAll(response.Body) + defer func(Body io.ReadCloser) { _ = Body.Close() }(response.Body) + body, fileErr := io.ReadAll(response.Body) if fileErr != nil { return types.VXC{}, fileErr } @@ -106,11 +107,12 @@ func (v *VXC) GetVXCDetails(id string) (types.VXC, error) { return vxcDetails.Data, nil } -// GetVXCDetails deletes a VXC. +// DeleteVXC deletes a VXC. func (v *VXC) DeleteVXC(id string, deleteNow bool) (bool, error) { return v.product.DeleteProduct(id, deleteNow) } +// UpdateVXC updates a VXC func (v *VXC) UpdateVXC(id string, name string, rateLimit int, aEndVLAN int, bEndVLAN int) (bool, error) { url := fmt.Sprintf("/v2/product/%s/%s", types.PRODUCT_VXC, id) var update interface{} @@ -147,77 +149,137 @@ func (v *VXC) UpdateVXC(id string, name string, rateLimit int, aEndVLAN int, bEn } func (v *VXC) WaitForVXCProvisioning(vxcId string) (bool, error) { + ctx, cancelFunc := context.WithTimeout(context.Background(), 15*time.Minute) + defer cancelFunc() + return v.WaitForVXCProvisioningCtx(ctx, vxcId) +} + +func (v *VXC) WaitForVXCProvisioningCtx(ctx context.Context, vxcId string) (bool, error) { vxcInfo, _ := v.GetVXCDetails(vxcId) - wait := 0 + if strings.Compare(vxcInfo.ProvisioningStatus, "LIVE") != 0 { + return true, nil + } // Go-Live v.Log.Info("Waiting for VXC status transition.") - for strings.Compare(vxcInfo.ProvisioningStatus, "LIVE") != 0 && wait < 30 { - time.Sleep(30 * time.Second) + + wait := 0 + pollFrequency := 30 * time.Second + timer := time.NewTicker(pollFrequency) + defer timer.Stop() + for { wait++ - vxcInfo, _ = v.GetVXCDetails(vxcId) - if wait%5 == 0 { - v.Log.Infoln("VXC is currently being provisioned. Status: ", vxcInfo.ProvisioningStatus) - } - } + select { - vxcInfo, _ = v.GetVXCDetails(vxcId) - v.Log.Debugln("VXC waiting cycle complete. Status: ", vxcInfo.ProvisioningStatus) + case <-ctx.Done(): + vxcInfo, _ = v.GetVXCDetails(vxcId) + if strings.Compare(vxcInfo.ProvisioningStatus, "LIVE") != 0 { + return true, nil + } + return false, ctx.Err() - if vxcInfo.ProvisioningStatus == "LIVE" { - return true, nil - } else { - if wait >= 30 { - return false, errors.New(mega_err.ERR_VXC_PROVISION_TIMEOUT_EXCEED) - } else { - return false, errors.New(mega_err.ERR_VXC_NOT_LIVE) + case t, ok := <-timer.C: + vxcInfo, _ = v.GetVXCDetails(vxcId) + if strings.Compare(vxcInfo.ProvisioningStatus, "LIVE") != 0 { + return true, nil + } + + deadline, hasDeadline := ctx.Deadline() + if !ok || (hasDeadline && t.After(deadline)) { + v.Log.Debugln("VXC waiting cycle complete. Status: ", vxcInfo.ProvisioningStatus) + return false, errors.New(mega_err.ERR_VXC_PROVISION_TIMEOUT_EXCEED) + } + + if wait%5 == 0 { + v.Log.Infoln("VXC is currently being provisioned. Status: ", vxcInfo.ProvisioningStatus) + } } } } func (v *VXC) WaitForVXCUpdated(id string, name string, rateLimit int, aEndVLAN int, bEndVLAN int) (bool, error) { + ctx, cancelFunc := context.WithTimeout(context.Background(), 15*time.Minute) + defer cancelFunc() + return v.WaitForVXCUpdatedCtx(ctx, id, name, rateLimit, aEndVLAN, bEndVLAN) +} + +func (v *VXC) WaitForVXCUpdatedCtx( + ctx context.Context, id string, name string, rateLimit int, aEndVLAN int, bEndVLAN int, +) (bool, error) { wait := 0 - hasUpdated := false - for !hasUpdated && wait < 30 { - time.Sleep(30 * time.Second) + pollFrequency := 30 * time.Second + timer := time.NewTicker(pollFrequency) + defer timer.Stop() + for { wait++ - vxcDetails, _ := v.GetVXCDetails(id) + select { + case <-ctx.Done(): + vxcDetails, _ := v.GetVXCDetails(id) + if isUpdated(name, rateLimit, aEndVLAN, bEndVLAN, vxcDetails) { + return true, nil + } - if aEndVLAN == 0 { - aEndVLAN = vxcDetails.AEndConfiguration.VLAN - } + // TODO: error here is going to be Canceled if the context is canceled, + // Timed out if context times out externally. Should we force it to + // mega_err.ERR_VXC_UPDATE_TIMEOUT_EXCEED or similar? + return false, ctx.Err() - if bEndVLAN == 0 { - bEndVLAN = vxcDetails.BEndConfiguration.VLAN - } + case t, ok := <-timer.C: + vxcDetails, _ := v.GetVXCDetails(id) + if isUpdated(name, rateLimit, aEndVLAN, bEndVLAN, vxcDetails) { + return true, nil + } - if wait%5 == 0 { - v.Log.Debugf("VXC Update in progress: Name %t; RateLimit %t; AEndVLAN %t; BEndVLAN %t\n", - vxcDetails.Name == name, - vxcDetails.RateLimit == rateLimit, - vxcDetails.AEndConfiguration.VLAN == aEndVLAN, - vxcDetails.BEndConfiguration.VLAN == bEndVLAN) - } + deadline, hasDeadline := ctx.Deadline() + if !ok || (hasDeadline && t.After(deadline)) { + v.Log.Debugln("VXC waiting cycle complete. Status: ", vxcDetails.ProvisioningStatus) + return false, errors.New(mega_err.ERR_VXC_PROVISION_TIMEOUT_EXCEED) + } + + if wait%5 == 0 { + if aEndVLAN == 0 { + aEndVLAN = vxcDetails.AEndConfiguration.VLAN + } - if vxcDetails.Name == name && vxcDetails.RateLimit == rateLimit && vxcDetails.AEndConfiguration.VLAN == aEndVLAN && vxcDetails.BEndConfiguration.VLAN == bEndVLAN { - hasUpdated = true + if bEndVLAN == 0 { + bEndVLAN = vxcDetails.BEndConfiguration.VLAN + } + v.Log.Debugf( + "VXC Update in progress: Name %t; RateLimit %t; AEndVLAN %t; BEndVLAN %t\n", + vxcDetails.Name == name, + vxcDetails.RateLimit == rateLimit, + vxcDetails.AEndConfiguration.VLAN == aEndVLAN, + vxcDetails.BEndConfiguration.VLAN == bEndVLAN, + ) + } } } +} - vxcDetails, _ := v.GetVXCDetails(id) - v.Log.Debugf("VXC wait cyclecomplete: Name %t; RateLimit %t; AEndVLAN %t; BEndVLAN %t\n", - vxcDetails.Name == name, - vxcDetails.RateLimit == rateLimit, - vxcDetails.AEndConfiguration.VLAN == aEndVLAN, - vxcDetails.BEndConfiguration.VLAN == bEndVLAN) +func isUpdated( + name string, + rateLimit int, + aEndVLAN int, + bEndVLAN int, + vxcDetails types.VXC, +) bool { + if aEndVLAN == 0 { + aEndVLAN = vxcDetails.AEndConfiguration.VLAN + } - if wait >= 30 { - return false, errors.New(mega_err.ERR_VXC_UPDATE_TIMEOUT_EXCEED) - } else { - return true, nil + if bEndVLAN == 0 { + bEndVLAN = vxcDetails.BEndConfiguration.VLAN + } + + if vxcDetails.Name == name && + vxcDetails.RateLimit == rateLimit && + vxcDetails.AEndConfiguration.VLAN == aEndVLAN && + vxcDetails.BEndConfiguration.VLAN == bEndVLAN { + return true } + return false } func (v *VXC) UnmarshallMcrAEndConfig(vxcDetails types.VXC) (interface{}, error) { @@ -232,7 +294,7 @@ func (v *VXC) UnmarshallMcrAEndConfig(vxcDetails types.VXC) (interface{}, error) // handle more than one interface if len(partner_interfaces) != 1 { v.Log.Warn("More than one interface present in MCR A end Resource") - return nil, errors.New("More than one interface present in MCR A end Resource") + return nil, errors.New("more than one interface present in MCR A end Resource") } for _, partner_interface := range partner_interfaces { @@ -304,11 +366,13 @@ func (v *VXC) UnmarshallMcrAEndConfig(vxcDetails types.VXC) (interface{}, error) v.Log.Info(" - bfd field present") // add bfd to configuration - partner_configuration["bfd_configuration"] = []interface{}{map[string]interface{}{ - "tx_interval": bfd_map["txInterval"], - "rx_interval": bfd_map["rxInterval"], - "multiplier": bfd_map["multiplier"], - }} + partner_configuration["bfd_configuration"] = []interface{}{ + map[string]interface{}{ + "tx_interval": bfd_map["txInterval"], + "rx_interval": bfd_map["rxInterval"], + "multiplier": bfd_map["multiplier"], + }, + } } else { v.Log.Info(" - bfd field not present") @@ -371,9 +435,13 @@ func (v *VXC) UnmarshallMcrAEndConfig(vxcDetails types.VXC) (interface{}, error) return nil, nil } -func (v *VXC) GetCspConnection(cspIdentifier string, cspIdentifierValue string, vxcDetails types.VXC) map[string]interface{} { +func (v *VXC) GetCspConnection( + cspIdentifier string, + cspIdentifierValue string, + vxcDetails types.VXC, +) map[string]interface{} { - v.Log.Info("searching for csp where " + cspIdentifier + "=" + cspIdentifierValue) + v.Log.Info("searching for csp where " + cspIdentifier + "=" + cspIdentifierValue) cspConnectionList := []map[string]interface{}{} if cspConnectionListInner, ok := vxcDetails.Resources.CspConnection.([]interface{}); ok { From 33e752a3af41026c879c44760edc51f0d1232efd Mon Sep 17 00:00:00 2001 From: Dave Setzke Date: Wed, 17 May 2023 22:25:55 -0600 Subject: [PATCH 3/3] Move poll interval to parameter; cleanup --- go.sum | 1 - service/vxc/vxc.go | 79 ++++++++++++++++++++++++++++++---------------- 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/go.sum b/go.sum index d6d44ed..5aff8f2 100644 --- a/go.sum +++ b/go.sum @@ -13,7 +13,6 @@ github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/xlzd/gotp v0.0.0-20220110052318-fab697c03c2c h1:LZpKQbMSngtN4ycCtogkxYl5ec0FimAA8rSrI4ZMGTM= github.com/xlzd/gotp v0.0.0-20220110052318-fab697c03c2c/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= diff --git a/service/vxc/vxc.go b/service/vxc/vxc.go index 4f74f6f..e849bb7 100644 --- a/service/vxc/vxc.go +++ b/service/vxc/vxc.go @@ -148,13 +148,17 @@ func (v *VXC) UpdateVXC(id string, name string, rateLimit int, aEndVLAN int, bEn } } +// WaitForVXCProvisioning waits up to 15 minutes for the VXC to reach the "LIVE" status. +// See WaitForVXCProvisioningCtx func (v *VXC) WaitForVXCProvisioning(vxcId string) (bool, error) { ctx, cancelFunc := context.WithTimeout(context.Background(), 15*time.Minute) defer cancelFunc() - return v.WaitForVXCProvisioningCtx(ctx, vxcId) + return v.WaitForVXCProvisioningCtx(ctx, 30*time.Second, vxcId) } -func (v *VXC) WaitForVXCProvisioningCtx(ctx context.Context, vxcId string) (bool, error) { +// WaitForVXCProvisioningCtx waits for the VXC to reach the "LIVE" status, retrying every [pollFrequency] +// seconds until the "LIVE" status is reached or the specified context expires or is canceled. +func (v *VXC) WaitForVXCProvisioningCtx(ctx context.Context, pollFrequency time.Duration, vxcId string) (bool, error) { vxcInfo, _ := v.GetVXCDetails(vxcId) if strings.Compare(vxcInfo.ProvisioningStatus, "LIVE") != 0 { return true, nil @@ -164,7 +168,6 @@ func (v *VXC) WaitForVXCProvisioningCtx(ctx context.Context, vxcId string) (bool v.Log.Info("Waiting for VXC status transition.") wait := 0 - pollFrequency := 30 * time.Second timer := time.NewTicker(pollFrequency) defer timer.Stop() for { @@ -177,7 +180,12 @@ func (v *VXC) WaitForVXCProvisioningCtx(ctx context.Context, vxcId string) (bool if strings.Compare(vxcInfo.ProvisioningStatus, "LIVE") != 0 { return true, nil } - return false, ctx.Err() + + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return false, errors.New(mega_err.ERR_VXC_PROVISION_TIMEOUT_EXCEED) + } else { + return false, ctx.Err() + } case t, ok := <-timer.C: vxcInfo, _ = v.GetVXCDetails(vxcId) @@ -198,18 +206,21 @@ func (v *VXC) WaitForVXCProvisioningCtx(ctx context.Context, vxcId string) (bool } } +// WaitForVXCUpdated waits up to 15 minutes for the VXC update to get applied. +// See WaitForVXCUpdatedCtx func (v *VXC) WaitForVXCUpdated(id string, name string, rateLimit int, aEndVLAN int, bEndVLAN int) (bool, error) { ctx, cancelFunc := context.WithTimeout(context.Background(), 15*time.Minute) defer cancelFunc() - return v.WaitForVXCUpdatedCtx(ctx, id, name, rateLimit, aEndVLAN, bEndVLAN) + return v.WaitForVXCUpdatedCtx(ctx, 30*time.Second, id, name, rateLimit, aEndVLAN, bEndVLAN) } +// WaitForVXCUpdatedCtx waits for the VXC update to be applied successfully, retrying every [pollFrequency] +// seconds until the update completes or the specified context expires or is canceled. func (v *VXC) WaitForVXCUpdatedCtx( - ctx context.Context, id string, name string, rateLimit int, aEndVLAN int, bEndVLAN int, + ctx context.Context, pollFrequency time.Duration, id string, name string, rateLimit int, aEndVLAN int, bEndVLAN int, ) (bool, error) { wait := 0 - pollFrequency := 30 * time.Second timer := time.NewTicker(pollFrequency) defer timer.Stop() for { @@ -221,10 +232,12 @@ func (v *VXC) WaitForVXCUpdatedCtx( return true, nil } - // TODO: error here is going to be Canceled if the context is canceled, - // Timed out if context times out externally. Should we force it to - // mega_err.ERR_VXC_UPDATE_TIMEOUT_EXCEED or similar? - return false, ctx.Err() + v.logUpdateStatus("VXC wait cycle complete", name, rateLimit, aEndVLAN, bEndVLAN, vxcDetails) + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return false, errors.New(mega_err.ERR_VXC_UPDATE_TIMEOUT_EXCEED) + } else { + return false, ctx.Err() + } case t, ok := <-timer.C: vxcDetails, _ := v.GetVXCDetails(id) @@ -234,30 +247,42 @@ func (v *VXC) WaitForVXCUpdatedCtx( deadline, hasDeadline := ctx.Deadline() if !ok || (hasDeadline && t.After(deadline)) { - v.Log.Debugln("VXC waiting cycle complete. Status: ", vxcDetails.ProvisioningStatus) - return false, errors.New(mega_err.ERR_VXC_PROVISION_TIMEOUT_EXCEED) + v.logUpdateStatus("VXC wait cycle complete", name, rateLimit, aEndVLAN, bEndVLAN, vxcDetails) + return false, errors.New(mega_err.ERR_VXC_UPDATE_TIMEOUT_EXCEED) } if wait%5 == 0 { - if aEndVLAN == 0 { - aEndVLAN = vxcDetails.AEndConfiguration.VLAN - } - - if bEndVLAN == 0 { - bEndVLAN = vxcDetails.BEndConfiguration.VLAN - } - v.Log.Debugf( - "VXC Update in progress: Name %t; RateLimit %t; AEndVLAN %t; BEndVLAN %t\n", - vxcDetails.Name == name, - vxcDetails.RateLimit == rateLimit, - vxcDetails.AEndConfiguration.VLAN == aEndVLAN, - vxcDetails.BEndConfiguration.VLAN == bEndVLAN, - ) + v.logUpdateStatus("VXC Update in progress", name, rateLimit, aEndVLAN, bEndVLAN, vxcDetails) } } } } +func (v *VXC) logUpdateStatus( + prefix string, + name string, + rateLimit int, + aEndVLAN int, + bEndVLAN int, + vxcDetails types.VXC, +) { + if aEndVLAN == 0 { + aEndVLAN = vxcDetails.AEndConfiguration.VLAN + } + + if bEndVLAN == 0 { + bEndVLAN = vxcDetails.BEndConfiguration.VLAN + } + v.Log.Debugf( + "%s: Name %t; RateLimit %t; AEndVLAN %t; BEndVLAN %t\n", + prefix, + vxcDetails.Name == name, + vxcDetails.RateLimit == rateLimit, + vxcDetails.AEndConfiguration.VLAN == aEndVLAN, + vxcDetails.BEndConfiguration.VLAN == bEndVLAN, + ) +} + func isUpdated( name string, rateLimit int,