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..5aff8f2 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,9 @@ 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= 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= 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..e849bb7 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{} @@ -146,78 +148,163 @@ 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, 30*time.Second, vxcId) +} + +// 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) - 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 + 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 + } - 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) + 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) + 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) + } } } } +// 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, 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, pollFrequency time.Duration, 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) + 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 - } + 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() + } - 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.logUpdateStatus("VXC wait cycle complete", name, rateLimit, aEndVLAN, bEndVLAN, vxcDetails) + return false, errors.New(mega_err.ERR_VXC_UPDATE_TIMEOUT_EXCEED) + } - if vxcDetails.Name == name && vxcDetails.RateLimit == rateLimit && vxcDetails.AEndConfiguration.VLAN == aEndVLAN && vxcDetails.BEndConfiguration.VLAN == bEndVLAN { - hasUpdated = true + if wait%5 == 0 { + 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 + } - vxcDetails, _ := v.GetVXCDetails(id) - v.Log.Debugf("VXC wait cyclecomplete: Name %t; RateLimit %t; AEndVLAN %t; BEndVLAN %t\n", + 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) + vxcDetails.BEndConfiguration.VLAN == bEndVLAN, + ) +} - if wait >= 30 { - return false, errors.New(mega_err.ERR_VXC_UPDATE_TIMEOUT_EXCEED) - } else { - return true, nil +func isUpdated( + name string, + rateLimit int, + aEndVLAN int, + bEndVLAN int, + vxcDetails types.VXC, +) bool { + if aEndVLAN == 0 { + aEndVLAN = vxcDetails.AEndConfiguration.VLAN } + + 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 +319,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 +391,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 +460,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 {