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
3 changes: 3 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ type Client struct {
ManagedAccountService ManagedAccountService
// IXService provides methods for interacting with the IX API
IXService IXService
// EventsService provides methods for interacting with the Events API
EventsService EventsService

accessToken string // Access Token for client
tokenExpiry time.Time // Token Expiration
Expand Down Expand Up @@ -169,6 +171,7 @@ func NewClient(httpClient *http.Client, base *url.URL) *Client {
c.PartnerService = NewPartnerService(c)
c.ServiceKeyService = NewServiceKeyService(c)
c.ManagedAccountService = NewManagedAccountService(c)
c.EventsService = NewEventsService(c)

c.headers = make(map[string]string)

Expand Down
22 changes: 22 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,25 @@ var ErrInvalidVXCAEndPartnerConfig = errors.New("invalid vxc a-end partner confi

// ErrInvalidVXCBEndPartnerConfig is returned when an invalid VXC B-End partner config is provided
var ErrInvalidVXCBEndPartnerConfig = errors.New("invalid vxc b-end partner config")

// maintenanceStatesToString converts a slice of MaintenanceState to a slice of string
func maintenanceStatesToString(states []MaintenanceState) []string {
strs := make([]string, len(states))
for i, v := range states {
strs[i] = string(v)
}
return strs
}

var ErrInvalidMaintenanceState = fmt.Errorf("invalid maintenance state, valid states are %s", strings.Join(maintenanceStatesToString(VALID_MAINTENANCE_STATES), ", "))

// outageStatesToString converts a slice of OutageState to a slice of string
func outageStatesToString(states []OutageState) []string {
strs := make([]string, len(states))
for i, v := range states {
strs[i] = string(v)
}
return strs
}

var ErrInvalidOutageState = fmt.Errorf("invalid outage state, valid states are %s", strings.Join(outageStatesToString(VALID_OUTAGE_STATES), ", "))
210 changes: 210 additions & 0 deletions events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package megaport

import (
"encoding/json"
"net/http"
"strings"
)

type EventsService interface {
// GetMaintenanceEvents returns details about maintenance events, filtered by the specified state value.
GetMaintenanceEvents(state string) ([]MaintenanceEvent, error)
// GetOutageEvents returns details about outage events, filtered by the specified state value.
GetOutageEvents(state string) ([]OutageEvent, error)
}

type EventsServiceOp struct {
client *Client
}

func NewEventsService(client *Client) EventsService {
return &EventsServiceOp{
client: client,
}
}

type MaintenanceState string
type OutageState string

var (
VALID_MAINTENANCE_STATES = []MaintenanceState{
MAINTENANCE_STATE_COMPLETED,
MAINTENANCE_STATE_SCHEDULED,
MAINTENANCE_STATE_CANCELLED,
MAINTENANCE_STATE_RUNNING,
}
VALID_OUTAGE_STATES = []OutageState{
OUTAGE_STATE_ONGOING,
OUTAGE_STATE_RESOLVED,
}
)

const (
MAINTENANCE_STATE_COMPLETED = MaintenanceState("Completed")
MAINTENANCE_STATE_SCHEDULED = MaintenanceState("Scheduled")
MAINTENANCE_STATE_CANCELLED = MaintenanceState("Cancelled")
MAINTENANCE_STATE_RUNNING = MaintenanceState("Running")
OUTAGE_STATE_ONGOING = OutageState("Ongoing")
OUTAGE_STATE_RESOLVED = OutageState("Resolved")
)

// MaintenanceEvent represents a maintenance event returned by the Events API.
//
// Returns details about maintenance events, filtered by the specified state value.
//
// The following information is returned for maintenance events in the response, with some fields being optional and only included under certain conditions.
type MaintenanceEvent struct {
// EventID is the ticket number against which a particular event is created.
EventID string `json:"eventId"`

// State is the current state of the event.
State string `json:"state"`

// StartTime is the event start time in ISO 8601 UTC format (yyyy-MM-dd'T'HH:mm:ss.SSSX).
StartTime string `json:"startTime"`

// EndTime is the event end time in ISO 8601 UTC format (yyyy-MM-dd'T'HH:mm:ss.SSSX).
EndTime string `json:"endTime"`

// Impact is the impact of the event on the services, if any.
Impact string `json:"impact"`

// Purpose is the reason why this event is created.
Purpose string `json:"purpose"`

// CancelReason is returned if the event is canceled, stating the cancellation reason.
CancelReason string `json:"cancelReason"`

// EventType is "Emergency" if the event is created on short notice; otherwise, it is a "Planned" event.
EventType string `json:"eventType"`

// ServiceIDs is the list of services affected by the event, containing the short UUIDs of the services.
ServiceIDs []string `json:"services"`
}

// OutageEvent represents an outage event returned by the Events API.
//
// The following information is returned for outage events in the response, with some fields being optional and only included under certain conditions.
type OutageEvent struct {
// OutageID is a unique identifier for each outage event.
OutageID string `json:"outageId"`

// EventID is the ticket number against which a particular event is created.
EventID string `json:"eventId"`

// State is the current state of the event.
State string `json:"state"`

// StartTime is the event start time in ISO 8601 UTC format (yyyy-MM-dd'T'HH:mm:ss.SSSX).
StartTime string `json:"startTime"`

// EndTime is the event end time in ISO 8601 UTC format (yyyy-MM-dd'T'HH:mm:ss.SSSX).
EndTime string `json:"endTime"`

// Purpose is the reason why this event is created.
Purpose string `json:"purpose"`

// Services is the list of services affected by the event, containing the short UUIDs of the services.
Services []string `json:"services"`

// RootCause is the reason explaining why an outage happened. This field is present only when an outage is resolved.
RootCause string `json:"rootCause"`

// Resolution explains the solution taken to resolve the outage. Present when an outage is resolved.
Resolution string `json:"resolution"`

// MitigationActions explains the steps taken to avoid such outages in the future. Present for resolved outages.
MitigationActions string `json:"mitigationActions"`

// CreatedBy is the user who created the outage.
CreatedBy string `json:"createdBy"`

// CreatedDate is the date and time when an outage event is created, in ISO 8601 UTC format (yyyy-MM-dd'T'HH:mm:ss.SSSX).
CreatedDate string `json:"createdDate"`

// UpdatedDate is the date and time when an outage event is updated, in ISO 8601 UTC format (yyyy-MM-dd'T'HH:mm:ss.SSSX).
UpdatedDate string `json:"updatedDate"`

// Notices is the list of notices sent as an update for an ongoing outage.
Notices []string `json:"notices"`
}

// GetMaintenanceEvents retrieves maintenance events from the Megaport API, filtered by the specified state.
// It validates the state against the valid maintenance states and returns an error if invalid.
func (s *EventsServiceOp) GetMaintenanceEvents(state string) ([]MaintenanceEvent, error) {
// Validate state
valid := false
for _, st := range VALID_MAINTENANCE_STATES {
if strings.EqualFold(string(st), state) {
valid = true
break
}
}
if !valid {
return nil, ErrInvalidMaintenanceState
}

// Build URL
url := s.client.BaseURL.JoinPath("/ens/v1/status/maintenance").String() + "?state=" + state

// Create HTTP request
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}

// Perform request
resp, err := s.client.HTTPClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

// Decode response as an array of MaintenanceEvent
var events []MaintenanceEvent
if err := json.NewDecoder(resp.Body).Decode(&events); err != nil {
return nil, err
}

return events, nil
}

// GetOutageEvents retrieves outage events from the Megaport API, filtered by the specified state.
// It validates the state against the valid outage states and returns an error if invalid.
func (s *EventsServiceOp) GetOutageEvents(state string) ([]OutageEvent, error) {
// Validate state
valid := false
for _, st := range VALID_OUTAGE_STATES {
if strings.EqualFold(string(st), state) {
valid = true
break
}
}
if !valid {
return nil, ErrInvalidOutageState
}

// Build URL
url := s.client.BaseURL.JoinPath("/ens/v1/status/outage").String() + "?state=" + state

// Create HTTP request
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}

// Perform request
resp, err := s.client.HTTPClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

// Decode response as an array of OutageEvent
var events []OutageEvent
if err := json.NewDecoder(resp.Body).Decode(&events); err != nil {
return nil, err
}

return events, nil
}
126 changes: 126 additions & 0 deletions events_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package megaport

import (
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/stretchr/testify/suite"
)

// EventsTestSuite tests the Events service.
type EventsTestSuite struct {
ClientTestSuite
}

func TestEventsTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(EventsTestSuite))
}

func (suite *EventsTestSuite) SetupTest() {
suite.mux = http.NewServeMux()
suite.server = httptest.NewServer(suite.mux)

suite.client = NewClient(nil, nil)
url, _ := url.Parse(suite.server.URL)
suite.client.BaseURL = url
}

func (suite *EventsTestSuite) TearDownTest() {
suite.server.Close()
}

func (suite *EventsTestSuite) TestGetMaintenanceEvents() {
sampleJSON := `[
{
"eventId": "CSS-1234",
"state": "Scheduled",
"startTime": "2024-05-24T09:12:00.000Z",
"endTime": "2024-05-24T09:42:00.000Z",
"impact": "There will be minor impact on services.",
"purpose": "Services will become more effective",
"eventType": "Emergency",
"services": [
"f06c80bc",
"0746e9a3"
]
},
{
"eventId": "CSS-1235",
"state": "Cancelled",
"startTime": "2024-05-24T09:12:00.000Z",
"endTime": "2024-05-24T09:42:00.000Z",
"impact": "There will be minor impact on services.",
"purpose": "Services will become more effective",
"cancelReason": "Not Needed",
"eventType": "Emergency",
"services": [
"f06c80bc",
"0746e9a3"
]
}
]`

suite.mux.HandleFunc("/ens/v1/status/maintenance", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(sampleJSON))
if err != nil {
suite.FailNowf("Failed to write response", "Error: %v", err)
}
})

svc := &EventsServiceOp{client: suite.client}
events, err := svc.GetMaintenanceEvents("Scheduled")
suite.NoError(err)
suite.Len(events, 2)
suite.Equal("CSS-1234", events[0].EventID)
suite.Equal("Not Needed", events[1].CancelReason)
}

func (suite *EventsTestSuite) TestGetOutageEvents() {
sampleJSON := `[
{
"outageId": "c2037361-eb5b-48a3-9c73-fb4efbf2c886",
"state": "Ongoing",
"eventId": "CSS-1234",
"purpose": "Due to high CPU Usage, service outage occurred",
"startTime": "2024-05-22T09:08:00.000Z",
"createdBy": "[email protected]",
"createdDate": "2024-05-22T13:39:32.468Z",
"updatedDate": "2024-05-22T13:39:32.468Z",
"services": [],
"notices": []
},
{
"outageId": "ce0dd76b-655c-425f-923f-af5ae896756f",
"state": "Ongoing",
"eventId": "CSS-12345",
"purpose": "This happened because something broke",
"startTime": "2024-05-23T08:32:00.000Z",
"createdBy": "[email protected]",
"createdDate": "2024-05-23T13:02:30.968Z",
"updatedDate": "2024-05-23T13:02:30.968Z",
"services": [],
"notices": []
}
]`

suite.mux.HandleFunc("/ens/v1/status/outage", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(sampleJSON))
if err != nil {
suite.FailNowf("Failed to write response", "Error: %v", err)
}
})

svc := &EventsServiceOp{client: suite.client}
events, err := svc.GetOutageEvents("Ongoing")
suite.NoError(err)
suite.Len(events, 2)
suite.Equal("c2037361-eb5b-48a3-9c73-fb4efbf2c886", events[0].OutageID)
suite.Equal("CSS-12345", events[1].EventID)
}
Loading