Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 9 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
)
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
12 changes: 6 additions & 6 deletions service/vxc/partner.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ package vxc
import (
"encoding/json"
"errors"
"io/ioutil"
"io"
"strings"

"github.com/megaport/megaportgo/mega_err"
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
203 changes: 148 additions & 55 deletions service/vxc/vxc.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
package vxc

import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"strings"
"time"

Expand Down Expand Up @@ -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
}
Expand All @@ -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{}
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down