Skip to content

Commit

Permalink
Add load balancer service monitor CRUD operation support (#196)
Browse files Browse the repository at this point in the history
* Add load balancer service monitor CRUD operation support
  • Loading branch information
Didainius authored Jun 13, 2019
1 parent 0b20d00 commit 07823be
Show file tree
Hide file tree
Showing 13 changed files with 481 additions and 81 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ secrets.yml
# Pycharm IDE
.idea

# VScode IDE
.vscode

# Test artifacts
govcd/govcd_test_config.yaml
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 2.3.0 (Unreleased)

* Added edge gateway create/delete functions [#130](https://github.com/vmware/go-vcloud-director/issues/130).
* Added load balancer service monitor [#196](https://github.com/vmware/go-vcloud-director/pull/196)

## 2.2.0 (May 15, 2019)

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/vmware/go-vcloud-director/v2

require (
github.com/hashicorp/go-version v1.1.0
github.com/kr/pretty v0.1.0 // indirect
github.com/kr/pretty v0.1.0
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
gopkg.in/yaml.v2 v2.2.2
)
54 changes: 45 additions & 9 deletions govcd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"bytes"
"encoding/xml"
"fmt"

"io"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -96,18 +97,16 @@ func (cli *Client) NewRequest(params map[string]string, method string, reqUrl ur
return cli.NewRequestWitNotEncodedParams(params, nil, method, reqUrl, body)
}

// ParseErr takes an error XML resp and returns a single string for use in error messages.
func ParseErr(resp *http.Response) error {

errBody := new(types.Error)

// ParseErr takes an error XML resp, error interface for unmarshaling and returns a single string for
// use in error messages.
func ParseErr(resp *http.Response, errType error) error {
// if there was an error decoding the body, just return that
if err := decodeBody(resp, errBody); err != nil {
if err := decodeBody(resp, errType); err != nil {
util.Logger.Printf("[ParseErr]: unhandled response <--\n%+v\n-->\n", resp)
return fmt.Errorf("[ParseErr]: error parsing error body for non-200 request: %s (%+v)", err, resp)
}

return fmt.Errorf("API Error: %d: %s", errBody.MajorErrorCode, errBody.Message)
return errType
}

// decodeBody is used to XML decode a response body
Expand All @@ -133,6 +132,12 @@ func decodeBody(resp *http.Response, out interface{}) error {
// parses the resultant XML error and returns a descriptive error, if the
// status code is not handled it returns a generic error with the status code.
func checkResp(resp *http.Response, err error) (*http.Response, error) {
return checkRespWithErrType(resp, err, &types.Error{})
}

// checkRespWithErrType allows to specify custom error errType for checkResp unmarshaling
// the error.
func checkRespWithErrType(resp *http.Response, err, errType error) (*http.Response, error) {
if err != nil {
return resp, err
}
Expand Down Expand Up @@ -172,7 +177,7 @@ func checkResp(resp *http.Response, err error) (*http.Response, error) {
http.StatusInternalServerError, // 500
http.StatusServiceUnavailable, // 503
http.StatusGatewayTimeout: // 504
return nil, ParseErr(resp)
return nil, ParseErr(resp, errType)
// Unhandled response.
default:
return nil, fmt.Errorf("unhandled API response, please report this issue, status code: %s", resp.Status)
Expand Down Expand Up @@ -230,6 +235,9 @@ func (client *Client) ExecuteRequestWithoutResponse(pathURL, requestType, conten
return fmt.Errorf(errorMessage, err)
}

// log response explicitly because decodeBody() was not triggered
util.ProcessResponseOutput(util.FuncNameCallStack(), resp, fmt.Sprintf("%s", resp.Body))

err = resp.Body.Close()
if err != nil {
return fmt.Errorf("error closing response body: %s", err)
Expand Down Expand Up @@ -272,7 +280,30 @@ func (client *Client) ExecuteRequest(pathURL, requestType, contentType, errorMes
return resp, nil
}

// ExecuteRequestWithCustomError sends the request and checks for 2xx response. If the returned status code
// was not as expected - the returned error will be unmarshaled to `errType` which implements Go's standard `error`
// interface.
func (client *Client) ExecuteRequestWithCustomError(pathURL, requestType, contentType, errorMessage string,
payload interface{}, errType error) (*http.Response, error) {
if !isMessageWithPlaceHolder(errorMessage) {
return &http.Response{}, fmt.Errorf("error message has to include place holder for error")
}

resp, err := executeRequestCustomErr(pathURL, requestType, contentType, payload, client, errType)
if err != nil {
return &http.Response{}, fmt.Errorf(errorMessage, err)
}

return resp, nil
}

// executeRequest does executeRequestCustomErr and checks for vCD errors in API response
func executeRequest(pathURL, requestType, contentType string, payload interface{}, client *Client) (*http.Response, error) {
return executeRequestCustomErr(pathURL, requestType, contentType, payload, client, &types.Error{})
}

// executeRequestCustomErr performs request and unmarshals API error to errType if not 2xx status was returned
func executeRequestCustomErr(pathURL, requestType, contentType string, payload interface{}, client *Client, errType error) (*http.Response, error) {
url, _ := url.ParseRequestURI(pathURL)

var req *http.Request
Expand All @@ -295,7 +326,12 @@ func executeRequest(pathURL, requestType, contentType string, payload interface{
req.Header.Add("Content-Type", contentType)
}

return checkResp(client.Http.Do(req))
resp, err := client.Http.Do(req)
if err != nil {
return resp, err
}

return checkRespWithErrType(resp, err, errType)
}

func isMessageWithPlaceHolder(message string) bool {
Expand Down
75 changes: 71 additions & 4 deletions govcd/api_vcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const (
TestVMDetachDisk = "TestVMDetachDisk"
TestCreateExternalNetwork = "TestCreateExternalNetwork"
TestDeleteExternalNetwork = "TestDeleteExternalNetwork"
Test_LBServiceMonitor = "Test_LBServiceMonitor"
)

const (
Expand Down Expand Up @@ -328,21 +329,26 @@ func (vcd *TestVCD) infoCleanup(format string, args ...interface{}) {
}

// Gets the two components of a "parent" string, as passed to AddToCleanupList
func splitParent(parent string, separator string) (first, second string) {
func splitParent(parent string, separator string) (first, second, third string) {
strList := strings.Split(parent, separator)
if len(strList) != 2 {
return "", ""
if len(strList) < 2 && len(strList) > 3 {
return "", "", ""
}
first = strList[0]
second = strList[1]

if len(strList) == 3 {
third = strList[2]
}

return
}

var splitParentNotFound string = "removeLeftoverEntries: [ERROR] missing parent info (%s). The parent fields must be defined with a separator '|'\n"
var notFoundMsg string = "removeLeftoverEntries: [INFO] No action for %s '%s'\n"

func (vcd *TestVCD) getAdminOrgAndVdcFromCleanupEntity(entity CleanupEntity) (org AdminOrg, vdc Vdc, err error) {
orgName, vdcName := splitParent(entity.Parent, "|")
orgName, vdcName, _ := splitParent(entity.Parent, "|")
if orgName == "" || vdcName == "" {
vcd.infoCleanup(splitParentNotFound, entity.Parent)
return AdminOrg{}, Vdc{}, fmt.Errorf("can't find parents names")
Expand Down Expand Up @@ -617,6 +623,37 @@ func (vcd *TestVCD) removeLeftoverEntities(entity CleanupEntity) {

vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy)
return
case "lbServiceMonitor":
if entity.Parent == "" {
vcd.infoCleanup("removeLeftoverEntries: [ERROR] No parent specified '%s'\n", entity.Name)
return
}

orgName, vdcName, edgeName := splitParent(entity.Parent, "|")

org, err := GetOrgByName(vcd.client, orgName)
if err != nil {
vcd.infoCleanup("removeLeftoverEntries: [ERROR] Could not find org '%s'\n", orgName)
}
vdc, err := org.GetVdcByName(vdcName)
if err != nil {
vcd.infoCleanup("removeLeftoverEntries: [ERROR] Could not find vdc '%s'\n", vdcName)
}

edge, err := vdc.FindEdgeGateway(edgeName)
if err != nil {
vcd.infoCleanup("removeLeftoverEntries: [ERROR] Could not find edge '%s'\n", vdcName)
}

err = edge.DeleteLBServiceMonitor(&types.LBMonitor{Name: entity.Name})
if err != nil {
vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name)
return
}

vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy)
return

default:
// If we reach this point, we are trying to clean up an entity that
// we aren't prepared for yet.
Expand Down Expand Up @@ -729,3 +766,33 @@ func (vcd *TestVCD) createTestVapp(name string) (VApp, error) {
func init() {
testingTags["api"] = "api_vcd_test.go"
}

func Test_splitParent(t *testing.T) {
type args struct {
parent string
separator string
}
tests := []struct {
name string
args args
wantFirst string
wantSecond string
wantThird string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotFirst, gotSecond, gotThird := splitParent(tt.args.parent, tt.args.separator)
if gotFirst != tt.wantFirst {
t.Errorf("splitParent() gotFirst = %v, want %v", gotFirst, tt.wantFirst)
}
if gotSecond != tt.wantSecond {
t.Errorf("splitParent() gotSecond = %v, want %v", gotSecond, tt.wantSecond)
}
if gotThird != tt.wantThird {
t.Errorf("splitParent() gotThird = %v, want %v", gotThird, tt.wantThird)
}
})
}
}
25 changes: 25 additions & 0 deletions govcd/edgegateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -727,3 +727,28 @@ func (egw *EdgeGateway) HasDefaultGateway() bool {
}
return false
}

// HasAdvancedNetworking returns true if the edge gateway has advanced network configuration enabled
func (egw *EdgeGateway) HasAdvancedNetworking() bool {
return egw.EdgeGateway.Configuration != nil && egw.EdgeGateway.Configuration.AdvancedNetworkingEnabled
}

// buildProxiedEdgeEndpointURL helps to get root endpoint for Edge Gateway using the
// NSX API Proxy and can append optionalSuffix which must have its own leading /
func (eGW *EdgeGateway) buildProxiedEdgeEndpointURL(optionalSuffix string) (string, error) {
apiEndpoint, err := url.ParseRequestURI(eGW.EdgeGateway.HREF)
if err != nil {
return "", fmt.Errorf("unable to process edge gateway URL: %s", err)
}
edgeID := strings.Split(eGW.EdgeGateway.ID, ":")
if len(edgeID) != 4 {
return "", fmt.Errorf("unable to find edge gateway id: %s", eGW.EdgeGateway.ID)
}
hostname := apiEndpoint.Scheme + "://" + apiEndpoint.Host + "/network/edges/" + edgeID[3]

if optionalSuffix != "" {
return hostname + optionalSuffix, nil
}

return hostname, nil
}
Loading

0 comments on commit 07823be

Please sign in to comment.