Skip to content

Commit

Permalink
Add support for firewall rule handling using NSX-V edge gateway API (v…
Browse files Browse the repository at this point in the history
…mware#247)

* Added methods `CreateNsxvFirewallRule()`, `UpdateNsxvFirewallRule()`, `GetNsxvFirewallRuleById()`, `DeleteNsxvFirewallRuleById()`
which use the proxied NSX-V API of advanced edge gateway for handling firewall rules
* Added methods `GetFirewallParams()`, `UpdateFirewallParams()` for changing global firewall settings
* Added method `GetAnyVnicIndexByNetworkName()` to for easier interface (vNic) lookup in edge gateway
* Added method `ExecuteParamRequestWithCustomError()` which adds query parameter support on top of `ExecuteRequestWithCustomError()`
  • Loading branch information
Didainius authored Oct 22, 2019
1 parent 9f7abf2 commit a6af6b4
Show file tree
Hide file tree
Showing 11 changed files with 773 additions and 95 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ which use the proxied NSX-V API of advanced edge gateway for handling NAT rules
* Added methods `Client.GetVMByHref` `Vapp.GetVAMByName` and related `GetVMById`, `GetVAMByNameOrId`
* Deprecated methods `Client.FindVMByHREF`, `Vdc.FindVMByName`, `Vdc.FindVAppByID`, and `Vdc.FindVAppByName`
* Added methods `Vm.GetGuestCustomizationSection` and `Vm.SetGuestCustomizationSection`
* Added methods `CreateNsxvFirewallRule()`, `UpdateNsxvFirewallRule()`, `GetNsxvFirewallRuleById()`, `DeleteNsxvFirewallRuleById()`
which use the proxied NSX-V API of advanced edge gateway for handling firewall rules [#247](https://github.com/vmware/go-vcloud-director/pull/247)
* Added methods `GetFirewallParams()`, `UpdateFirewallParams()` for changing global firewall settings [#247](https://github.com/vmware/go-vcloud-director/pull/247)
* Added method `GetAnyVnicIndexByNetworkName()` to for easier interface (vNic) lookup in edge gateway [#247](https://github.com/vmware/go-vcloud-director/pull/247)
* Added method `ExecuteParamRequestWithCustomError()` which adds query parameter support on top of `ExecuteRequestWithCustomError()` [#247](https://github.com/vmware/go-vcloud-director/pull/247)
* Deprecated methods `VDC.FindDiskByHREF` and `FindDiskByHREF`
* Added methods `VDC.GetDiskByHref` `VDC.GetDisksByName` and related `GetDiskById`
* Added new methods `Catalog.QueryMedia`, `Catalog.GetMediaByName`, `Catalog.GetMediaById`, `Catalog.GetMediaByNameOrId`, `AdminCatalog.QueryMedia`, `AdminCatalog.GetMediaByName`, `AdminCatalog.GetMediaById`, `AdminCatalog.GetMediaByNameOrId`, `MediaRecord.Refresh`, `MediaRecord.Delete`, `MediaRecord.GetMetadata`, `MediaRecord.AddMetadata`, `MediaRecord.AddMetadataAsync`, `MediaRecord.DeleteMetadata`, `MediaRecord.DeleteMetadataAsync`, `Media.GetMetadata`, `Media.AddMetadata`, `Media.AddMetadataAsync`, `Media.DeleteMetadata`, `Media.DeleteMetadataAsync` [#245](https://github.com/vmware/go-vcloud-director/pull/245)
Expand Down
18 changes: 13 additions & 5 deletions govcd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,11 +375,19 @@ func (client *Client) ExecuteRequest(pathURL, requestType, contentType, errorMes
// interface.
func (client *Client) ExecuteRequestWithCustomError(pathURL, requestType, contentType, errorMessage string,
payload interface{}, errType error) (*http.Response, error) {
return client.ExecuteParamRequestWithCustomError(pathURL, map[string]string{}, requestType, contentType,
errorMessage, payload, errType)
}

// ExecuteParamRequestWithCustomError behaves exactly like ExecuteRequestWithCustomError but accepts
// query parameter specification
func (client *Client) ExecuteParamRequestWithCustomError(pathURL string, params map[string]string,
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)
resp, err := executeRequestCustomErr(pathURL, params, requestType, contentType, payload, client, errType)
if err != nil {
return &http.Response{}, fmt.Errorf(errorMessage, err)
}
Expand All @@ -403,11 +411,11 @@ func (client *Client) ExecuteRequestWithCustomError(pathURL, requestType, conten

// 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{})
return executeRequestCustomErr(pathURL, map[string]string{}, 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) {
func executeRequestCustomErr(pathURL string, params map[string]string, requestType, contentType string, payload interface{}, client *Client, errType error) (*http.Response, error) {
url, _ := url.ParseRequestURI(pathURL)

var req *http.Request
Expand All @@ -420,10 +428,10 @@ func executeRequestCustomErr(pathURL, requestType, contentType string, payload i
}
body := bytes.NewBufferString(xml.Header + string(marshaledXml))

req = client.NewRequest(map[string]string{}, requestType, *url, body)
req = client.NewRequest(params, requestType, *url, body)

default:
req = client.NewRequest(map[string]string{}, requestType, *url, nil)
req = client.NewRequest(params, requestType, *url, nil)
}

if contentType != "" {
Expand Down
77 changes: 25 additions & 52 deletions govcd/api_vcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
Expand Down Expand Up @@ -1024,6 +1023,31 @@ func (vcd *TestVCD) removeLeftoverEntities(entity CleanupEntity) {
vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy)
return

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

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

_, _, edge, err := getOrgVdcEdgeByNames(vcd, orgName, vdcName, edgeName)
if err != nil {
vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err)
}

err = edge.DeleteNsxvFirewallRuleById(entity.Name)
if IsNotFound(err) {
vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name)
return
}
if err != nil {
vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err)
}

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 @@ -1197,54 +1221,3 @@ func Test_splitParent(t *testing.T) {
})
}
}

// testGetLBGeneralParamsXML is used for additional validation that modifying load balancer
// does not change any single field. It returns a string of whole load balancer configuration
func testGetLBGeneralParamsXML(edge EdgeGateway, check *C) string {

httpPath, err := edge.buildProxiedEdgeEndpointURL(types.LbConfigPath)
check.Assert(err, IsNil)

resp, err := edge.client.ExecuteRequestWithCustomError(httpPath, http.MethodGet, types.AnyXMLMime,
"unable to get XML from load balancer %s", nil, &types.NSXError{})
check.Assert(err, IsNil)

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
check.Assert(err, IsNil)

return string(body)
}

// cacheLoadBalancer is meant to store load balancer settings before any operations so that all
// configuration can be checked after manipulation
func testCacheLoadBalancer(edge EdgeGateway, check *C) (*types.LbGeneralParamsWithXml, string) {
beforeLb, err := edge.GetLBGeneralParams()
check.Assert(err, IsNil)
beforeLbXml := testGetLBGeneralParamsXML(edge, check)
return beforeLb, beforeLbXml
}

// testCheckLoadBalancerConfig validates if both raw XML string and load balancer struct remain
// identical after settings manipulation.
func testCheckLoadBalancerConfig(beforeLb *types.LbGeneralParamsWithXml, beforeLbXml string, edge EdgeGateway, check *C) {
afterLb, err := edge.GetLBGeneralParams()
check.Assert(err, IsNil)

afterLbXml := testGetLBGeneralParamsXML(edge, check)

// remove `<version></version>` tag from both XML represntation and struct for deep comparison
// because this version changes with each update and will never be the same after a few
// operations

reVersion := regexp.MustCompile(`<version>\w*<\/version>`)
beforeLbXml = reVersion.ReplaceAllLiteralString(beforeLbXml, "")
afterLbXml = reVersion.ReplaceAllLiteralString(afterLbXml, "")

beforeLb.Version = ""
afterLb.Version = ""

check.Assert(beforeLb, DeepEquals, afterLb)
check.Assert(beforeLbXml, DeepEquals, afterLbXml)
}
129 changes: 121 additions & 8 deletions govcd/edgegateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -1144,20 +1144,99 @@ func (egw *EdgeGateway) UpdateLBGeneralParams(enabled, accelerationEnabled, logg
return nil, fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
}
_, err = egw.client.ExecuteRequestWithCustomError(httpPath, http.MethodPut, types.AnyXMLMime,
"error while updating load balancer application rule : %s", currentLb, &types.NSXError{})
"error while updating load balancer config: %s", currentLb, &types.NSXError{})
if err != nil {
return nil, err
}

// Retrieve configuration after update
updatedLb, err := egw.GetLBGeneralParams()
if err != nil {
return nil, fmt.Errorf("unable to retrieve load balancer after update: %s", err)
return nil, fmt.Errorf("unable to retrieve load balancer config after update: %s", err)
}

return updatedLb, nil
}

// GetFirewallConfig retrieves firewall configuration and can be used
// to alter master configuration options. These are 3 fields only:
// FirewallConfigWithXml.Enabled, FirewallConfigWithXml.DefaultPolicy.LoggingEnabled and
// FirewallConfigWithXml.DefaultPolicy.Action
func (egw *EdgeGateway) GetFirewallConfig() (*types.FirewallConfigWithXml, error) {
if !egw.HasAdvancedNetworking() {
return nil, fmt.Errorf("only advanced edge gateway support firewall configuration")
}

httpPath, err := egw.buildProxiedEdgeEndpointURL(types.EdgeFirewallPath)
if err != nil {
return nil, fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
}

firewallConfig := &types.FirewallConfigWithXml{}
_, err = egw.client.ExecuteRequest(httpPath, http.MethodGet, types.AnyXMLMime,
"unable to read firewall configuration: %s", nil, firewallConfig)

if err != nil {
return nil, err
}

return firewallConfig, nil
}

// UpdateFirewallConfig allows to update firewall configuration.
// It accepts three fields (Enabled, DefaultLoggingEnabled, DefaultAction) and uses
// them to construct types.FirewallConfigWithXml without altering other options to prevent config
// corruption.
// They are represented in firewall configuration page in the UI.
func (egw *EdgeGateway) UpdateFirewallConfig(enabled, defaultLoggingEnabled bool, defaultAction string) (*types.FirewallConfigWithXml, error) {
if !egw.HasAdvancedNetworking() {
return nil, fmt.Errorf("only advanced edge gateway supports load balancing")
}

if defaultAction != "accept" && defaultAction != "deny" {
return nil, fmt.Errorf("default action must be either 'accept' or 'deny'")
}

// Retrieve firewall latest configuration
currentFw, err := egw.GetFirewallConfig()
if err != nil {
return nil, fmt.Errorf("unable to retrieve firewall config before update: %s", err)
}

// Check if change is needed. If not - return early.
if currentFw.Enabled == enabled && currentFw.DefaultPolicy.LoggingEnabled == defaultLoggingEnabled &&
currentFw.DefaultPolicy.Action == defaultAction {
return currentFw, nil
}

// Modify only the global configuration settings
currentFw.Enabled = enabled
currentFw.DefaultPolicy.LoggingEnabled = defaultLoggingEnabled
currentFw.DefaultPolicy.Action = defaultAction

// Omit the version as it is updated automatically with each put
currentFw.Version = ""

// Push updated configuration
httpPath, err := egw.buildProxiedEdgeEndpointURL(types.EdgeFirewallPath)
if err != nil {
return nil, fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
}
_, err = egw.client.ExecuteRequestWithCustomError(httpPath, http.MethodPut, types.AnyXMLMime,
"error while updating firewall configuration : %s", currentFw, &types.NSXError{})
if err != nil {
return nil, err
}

// Retrieve configuration after update
updatedFw, err := egw.GetFirewallConfig()
if err != nil {
return nil, fmt.Errorf("unable to retrieve firewall after update: %s", err)
}

return updatedFw, nil
}

// validateUpdateLoadBalancer validates mandatory fields for global load balancer configuration
// settings
func validateUpdateLBGeneralParams(logLevel string) error {
Expand Down Expand Up @@ -1199,7 +1278,41 @@ func (egw *EdgeGateway) GetVnicIndexByNetworkNameAndType(networkName, networkTyp
if err != nil {
return nil, fmt.Errorf("cannot retrieve vNic configuration: %s", err)
}
return GetVnicIndexByNetworkNameAndType(networkName, networkType, vnics)
return getVnicIndexByNetworkNameAndType(networkName, networkType, vnics)
}

// GetAnyVnicIndexByNetworkName parses XML structure of vNic mapping to networks in edge gateway XML
// and returns *int of vNic index and network type by network name
// networkName cannot be empty
// networkType will be one of: 'internal', 'uplink', 'trunk', 'subinterface'
//
// Warning: this function assumes that there are no duplicate network names attached. If it is so
// this function will return the first network
func (egw *EdgeGateway) GetAnyVnicIndexByNetworkName(networkName string) (*int, string, error) {
vnics, err := egw.getVnics()
if err != nil {
return nil, "", fmt.Errorf("cannot retrieve vNic configuration: %s", err)
}

var foundVnicIndex *int
var foundVnicType string

possibleNicTypes := []string{types.EdgeGatewayVnicTypeUplink, types.EdgeGatewayVnicTypeInternal,
types.EdgeGatewayVnicTypeTrunk, types.EdgeGatewayVnicTypeSubinterface}

for _, nicType := range possibleNicTypes {
vNicIndex, err := getVnicIndexByNetworkNameAndType(networkName, nicType, vnics)
if err == nil { // nil error means we have found nic
foundVnicIndex = vNicIndex
foundVnicType = nicType
break
}
}

if foundVnicIndex == nil && foundVnicType == "" {
return nil, "", ErrorEntityNotFound
}
return foundVnicIndex, foundVnicType, nil
}

// GetNetworkNameAndTypeByVnicIndex returns network name and network type for given vNic index
Expand All @@ -1209,11 +1322,11 @@ func (egw *EdgeGateway) GetNetworkNameAndTypeByVnicIndex(vNicIndex int) (string,
if err != nil {
return "", "", fmt.Errorf("cannot retrieve vNic configuration: %s", err)
}
return GetNetworkNameAndTypeByVnicIndex(vNicIndex, vnics)
return getNetworkNameAndTypeByVnicIndex(vNicIndex, vnics)
}

// GetVnicIndexByNetworkNameAndType is wrapped and used by public function GetVnicIndexByNetworkNameAndType
func GetVnicIndexByNetworkNameAndType(networkName, networkType string, vnics *types.EdgeGatewayVnics) (*int, error) {
// getVnicIndexByNetworkNameAndType is wrapped and used by public function GetVnicIndexByNetworkNameAndType
func getVnicIndexByNetworkNameAndType(networkName, networkType string, vnics *types.EdgeGatewayVnics) (*int, error) {
if networkName == "" {
return nil, fmt.Errorf("network name cannot be empty")
}
Expand Down Expand Up @@ -1257,8 +1370,8 @@ func GetVnicIndexByNetworkNameAndType(networkName, networkType string, vnics *ty
return foundIndex, nil
}

// GetNetworkNameAndTypeByVnicIndex is wrapped and used by public function GetNetworkNameAndTypeByVnicIndex
func GetNetworkNameAndTypeByVnicIndex(vNicIndex int, vnics *types.EdgeGatewayVnics) (string, string, error) {
// getNetworkNameAndTypeByVnicIndex looks up network type and name in list of edge gateway interfaces
func getNetworkNameAndTypeByVnicIndex(vNicIndex int, vnics *types.EdgeGatewayVnics) (string, string, error) {
if vNicIndex < 0 {
return "", "", fmt.Errorf("vNic index cannot be negative")
}
Expand Down
Loading

0 comments on commit a6af6b4

Please sign in to comment.