diff --git a/CHANGELOG.md b/CHANGELOG.md index 381b5dcac..1aba93d26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ [#368](https://github.com/vmware/go-vcloud-director/pull/368) * Added methods Org.QueryVmList and Org.QueryVmById to find VM by ID in an Org [#368](https://github.com/vmware/go-vcloud-director/pull/368) +* Added `NsxtAppPortProfile` and `types.NsxtAppPortProfile` for NSX-T Application Port Profile management + [#378](https://github.com/vmware/go-vcloud-director/pull/378) + BREAKING CHANGES: * Added parameter `description` to method `vdc.ComposeRawVapp` [#372](https://github.com/vmware/go-vcloud-director/pull/372) @@ -41,6 +44,11 @@ IMPROVEMENTS: [#368](https://github.com/vmware/go-vcloud-director/pull/368) * Improved test entity cleanup to allow specifying parent VDC for vApp removals [#368](https://github.com/vmware/go-vcloud-director/pull/368) +* Improved `OpenApiGetAllItems` to still follow pages in VCD endpoints with BUG which don't return 'nextPage' link for + pagination [#378](https://github.com/vmware/go-vcloud-director/pull/378) +* Improved LDAP container related tests to use correct port mapping for latest LDAP container version + [#378](https://github.com/vmware/go-vcloud-director/pull/378) + ## 2.11.0 (March 10, 2021) diff --git a/govcd/adminorg_ldap_test.go b/govcd/adminorg_ldap_test.go index 2eb4863a8..e722ebbf4 100644 --- a/govcd/adminorg_ldap_test.go +++ b/govcd/adminorg_ldap_test.go @@ -161,6 +161,15 @@ func configureLdapForOrg(vcd *TestVCD, check *C, ldapHostIp string) { // In essence it creates two groups - "admin_staff" and "ship_crew" and a few users. // More information about users and groups in: https://github.com/rroemhild/docker-test-openldap func createLdapServer(vcd *TestVCD, check *C, directNetworkName string) (string, string, string) { + // LDAP configuration is tailored for this testing LDAP container. However it may make sense to host it in custom + // docker registry because docker throttles container downloads and this might cause test failures. + // Config vcd.config.Misc.LdapContainer can be set to pull this container from custom registry. + ldapContainerName := "rroemhild/test-openldap" + if vcd.config.Misc.LdapContainer != "" { + ldapContainerName = vcd.config.Misc.LdapContainer + } + fmt.Printf("# Using docker LDAP image '%s'\n", ldapContainerName) + vAppName := "ldap" // The customization script waits until IP address is set on the NIC because Guest tools run // script and network configuration together. If the script runs too quick - there is a risk @@ -168,7 +177,7 @@ func createLdapServer(vcd *TestVCD, check *C, directNetworkName string) (string, // remote. Guest tools could also be interrupted if the script below failed before NICs are // configured therefore it is run in background. // It waits until "inet" (not "inet6") is set and then runs docker container - const ldapCustomizationScript = ` + ldapCustomizationScript := ` { until ip a show eth0 | grep "inet " do @@ -176,7 +185,7 @@ func createLdapServer(vcd *TestVCD, check *C, directNetworkName string) (string, done systemctl enable docker systemctl start docker - docker run --name ldap-server --restart=always --privileged -d -p 389:389 rroemhild/test-openldap + docker run --name ldap-server --restart=always -d -p 389:10389 ` + ldapContainerName + ` } & ` // Get Org, Vdc diff --git a/govcd/api_vcd_test.go b/govcd/api_vcd_test.go index 7e505bb40..cc71618d8 100644 --- a/govcd/api_vcd_test.go +++ b/govcd/api_vcd_test.go @@ -193,6 +193,9 @@ type TestConfig struct { Media string `yaml:"mediaName,omitempty"` PhotonOsOvaPath string `yaml:"photonOsOvaPath,omitempty"` } `yaml:"media"` + Misc struct { + LdapContainer string `yaml:"ldapContainer,omitempty"` + } `yaml:"misc"` } // Test struct for vcloud-director. diff --git a/govcd/nsxt_application_profile.go b/govcd/nsxt_application_profile.go new file mode 100644 index 000000000..7923a05ae --- /dev/null +++ b/govcd/nsxt_application_profile.go @@ -0,0 +1,219 @@ +/* + * Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + "fmt" + "net/url" + + "github.com/vmware/go-vcloud-director/v2/types/v56" +) + +// NsxtAppPortProfile uses OpenAPI endpoint to operate NSX-T Application Port Profiles +// It can have 3 types of scopes: +// * SYSTEM - Read-only (The ones that are provided by SYSTEM). Constant `types.ApplicationPortProfileScopeSystem` +// * PROVIDER - Created by Provider on a particular network provider (NSX-T manager). Constant `types.ApplicationPortProfileScopeProvider` +// * TENANT (Created by Tenant at Org VDC level). Constant `types.ApplicationPortProfileScopeTenant` +// +// More details about scope in documentation for types.NsxtAppPortProfile +type NsxtAppPortProfile struct { + NsxtAppPortProfile *types.NsxtAppPortProfile + client *Client +} + +// CreateNsxtAppPortProfile allows users to create NSX-T Application Port Profile definition. +// It can have 3 types of scopes: +// * SYSTEM (The ones that are provided by SYSTEM) Read-only +// * PROVIDER (Created by Provider globally) +// * TENANT (Create by tenant at Org level) +// More details about scope in documentation for types.NsxtAppPortProfile +func (org *Org) CreateNsxtAppPortProfile(appPortProfileConfig *types.NsxtAppPortProfile) (*NsxtAppPortProfile, error) { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAppPortProfiles + minimumApiVersion, err := org.client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := org.client.OpenApiBuildEndpoint(endpoint) + if err != nil { + return nil, err + } + + returnObject := &NsxtAppPortProfile{ + NsxtAppPortProfile: &types.NsxtAppPortProfile{}, + client: org.client, + } + + err = org.client.OpenApiPostItem(minimumApiVersion, urlRef, nil, appPortProfileConfig, returnObject.NsxtAppPortProfile) + if err != nil { + return nil, fmt.Errorf("error creating NSX-T Application Port Profile: %s", err) + } + + return returnObject, nil +} + +// GetAllNsxtAppPortProfiles returns all NSX-T Application Port Profiles for specific scope +// More details about scope in documentation for types.NsxtAppPortProfile +func (org *Org) GetAllNsxtAppPortProfiles(queryParameters url.Values, scope string) ([]*NsxtAppPortProfile, error) { + queryParams := copyOrNewUrlValues(queryParameters) + if scope != "" { + queryParams = queryParameterFilterAnd("scope=="+scope, queryParams) + } + + return getAllNsxtAppPortProfiles(org.client, queryParams) +} + +// GetNsxtAppPortProfileByName allows users to retrieve Application Port Profiles for specific scope. +// More details in documentation for types.NsxtAppPortProfile +// +// Note. Names are enforced to be unique per scope +func (org *Org) GetNsxtAppPortProfileByName(name, scope string) (*NsxtAppPortProfile, error) { + queryParameters := url.Values{} + if scope != "" { + queryParameters = queryParameterFilterAnd("scope=="+scope, queryParameters) + } + + return getNsxtAppPortProfileByName(org.client, name, queryParameters) +} + +// GetNsxtAppPortProfileById retrieves NSX-T Application Port Profile by ID +func (org *Org) GetNsxtAppPortProfileById(id string) (*NsxtAppPortProfile, error) { + return getNsxtAppPortProfileById(org.client, id) +} + +// Update allows users to update NSX-T Application Port Profile +func (appPortProfile *NsxtAppPortProfile) Update(appPortProfileConfig *types.NsxtAppPortProfile) (*NsxtAppPortProfile, error) { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAppPortProfiles + minimumApiVersion, err := appPortProfile.client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + if appPortProfileConfig.ID == "" { + return nil, fmt.Errorf("cannot update NSX-T Application Port Profile without ID") + } + + urlRef, err := appPortProfile.client.OpenApiBuildEndpoint(endpoint, appPortProfileConfig.ID) + if err != nil { + return nil, err + } + + returnObject := &NsxtAppPortProfile{ + NsxtAppPortProfile: &types.NsxtAppPortProfile{}, + client: appPortProfile.client, + } + + err = appPortProfile.client.OpenApiPutItem(minimumApiVersion, urlRef, nil, appPortProfileConfig, returnObject.NsxtAppPortProfile) + if err != nil { + return nil, fmt.Errorf("error updating NSX-T Application Port Profile : %s", err) + } + + return returnObject, nil +} + +// Delete allows users to delete NSX-T Application Port Profile +func (appPortProfile *NsxtAppPortProfile) Delete() error { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAppPortProfiles + minimumApiVersion, err := appPortProfile.client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return err + } + + if appPortProfile.NsxtAppPortProfile.ID == "" { + return fmt.Errorf("cannot delete NSX-T Application Port Profile without ID") + } + + urlRef, err := appPortProfile.client.OpenApiBuildEndpoint(endpoint, appPortProfile.NsxtAppPortProfile.ID) + if err != nil { + return err + } + + err = appPortProfile.client.OpenApiDeleteItem(minimumApiVersion, urlRef, nil) + + if err != nil { + return fmt.Errorf("error deleting NSX-T Application Port Profile: %s", err) + } + + return nil +} + +func getNsxtAppPortProfileByName(client *Client, name string, queryParameters url.Values) (*NsxtAppPortProfile, error) { + queryParams := copyOrNewUrlValues(queryParameters) + queryParams = queryParameterFilterAnd("name=="+name, queryParams) + + allAppPortProfiles, err := getAllNsxtAppPortProfiles(client, queryParams) + if err != nil { + return nil, fmt.Errorf("could not find NSX-T Application Port Profile with name '%s': %s", name, err) + } + + if len(allAppPortProfiles) == 0 { + return nil, fmt.Errorf("%s: expected exactly one NSX-T Application Port Profile with name '%s'. Got %d", ErrorEntityNotFound, name, len(allAppPortProfiles)) + } + + if len(allAppPortProfiles) > 1 { + return nil, fmt.Errorf("expected exactly one NSX-T Application Port Profile with name '%s'. Got %d", name, len(allAppPortProfiles)) + } + + return getNsxtAppPortProfileById(client, allAppPortProfiles[0].NsxtAppPortProfile.ID) +} + +func getNsxtAppPortProfileById(client *Client, id string) (*NsxtAppPortProfile, error) { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAppPortProfiles + minimumApiVersion, err := client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + if id == "" { + return nil, fmt.Errorf("empty NSX-T Application Port Profile ID specified") + } + + urlRef, err := client.OpenApiBuildEndpoint(endpoint, id) + if err != nil { + return nil, err + } + + appPortProfile := &NsxtAppPortProfile{ + NsxtAppPortProfile: &types.NsxtAppPortProfile{}, + client: client, + } + + err = client.OpenApiGetItem(minimumApiVersion, urlRef, nil, appPortProfile.NsxtAppPortProfile) + if err != nil { + return nil, err + } + + return appPortProfile, nil +} + +func getAllNsxtAppPortProfiles(client *Client, queryParameters url.Values) ([]*NsxtAppPortProfile, error) { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAppPortProfiles + minimumApiVersion, err := client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := client.OpenApiBuildEndpoint(endpoint) + if err != nil { + return nil, err + } + + typeResponses := []*types.NsxtAppPortProfile{{}} + err = client.OpenApiGetAllItems(minimumApiVersion, urlRef, queryParameters, &typeResponses) + if err != nil { + return nil, err + } + + // Wrap all typeResponses into NsxtAppPortProfile types with client + wrappedResponses := make([]*NsxtAppPortProfile, len(typeResponses)) + for sliceIndex := range typeResponses { + wrappedResponses[sliceIndex] = &NsxtAppPortProfile{ + NsxtAppPortProfile: typeResponses[sliceIndex], + client: client, + } + } + + return wrappedResponses, nil +} diff --git a/govcd/nsxt_application_profile_test.go b/govcd/nsxt_application_profile_test.go new file mode 100644 index 000000000..5d4e11a3b --- /dev/null +++ b/govcd/nsxt_application_profile_test.go @@ -0,0 +1,182 @@ +// +build network nsxt functional openapi ALL + +package govcd + +import ( + "fmt" + "net/url" + + "github.com/vmware/go-vcloud-director/v2/types/v56" + . "gopkg.in/check.v1" +) + +func (vcd *TestVCD) Test_NsxtApplicationPortProfileProvider(check *C) { + skipNoNsxtConfiguration(vcd, check) + skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointAppPortProfiles) + + appPortProfileConfig := getAppProfileProvider(vcd, check) + testAppPortProfile(appPortProfileConfig, types.ApplicationPortProfileScopeProvider, vcd, check) + +} + +func (vcd *TestVCD) Test_NsxtApplicationPortProfileTenant(check *C) { + skipNoNsxtConfiguration(vcd, check) + skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointAppPortProfiles) + + appPortProfileConfig := getAppProfileTenant(vcd, check) + testAppPortProfile(appPortProfileConfig, types.ApplicationPortProfileScopeTenant, vcd, check) + +} + +func (vcd *TestVCD) Test_NsxtApplicationPortProfileReadSystem(check *C) { + skipNoNsxtConfiguration(vcd, check) + skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointAppPortProfiles) + + testApplicationProfilesForScope(types.ApplicationPortProfileScopeSystem, check, vcd) +} + +func (vcd *TestVCD) Test_NsxtApplicationPortProfileReadProvider(check *C) { + skipNoNsxtConfiguration(vcd, check) + skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointAppPortProfiles) + + testApplicationProfilesForScope(types.ApplicationPortProfileScopeProvider, check, vcd) +} + +func (vcd *TestVCD) Test_NsxtApplicationPortProfileReadTenant(check *C) { + skipNoNsxtConfiguration(vcd, check) + skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointAppPortProfiles) + + testApplicationProfilesForScope(types.ApplicationPortProfileScopeTenant, check, vcd) +} + +func getAppProfileProvider(vcd *TestVCD, check *C) *types.NsxtAppPortProfile { + nsxtManager, err := vcd.client.QueryNsxtManagerByName(vcd.config.VCD.Nsxt.Manager) + check.Assert(err, IsNil) + + nsxtManagerUuid, err := GetUuidFromHref(nsxtManager[0].HREF, true) + check.Assert(err, IsNil) + + nsxtManagerUrn, err := BuildUrnWithUuid("urn:vcloud:nsxtmanager:", nsxtManagerUuid) + check.Assert(err, IsNil) + + // For PROVIDER scope application port profile must have ContextEntityId set as NSX-T Managers URN and no Org + appPortProfileConfig := &types.NsxtAppPortProfile{ + Name: check.TestName() + "PROVIDER", + Description: "Provider config", + ApplicationPorts: []types.NsxtAppPortProfilePort{ + types.NsxtAppPortProfilePort{ + Protocol: "TCP", + DestinationPorts: []string{"11000-12000"}, + }, + }, + ContextEntityId: nsxtManagerUrn, + Scope: types.ApplicationPortProfileScopeProvider, + } + return appPortProfileConfig +} + +func getAppProfileTenant(vcd *TestVCD, check *C) *types.NsxtAppPortProfile { + org, err := vcd.client.GetOrgByName(vcd.config.VCD.Org) + check.Assert(err, IsNil) + + // For PROVIDER scope application port profile must have ContextEntityId set as NSX-T Managers URN and no Org + appPortProfileConfig := &types.NsxtAppPortProfile{ + Name: check.TestName() + "TENANT", + Description: "Provider config", + ApplicationPorts: []types.NsxtAppPortProfilePort{ + types.NsxtAppPortProfilePort{ + Protocol: "ICMPv4", + DestinationPorts: []string{}, + }, + }, + OrgRef: &types.OpenApiReference{ID: org.Org.ID, Name: org.Org.Name}, + + ContextEntityId: vcd.nsxtVdc.Vdc.ID, // VDC ID + Scope: types.ApplicationPortProfileScopeTenant, + } + return appPortProfileConfig +} + +func testAppPortProfile(appPortProfileConfig *types.NsxtAppPortProfile, scope string, vcd *TestVCD, check *C) { + org, err := vcd.client.GetOrgByName(vcd.config.VCD.Org) + check.Assert(err, IsNil) + appProfile, err := org.CreateNsxtAppPortProfile(appPortProfileConfig) + check.Assert(err, IsNil) + + openApiEndpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAppPortProfiles + appProfile.NsxtAppPortProfile.ID + AddToCleanupListOpenApi(appProfile.NsxtAppPortProfile.Name, check.TestName(), openApiEndpoint) + + appPortProfileConfig.ID = appProfile.NsxtAppPortProfile.ID // Inject ID into original creation + appPortProfileConfig.ContextEntityId = "" // Remove NSX-T Manager URN because read does not return it + check.Assert(appProfile.NsxtAppPortProfile, DeepEquals, appPortProfileConfig) + + // Check update + appProfile.NsxtAppPortProfile.Description = appProfile.NsxtAppPortProfile.Description + "-Update" + updatedAppProfile, err := appProfile.Update(appProfile.NsxtAppPortProfile) + check.Assert(err, IsNil) + check.Assert(updatedAppProfile.NsxtAppPortProfile, DeepEquals, appProfile.NsxtAppPortProfile) + + // Check lookup + foundAppProfileById, err := org.GetNsxtAppPortProfileById(appProfile.NsxtAppPortProfile.ID) + check.Assert(err, IsNil) + check.Assert(foundAppProfileById.NsxtAppPortProfile, DeepEquals, appProfile.NsxtAppPortProfile) + + foundAppProfileByName, err := org.GetNsxtAppPortProfileByName(appProfile.NsxtAppPortProfile.Name, scope) + check.Assert(err, IsNil) + check.Assert(foundAppProfileByName.NsxtAppPortProfile, DeepEquals, foundAppProfileById.NsxtAppPortProfile) + + err = appProfile.Delete() + check.Assert(err, IsNil) + + // Expect a not found error + _, err = org.GetNsxtAppPortProfileById(appProfile.NsxtAppPortProfile.ID) + check.Assert(ContainsNotFound(err), Equals, true) + + _, err = org.GetNsxtAppPortProfileByName(appProfile.NsxtAppPortProfile.Name, scope) + check.Assert(ContainsNotFound(err), Equals, true) +} + +func testApplicationProfilesForScope(scope string, check *C, vcd *TestVCD) { + org, err := vcd.client.GetOrgByName(vcd.config.VCD.Org) + check.Assert(err, IsNil) + + resultCount := getResultCountByScope(scope, check, vcd) + if testVerbose { + fmt.Printf("# API shows results for scope '%s': %d\n", scope, resultCount) + } + + appProfileSlice, err := org.GetAllNsxtAppPortProfiles(nil, scope) + check.Assert(err, IsNil) + + if testVerbose { + fmt.Printf("# Paginated item number for scope '%s': %d\n", scope, len(appProfileSlice)) + } + + // Ensure the amount of results is exactly the same as returned by getResultCountByScope which makes sure that + // pagination is not broken. + check.Assert(len(appProfileSlice), Equals, resultCount) +} + +func getResultCountByScope(scope string, check *C, vcd *TestVCD) int { + // Get element count by using a simple query and parse response directly to compare it against paginated list of items + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAppPortProfiles + skipOpenApiEndpointTest(vcd, check, endpoint) + apiVersion, err := vcd.client.Client.checkOpenApiEndpointCompatibility(endpoint) + check.Assert(err, IsNil) + + urlRef, err := vcd.client.Client.OpenApiBuildEndpoint(endpoint) + check.Assert(err, IsNil) + + // Limit search of audits trails to the last 12 hours so that it doesn't take too long and set pageSize to be 1 result + // to force following pages + queryParams := url.Values{} + queryParams.Add("filter", "scope=="+scope) + + result := struct { + Resulttotal int `json:"resultTotal"` + }{} + + err = vcd.vdc.client.OpenApiGetItem(apiVersion, urlRef, queryParams, &result) + check.Assert(err, IsNil) + return result.Resulttotal +} diff --git a/govcd/openapi.go b/govcd/openapi.go index 0c218f16e..05fd011ac 100644 --- a/govcd/openapi.go +++ b/govcd/openapi.go @@ -13,6 +13,7 @@ import ( "net/http" "net/url" "reflect" + "strconv" "strings" "github.com/peterhellberg/link" @@ -520,6 +521,18 @@ func (client *Client) openApiPerformPostPut(httpMethod string, apiVersion string // works by at first crawling pages and accumulating all responses into []json.RawMessage (as strings). Because there is // no intermediate unmarshalling to exact `outType` for every page it can unmarshal into direct `outType` supplied. // outType must be a slice of object (e.g. []*types.OpenApiRole) because accumulated responses are in JSON list +// +// It follows pages in two ways: +// * Finds a 'nextPage' link and uses it to recursively crawl all pages (default for all, except for API bug) +// * Uses fields 'resultTotal', 'page', and 'pageSize' to calculate if it should crawl further on. It is only done +// because there is a BUG in API and in some endpoints it does not return 'nextPage' link as well as null 'pageCount' +// +// In general 'nextPage' header is preferred because some endpoints +// (like cloudapi/1.0.0/nsxTResources/importableTier0Routers) do not contain pagination details and nextPage header +// contains a base64 encoded data chunk via a supplied `cursor` field +// (e.g. ...importableTier0Routers?filter=_context==urn:vcloud:nsxtmanager:85aa2514-6a6f-4a32-8904-9695dc0f0298& +// cursor=eyJORVRXT1JLSU5HX0NVUlNPUl9PRkZTRVQiOiIwIiwicGFnZVNpemUiOjEsIk5FVFdPUktJTkdfQ1VSU09SIjoiMDAwMTMifQ==) +// The 'cursor' in example contains such values {"NETWORKING_CURSOR_OFFSET":"0","pageSize":1,"NETWORKING_CURSOR":"00013"} func (client *Client) openApiGetAllPages(apiVersion string, urlRef *url.URL, queryParams url.Values, outType interface{}, responses []json.RawMessage) ([]json.RawMessage, error) { // copy passed in URL ref so that it is not mutated urlRefCopy := copyUrlRef(urlRef) @@ -576,6 +589,35 @@ func (client *Client) openApiGetAllPages(apiVersion string, urlRef *url.URL, que } } + // If nextPage header was not found, but we are not at the last page - the query URL should be forged manually to + // overcome OpenAPI BUG when it does not return 'nextPage' header + // Some API calls do not return `OpenApiPages` results at all (just values) + if nextPageUrlRef == nil && pages.PageSize != 0 { + // Next URL page ref was not found therefore one must double check if it is not an API BUG. There are endpoints which + // return only Total results and pageSize (not 'pageCount' and not 'nextPage' header) + pageCount := pages.ResultTotal / pages.PageSize // This division returns number of "full pages" (containing 'pageSize' amount of results) + if pages.ResultTotal%pages.PageSize > 0 { // Check if is an incomplete page (containing less than 'pageSize' results) + pageCount++ // Total pageCount is "number of complete pages + 1 incomplete" if it exists) + } + if pages.Page < pageCount { + // Clone all originally supplied query parameters to avoid overwriting them + urlQueryString := queryParams.Encode() + urlQuery, err := url.ParseQuery(urlQueryString) + if err != nil { + return nil, fmt.Errorf("error cloning queryParams: %s", err) + } + + // Increase page query by one to fetch "next" page + urlQuery.Set("page", strconv.Itoa(pages.Page+1)) + + responses, err = client.openApiGetAllPages(apiVersion, urlRefCopy, urlQuery, outType, responses) + if err != nil { + return nil, fmt.Errorf("got error on page %d: %s", pages.Page, err) + } + } + + } + return responses, nil } diff --git a/govcd/openapi_endpoints.go b/govcd/openapi_endpoints.go index e782ea9ed..243b018ac 100644 --- a/govcd/openapi_endpoints.go +++ b/govcd/openapi_endpoints.go @@ -26,6 +26,7 @@ var endpointMinApiVersions = map[string]string{ types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointOrgVdcNetworks: "32.0", // VCD 9.7+ for NSX-V, 10.1+ for NSX-T types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointOrgVdcNetworksDhcp: "32.0", // VCD 9.7+ for NSX-V, 10.1+ for NSX-T types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcCapabilities: "32.0", + types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAppPortProfiles: "34.0", // VCD 10.1+ } // checkOpenApiEndpointCompatibility checks if VCD version (to which the client is connected) is sufficient to work with diff --git a/govcd/sample_govcd_test_config.yaml b/govcd/sample_govcd_test_config.yaml index da39bac42..51946162d 100644 --- a/govcd/sample_govcd_test_config.yaml +++ b/govcd/sample_govcd_test_config.yaml @@ -185,3 +185,8 @@ media: mediaPath: ../test-resources/test.iso # Existing media in test system mediaName: uploadedMediaName +misc: + # By default tests in this repository pick LDAP container 'rroemhild/test-openldap'. As docker throttles downloads + # it can help to host the image on local registry and pull it from there. This variable overrides default container + # location when set. + #ldapContainer: custom-registry.yyy/directory/test-openldap:latest diff --git a/govcd/vm_test.go b/govcd/vm_test.go index 1e281fbf8..77ee8b0b4 100644 --- a/govcd/vm_test.go +++ b/govcd/vm_test.go @@ -47,7 +47,7 @@ func (vcd *TestVCD) Test_FindVMByHREF(check *C) { // Test attach disk to VM and detach disk from VM func (vcd *TestVCD) Test_VMAttachOrDetachDisk(check *C) { // Find VM - if vcd.vapp.VApp == nil { + if vcd.vapp != nil && vcd.vapp.VApp == nil { check.Skip("skipping test because no vApp is found") } @@ -317,7 +317,7 @@ func (vcd *TestVCD) Test_HandleInsertOrEjectMedia(check *C) { itemName := "TestHandleInsertOrEjectMedia" // Find VApp - if vcd.vapp.VApp == nil { + if vcd.vapp != nil && vcd.vapp.VApp == nil { check.Skip("skipping test because no vApp is found") } @@ -384,7 +384,7 @@ func (vcd *TestVCD) Test_InsertOrEjectMedia(check *C) { itemName := "TestInsertOrEjectMedia" // Find VApp - if vcd.vapp.VApp == nil { + if vcd.vapp != nil && vcd.vapp.VApp == nil { check.Skip("skipping test because no vApp is found") } @@ -467,7 +467,7 @@ func (vcd *TestVCD) Test_AnswerVmQuestion(check *C) { itemName := "TestAnswerVmQuestion" // Find VApp - if vcd.vapp.VApp == nil { + if vcd.vapp != nil && vcd.vapp.VApp == nil { check.Skip("skipping test because no vApp is found") } @@ -1206,7 +1206,7 @@ func (vcd *TestVCD) Test_AddNewEmptyVMMultiNIC(check *C) { } // Find VApp - if vcd.vapp.VApp == nil { + if vcd.vapp != nil && vcd.vapp.VApp == nil { check.Skip("skipping test because no vApp is found") } diff --git a/types/v56/constants.go b/types/v56/constants.go index e52af4f23..40d362523 100644 --- a/types/v56/constants.go +++ b/types/v56/constants.go @@ -346,6 +346,7 @@ const ( OpenApiEndpointFirewallGroups = "firewallGroups/" OpenApiEndpointOrgVdcNetworks = "orgVdcNetworks/" OpenApiEndpointOrgVdcNetworksDhcp = "orgVdcNetworks/%s/dhcp" + OpenApiEndpointAppPortProfiles = "applicationPortProfiles/" ) // Header keys to run operations in tenant context @@ -393,3 +394,14 @@ const ( // Set FirewallGroupTypeIpSet = "IP_SET" ) + +const ( + // ApplicationPortProfileScopeSystem is a defined scope which allows user to only read (no write capability) system + // predefined Application Port Profiles + ApplicationPortProfileScopeSystem = "SYSTEM" + // ApplicationPortProfileScopeProvider allows user to read and set Application Port Profiles at provider level. In + // reality Network Provider (NSX-T Manager) must be specified while creating. + ApplicationPortProfileScopeProvider = "PROVIDER" + // ApplicationPortProfileScopeTenant allows user to read and set Application Port Profiles at Org VDC level. + ApplicationPortProfileScopeTenant = "TENANT" +) diff --git a/types/v56/nsxt_types.go b/types/v56/nsxt_types.go index c5ee481de..6cc37b8db 100644 --- a/types/v56/nsxt_types.go +++ b/types/v56/nsxt_types.go @@ -290,3 +290,44 @@ type NsxtFirewallGroupMemberVms struct { VdcRef *OpenApiReference `json:"vdcRef"` OrgRef *OpenApiReference `json:"orgRef"` } + +// NsxtAppPortProfile allows user to set custom application port definitions so that these can later be used +// in NSX-T Firewall rules in combination with IP Sets and Security Groups. +type NsxtAppPortProfile struct { + ID string `json:"id,omitempty"` + // Name must be unique per Scope + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + // ApplicationPorts contains one or more protocol and port definitions + ApplicationPorts []NsxtAppPortProfilePort `json:"applicationPorts,omitempty"` + // OrgRef must contain at least Org ID when SCOPE==TENANT + OrgRef *OpenApiReference `json:"orgRef,omitempty"` + // ContextEntityId must contain: + // * NSX-T Manager URN (when scope==PROVIDER) + // * VDC or VDC Group ID (when scope==TENANT) + ContextEntityId string `json:"contextEntityId,omitempty"` + // Scope can be one of the following: + // * SYSTEM - Read-only (The ones that are provided by SYSTEM). Constant `types.ApplicationPortProfileScopeSystem` + // * PROVIDER - Created by Provider on a particular network provider (NSX-T manager). Constant `types.ApplicationPortProfileScopeProvider` + // * TENANT (Created by Tenant at Org VDC level). Constant `types.ApplicationPortProfileScopeTenant` + // + // When scope==PROVIDER: + // OrgRef is not required + // ContextEntityId must have NSX-T Managers URN + // When scope==TENANT + // OrgRef ID must be specified + // ContextEntityId must be set to VDC or VDC group URN + Scope string `json:"scope,omitempty"` +} + +// NsxtAppPortProfilePort allows user to set protocol and one or more ports +type NsxtAppPortProfilePort struct { + // Protocol can be one of the following: + // * "ICMPv4" + // * "ICMPv6" + // * "TCP" + // * "UDP" + Protocol string `json:"protocol"` + // DestinationPorts is optional, but can define list of ports ("1000", "1500") or port ranges ("1200-1400") + DestinationPorts []string `json:"destinationPorts,omitempty"` +}