From 23c55ee639b80e4a9ddf6f5a35efa70461b2307a Mon Sep 17 00:00:00 2001 From: MegaportPhilipBrowne Date: Wed, 25 Sep 2024 11:15:59 -0400 Subject: [PATCH] feat: support checking VLAN availability of port --- errors.go | 3 ++ port.go | 48 ++++++++++++++++++++++ port_types.go | 6 +++ vxc_integration_test.go | 90 ++++++++++++++++++++++++++++++++++++++--- 4 files changed, 142 insertions(+), 5 deletions(-) diff --git a/errors.go b/errors.go index 94b92db..88c6c33 100644 --- a/errors.go +++ b/errors.go @@ -63,3 +63,6 @@ var ErrCostCentreTooLong = errors.New("cost centre must be less than 255 charact // ErrManagedAccountNotFound is returned when a managed account can't be found var ErrManagedAccountNotFound = errors.New("managed account not found") + +// ErrInvalidVLAN is returned when a VLAN is invalid +var ErrInvalidVLAN = errors.New("invalid VLAN, must be between 0 and 4094") diff --git a/port.go b/port.go index a70d34b..4f97461 100644 --- a/port.go +++ b/port.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "net/url" "slices" "time" ) @@ -30,6 +31,8 @@ type PortService interface { LockPort(ctx context.Context, portId string) (*LockPortResponse, error) // UnlockPort unlocks a port in the Megaport Port API. UnlockPort(ctx context.Context, portId string) (*UnlockPortResponse, error) + // CheckPortVLANAvailability checks if a VLAN is available on a port in the Megaport Products API. + CheckPortVLANAvailability(ctx context.Context, portId string, vlan int) (bool, error) } // NewPortService creates a new instance of the Port Service. @@ -432,3 +435,48 @@ func (svc *PortServiceOp) UnlockPort(ctx context.Context, portId string) (*Unloc return nil, ErrPortNotLocked } } + +func (svc *PortServiceOp) CheckPortVLANAvailability(ctx context.Context, portId string, vlan int) (bool, error) { + if vlan < 0 || vlan > 4094 { + return false, ErrInvalidVLAN + } + + _, err := svc.GetPort(ctx, portId) + if err != nil { + return false, err + } + + path := "/v2/product/port/" + portId + "/vlan" + params := url.Values{} + params.Add("vlan", fmt.Sprintf("%d", vlan)) + url := svc.Client.BaseURL.JoinPath(path) + url.RawQuery = params.Encode() + urlStr := url.String() + + clientReq, err := svc.Client.NewRequest(ctx, http.MethodGet, urlStr, nil) + if err != nil { + return false, err + } + + response, err := svc.Client.Do(ctx, clientReq, nil) + if err != nil { + return false, err + } + defer response.Body.Close() + + body, fileErr := io.ReadAll(response.Body) + if fileErr != nil { + return false, err + } + + vlanResponse := PortVLANAvailabilityAPIResponse{} + unmarshalErr := json.Unmarshal(body, &vlanResponse) + if unmarshalErr != nil { + return false, unmarshalErr + } + + if slices.Contains(vlanResponse.Data, vlan) { + return true, nil + } + return false, nil +} diff --git a/port_types.go b/port_types.go index 2589702..dc1c090 100644 --- a/port_types.go +++ b/port_types.go @@ -142,3 +142,9 @@ type PortTerminatedServiceDetailsInterface struct { Up int `json:"up"` Shutdown bool `json:"shutdown"` } + +type PortVLANAvailabilityAPIResponse struct { + Message string `json:"message"` + Terms string `json:"terms"` + Data []int `json:"data"` +} diff --git a/vxc_integration_test.go b/vxc_integration_test.go index 849b8e5..f9bfbcd 100644 --- a/vxc_integration_test.go +++ b/vxc_integration_test.go @@ -122,6 +122,39 @@ func (suite *VXCIntegrationTestSuite) TestVXCBuy() { } serviceKeyID := serviceKeyRes.ServiceKeyUID + var aEndVLAN, bEndVLAN int + var aEndAvailable, bEndAvailable bool + var err error + + for i := 0; i < 10; i++ { + aEndVLAN = GenerateRandomVLAN() + aEndAvailable, err = portSvc.CheckPortVLANAvailability(ctx, aEndUid, aEndVLAN) + if err != nil { + suite.FailNowf("cannot check a end vlan availability", "cannot check a end vlan availability %v", err) + } + if aEndAvailable { + break + } + } + + if !aEndAvailable { + suite.FailNowf("a end vlan not available after 10 attempts", "a end vlan %d is not available after 10 attempts", aEndVLAN) + } + + for i := 0; i < 10; i++ { + bEndVLAN = GenerateRandomVLAN() + bEndAvailable, err = portSvc.CheckPortVLANAvailability(ctx, bEndUid, bEndVLAN) + if err != nil { + suite.FailNowf("cannot check b end vlan availability", "cannot check b end vlan availability %v", err) + } + if bEndAvailable { + break + } + } + if !bEndAvailable { + suite.FailNowf("b end vlan not available after 10 attempts", "b end vlan %d is not available after 10 attempts", bEndVLAN) + } + logger.InfoContext(ctx, "buying vxc") buyVxcRes, vxcErr := vxcSvc.BuyVXC(ctx, &BuyVXCRequest{ @@ -132,10 +165,10 @@ func (suite *VXCIntegrationTestSuite) TestVXCBuy() { Shutdown: false, ServiceKey: serviceKeyID, AEndConfiguration: VXCOrderEndpointConfiguration{ - VLAN: GenerateRandomVLAN(), + VLAN: aEndVLAN, }, BEndConfiguration: VXCOrderEndpointConfiguration{ - VLAN: GenerateRandomVLAN(), + VLAN: bEndVLAN, ProductUID: bEndUid, }, WaitForProvision: true, @@ -147,11 +180,41 @@ func (suite *VXCIntegrationTestSuite) TestVXCBuy() { vxcUid := buyVxcRes.TechnicalServiceUID suite.True(IsGuid(vxcUid), "invalid guid for vxc uid") - newAVLAN := GenerateRandomVLAN() - newBVLAN := GenerateRandomVLAN() + var newAEndAvailable, newBEndAvailable bool + var newAVLAN, newBVLAN int + newCostCentre := "Test Cost Centre 2" newTerm := 24 + for i := 0; i < 10; i++ { + newAVLAN = GenerateRandomVLAN() + newAEndAvailable, err = portSvc.CheckPortVLANAvailability(ctx, aEndUid, newAVLAN) + if err != nil { + suite.FailNowf("cannot check new a end vlan availability", "cannot check new a end vlan availability %v", err) + } + if newAEndAvailable { + break + } + } + if !newAEndAvailable { + suite.FailNowf("new a end vlan not available after 10 attempts", "new a end vlan %d is not available after 10 attempts", newAVLAN) + } + + for i := 0; i < 10; i++ { + newBVLAN = GenerateRandomVLAN() + newBEndAvailable, err = portSvc.CheckPortVLANAvailability(ctx, bEndUid, newBVLAN) + if err != nil { + suite.FailNowf("cannot check new b end vlan availability", "cannot check new b end vlan availability %v", err) + } + if newBEndAvailable { + break + } + } + + if !newBEndAvailable { + suite.FailNowf("new b end vlan not available after 10 attempts", "new b end vlan %d is not available after 10 attempts", newBVLAN) + } + updateRes, updateErr := vxcSvc.UpdateVXC(ctx, vxcUid, &UpdateVXCRequest{ AEndVLAN: &newAVLAN, BEndVLAN: &newBVLAN, @@ -409,6 +472,23 @@ func (suite *VXCIntegrationTestSuite) TestAWSVIFConnectionBuy() { logger.InfoContext(ctx, "buying aws vif connection (b-end)") + var aEndVLAN int + var vlanAvailable bool + var err error + for i := 0; i < 10; i++ { + aEndVLAN = GenerateRandomVLAN() + vlanAvailable, err = portSvc.CheckPortVLANAvailability(ctx, portUid, aEndVLAN) + if err != nil { + suite.FailNowf("cannot check vlan availability", "cannot check vlan availability %v", err) + } + if vlanAvailable { + break + } + } + if !vlanAvailable { + suite.FailNowf("vlan not available after 10 attempts", "vlan %d is not available after 10 attempts", aEndVLAN) + } + buyVxcRes, vxcErr := vxcSvc.BuyVXC(ctx, &BuyVXCRequest{ PortUID: portUid, VXCName: "Hosted AWS VIF Test Connection", @@ -416,7 +496,7 @@ func (suite *VXCIntegrationTestSuite) TestAWSVIFConnectionBuy() { Term: 1, Shutdown: false, AEndConfiguration: VXCOrderEndpointConfiguration{ - VLAN: GenerateRandomVLAN(), + VLAN: aEndVLAN, }, BEndConfiguration: VXCOrderEndpointConfiguration{ ProductUID: "87860c28-81ef-4e79-8cc7-cfc5a4c4bc86",