Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add load balancer service monitor CRUD to govcd #196

Merged
merged 16 commits into from
Jun 13, 2019
Merged
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: 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