diff --git a/.changes/v3.0.0/716-features.md b/.changes/v3.0.0/716-features.md index 4b086217d..6cd1aa55f 100644 --- a/.changes/v3.0.0/716-features.md +++ b/.changes/v3.0.0/716-features.md @@ -1,3 +1,3 @@ -* Added `TmOrg` and `types.TmOrg` to structure for OpenAPI management of Organizations with methods +* Added `TmOrg` and `types.TmOrg` structure for OpenAPI management of Organizations with methods `VCDClient.CreateTmOrg`, `VCDClient.GetAllTmOrgs`, `VCDClient.GetTmOrgByName`, `VCDClient.GetTmOrgById`, `TmOrg.Update`, `TmOrg.Delete`, `TmOrg.Disable` [GH-716] diff --git a/.changes/v3.0.0/718-features.md b/.changes/v3.0.0/718-features.md new file mode 100644 index 000000000..838ae77df --- /dev/null +++ b/.changes/v3.0.0/718-features.md @@ -0,0 +1,18 @@ +* Added `VCDClient.GetNsxtManagerOpenApiByUrl` method to retrieve configured NSX-T Manager entry + based on URL [GH-718] +* Added `VCDClient.GetVCenterByUrl` method to retrieve configured vCenter server entry based on URL + [GH-718] +* Added `VCenter.RefreshStorageProfiles` to refresh storage profiles available in vCenter server + [GH-718] +* Added `Region` and `types.Region` to structure for OpenAPI management of Regions with methods + `VCDClient.CreateRegion`, `VCDClient.GetAllRegions`, `VCDClient.GetRegionByName`, + `VCDClient.GetRegionById`, `Region.Update`, `Region.Delete` [GH-718] +* Added `Supervisor` and `types.Supervisor` structure for reading available Supervisors + `VCDClient.GetAllSupervisors`, `VCDClient.GetSupervisorById`, `VCDClient.GetSupervisorByName`, + `VCDClient.GetSupervisorByNameAndVcenterId`, `Vcenter.GetAllSupervisors`, + `Vcenter.GetSupervisorByName` [GH-718] +* Added `SupervisorZone` and `types.SupervisorZone` structure for reading available Supervisor Zones + `Supervisor.GetAllSupervisorZones`, `Supervisor.GetSupervisorZoneById`, + `Supervisor.GetSupervisorZoneByName`, `Supervisor.`, `VCDClient.GetAllSupervisors`, + `VCDClient.GetSupervisorById`, `VCDClient.GetSupervisorByName`, `Vcenter.GetAllSupervisors` + [GH-718] diff --git a/govcd/api_vcd_test.go b/govcd/api_vcd_test.go index 7142f6e8f..2935a9f85 100644 --- a/govcd/api_vcd_test.go +++ b/govcd/api_vcd_test.go @@ -144,10 +144,13 @@ type TestConfig struct { Tm struct { RegionStoragePolicy string `yaml:"regionStoragePolicy"` - CreateVcenter bool `yaml:"createVcenter"` - VcenterUsername string `yaml:"vcenterUsername"` - VcenterPassword string `yaml:"vcenterPassword"` - VcenterUrl string `yaml:"vcenterUrl"` + CreateVcenter bool `yaml:"createVcenter"` + VcenterUsername string `yaml:"vcenterUsername"` + VcenterPassword string `yaml:"vcenterPassword"` + VcenterUrl string `yaml:"vcenterUrl"` + VcenterStorageProfile string `yaml:"vcenterStorageProfile"` + VcenterSupervisor string `yaml:"vcenterSupervisor"` + VcenterSupervisorZone string `yaml:"vcenterSupervisorZone"` CreateNsxtManager bool `yaml:"createNsxtManager"` NsxtManagerUsername string `yaml:"nsxtManagerUsername"` @@ -953,6 +956,26 @@ func (vcd *TestVCD) removeLeftoverEntities(entity CleanupEntity) { return } + vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) + case "OpenApiEntityVcenter": + vc, err := vcd.client.GetVCenterByName(entity.Name) + if err != nil { + vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) + return + } + + err = vc.Disable() + if err != nil { + vcd.infoCleanup("removeLeftoverEntries: [ERROR] Error disabling %s '%s': %s\n", entity.EntityType, entity.Name, err) + return + } + + err = vc.Delete() + if err != nil { + vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) + return + } + vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) case "vapp": vdc := vcd.vdc diff --git a/govcd/nsxt_manager_openapi.go b/govcd/nsxt_manager_openapi.go index 2a3d5a2c7..2e090c53d 100644 --- a/govcd/nsxt_manager_openapi.go +++ b/govcd/nsxt_manager_openapi.go @@ -82,6 +82,34 @@ func (vcdClient *VCDClient) GetNsxtManagerOpenApiByName(name string) (*NsxtManag return singleEntity, nil } +// GetNsxtManagerOpenApiByName retrieves NSX-T Manager by name +func (vcdClient *VCDClient) GetNsxtManagerOpenApiByUrl(nsxtManagerUrl string) (*NsxtManagerOpenApi, error) { + if nsxtManagerUrl == "" { + return nil, fmt.Errorf("%s lookup requires URL", labelNsxtManagerOpenApi) + } + + // API filtering by URL is not supported so relying on local filtering + nsxtManagers, err := vcdClient.GetAllNsxtManagersOpenApi(nil) + if err != nil { + return nil, err + } + + filteredEntities := make([]*NsxtManagerOpenApi, 0) + for _, nsxtManager := range nsxtManagers { + if nsxtManager.NsxtManagerOpenApi.Url == nsxtManagerUrl { + filteredEntities = append(filteredEntities, nsxtManager) + } + + } + + singleEntity, err := oneOrError("Url", nsxtManagerUrl, filteredEntities) + if err != nil { + return nil, err + } + + return singleEntity, nil +} + // Update NSX-T Manager configuration func (t *NsxtManagerOpenApi) Update(TmNsxtManagerConfig *types.NsxtManagerOpenApi) (*NsxtManagerOpenApi, error) { c := crudConfig{ diff --git a/govcd/openapi_endpoints.go b/govcd/openapi_endpoints.go index 707046409..019e3a1d0 100644 --- a/govcd/openapi_endpoints.go +++ b/govcd/openapi_endpoints.go @@ -160,6 +160,9 @@ var endpointMinApiVersions = map[string]string{ types.OpenApiPathVcf + types.OpenApiEndpointRegionStoragePolicies: "40.0", types.OpenApiPathVcf + types.OpenApiEndpointContentLibraries: "40.0", types.OpenApiPathVcf + types.OpenApiEndpointNsxManagers: "40.0", + types.OpenApiPathVcf + types.OpenApiEndpointRegions: "40.0", + types.OpenApiPathVcf + types.OpenApiEndpointSupervisors: "40.0", + types.OpenApiPathVcf + types.OpenApiEndpointSupervisorZones: "40.0", } // endpointElevatedApiVersions endpoint elevated API versions diff --git a/govcd/tm_region.go b/govcd/tm_region.go new file mode 100644 index 000000000..547c49fb2 --- /dev/null +++ b/govcd/tm_region.go @@ -0,0 +1,100 @@ +package govcd + +import ( + "fmt" + "net/url" + + "github.com/vmware/go-vcloud-director/v3/types/v56" +) + +const labelRegion = "Region" + +type Region struct { + Region *types.Region + vcdClient *VCDClient +} + +// wrap is a hidden helper that facilitates the usage of a generic CRUD function +// +//lint:ignore U1000 this method is used in generic functions, but annoys staticcheck +func (r Region) wrap(inner *types.Region) *Region { + r.Region = inner + return &r +} + +// CreateRegion creates a new region +func (vcdClient *VCDClient) CreateRegion(config *types.Region) (*Region, error) { + c := crudConfig{ + entityLabel: labelRegion, + endpoint: types.OpenApiPathVcf + types.OpenApiEndpointRegions, + } + outerType := Region{vcdClient: vcdClient} + return createOuterEntity(&vcdClient.Client, outerType, c, config) +} + +// GetAllRegions retrieves all Regions with an optional query filter +func (vcdClient *VCDClient) GetAllRegions(queryParameters url.Values) ([]*Region, error) { + c := crudConfig{ + entityLabel: labelRegion, + endpoint: types.OpenApiPathVcf + types.OpenApiEndpointRegions, + queryParameters: queryParameters, + } + + outerType := Region{vcdClient: vcdClient} + return getAllOuterEntities(&vcdClient.Client, outerType, c) +} + +// GetRegionByName retrieves a region by name +func (vcdClient *VCDClient) GetRegionByName(name string) (*Region, error) { + if name == "" { + return nil, fmt.Errorf("%s lookup requires name", labelRegion) + } + + queryParams := url.Values{} + queryParams.Add("filter", "name=="+name) + + filteredEntities, err := vcdClient.GetAllRegions(queryParams) + if err != nil { + return nil, err + } + + singleEntity, err := oneOrError("name", name, filteredEntities) + if err != nil { + return nil, err + } + + return vcdClient.GetRegionById(singleEntity.Region.ID) +} + +// GetRegionById retrieves a region by ID +func (vcdClient *VCDClient) GetRegionById(id string) (*Region, error) { + c := crudConfig{ + entityLabel: labelRegion, + endpoint: types.OpenApiPathVcf + types.OpenApiEndpointRegions, + endpointParams: []string{id}, + } + + outerType := Region{vcdClient: vcdClient} + return getOuterEntity(&vcdClient.Client, outerType, c) +} + +// Update Region with new configuration +func (r *Region) Update(RegionConfig *types.Region) (*Region, error) { + c := crudConfig{ + entityLabel: labelRegion, + endpoint: types.OpenApiPathVcf + types.OpenApiEndpointRegions, + endpointParams: []string{r.Region.ID}, + } + outerType := Region{vcdClient: r.vcdClient} + return updateOuterEntity(&r.vcdClient.Client, outerType, c, RegionConfig) +} + +// Delete Region +func (r *Region) Delete() error { + c := crudConfig{ + entityLabel: labelRegion, + endpoint: types.OpenApiPathVcf + types.OpenApiEndpointRegions, + endpointParams: []string{r.Region.ID}, + } + return deleteEntityById(&r.vcdClient.Client, c) +} diff --git a/govcd/tm_region_test.go b/govcd/tm_region_test.go new file mode 100644 index 000000000..571f4368c --- /dev/null +++ b/govcd/tm_region_test.go @@ -0,0 +1,163 @@ +//go:build tm || functional || ALL + +/* + * Copyright 2024 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + "net/url" + + "github.com/vmware/go-vcloud-director/v3/types/v56" + . "gopkg.in/check.v1" +) + +func (vcd *TestVCD) Test_TmRegion(check *C) { + skipNonTm(vcd, check) + sysadminOnly(vcd, check) + + vc, vcCreated, nsxtManager, nsxtManagerCreated := getOrCreateVcAndNsxtManager(vcd, check) + supervisor, err := vc.GetSupervisorByName(vcd.config.Tm.VcenterSupervisor) + check.Assert(err, IsNil) + + r := &types.Region{ + Name: check.TestName(), + NsxManager: &types.OpenApiReference{ + ID: nsxtManager.NsxtManagerOpenApi.ID, + }, + Supervisors: []types.OpenApiReference{ + { + ID: supervisor.Supervisor.SupervisorID, + Name: supervisor.Supervisor.Name, + }, + }, + StoragePolicies: []string{vcd.config.Tm.VcenterStorageProfile}, + IsEnabled: true, + } + + createdRegion, err := vcd.client.CreateRegion(r) + check.Assert(err, IsNil) + check.Assert(createdRegion.Region, NotNil) + AddToCleanupListOpenApi(createdRegion.Region.ID, check.TestName(), types.OpenApiPathVcf+types.OpenApiEndpointRegions+createdRegion.Region.ID) + + check.Assert(createdRegion.Region.Status, Equals, "READY") // Region is operational + + // Get By Name + byName, err := vcd.client.GetRegionByName(r.Name) + check.Assert(err, IsNil) + check.Assert(byName, NotNil) + + // Get By ID + byId, err := vcd.client.GetRegionById(createdRegion.Region.ID) + check.Assert(err, IsNil) + check.Assert(byId, NotNil) + + check.Assert(byName.Region, DeepEquals, byId.Region) + + // Get All + allRegions, err := vcd.client.GetAllRegions(nil) + check.Assert(err, IsNil) + check.Assert(allRegions, NotNil) + check.Assert(len(allRegions) > 0, Equals, true) + + // TODO: TM: No Update so far + // Update + // createdRegion.Region.IsEnabled = false + // updated, err := createdRegion.Update(createdRegion.Region) + // check.Assert(err, IsNil) + // check.Assert(updated, NotNil) + + // Delete + err = createdRegion.Delete() + check.Assert(err, IsNil) + + notFoundByName, err := vcd.client.GetRegionByName(createdRegion.Region.Name) + check.Assert(ContainsNotFound(err), Equals, true) + check.Assert(notFoundByName, IsNil) + + // Cleanup + if vcCreated { + err = vc.Disable() + check.Assert(err, IsNil) + err = vc.Delete() + check.Assert(err, IsNil) + } + + if nsxtManagerCreated { + err = nsxtManager.Delete() + check.Assert(err, IsNil) + } +} + +// getOrCreateVcAndNsxtManager will check configuration file and create vCenter and NSX-T Manager if +// stated in the 'createVcenter' and 'createNsxtManager' properties and they are not present in TM. +// Otherwise it just retrieves them +func getOrCreateVcAndNsxtManager(vcd *TestVCD, check *C) (*VCenter, bool, *NsxtManagerOpenApi, bool) { + vCenterCreated := false + nsxtManagerCreated := false + vc, err := vcd.client.GetVCenterByUrl(vcd.config.Tm.VcenterUrl) + if ContainsNotFound(err) && !vcd.config.Tm.CreateVcenter { + check.Skip("vCenter is not configured and configuration is not allowed in config file") + } + if ContainsNotFound(err) { + vcCfg := &types.VSphereVirtualCenter{ + Name: check.TestName() + "-vc", + Username: vcd.config.Tm.VcenterUsername, + Password: vcd.config.Tm.VcenterPassword, + Url: vcd.config.Tm.VcenterUrl, + IsEnabled: true, + } + // Certificate must be trusted before adding vCenter + url, err := url.Parse(vcCfg.Url) + check.Assert(err, IsNil) + trustedCert, err := vcd.client.AutoTrustCertificate(url) + check.Assert(err, IsNil) + if trustedCert != nil { + AddToCleanupListOpenApi(trustedCert.TrustedCertificate.ID, check.TestName()+"trusted-cert", types.OpenApiPathVersion1_0_0+types.OpenApiEndpointTrustedCertificates+trustedCert.TrustedCertificate.ID) + } + + vc, err = vcd.client.CreateVcenter(vcCfg) + check.Assert(err, IsNil) + check.Assert(vc, NotNil) + PrependToCleanupList(vcCfg.Name, "OpenApiEntityVcenter", check.TestName(), types.OpenApiPathVersion1_0_0+types.OpenApiEndpointVirtualCenters+vc.VSphereVCenter.VcId) + + vCenterCreated = true + // Refresh connected vCenter to be sure that all artifacts are loaded + printVerbose("# Refreshing vCenter %s\n", vc.VSphereVCenter.Url) + err = vc.Refresh() + check.Assert(err, IsNil) + + printVerbose("# Refreshing Storage Profiles in vCenter %s\n", vc.VSphereVCenter.Url) + err = vc.RefreshStorageProfiles() + check.Assert(err, IsNil) + } + + nsxtManager, err := vcd.client.GetNsxtManagerOpenApiByUrl(vcd.config.Tm.NsxtManagerUrl) + if ContainsNotFound(err) && !vcd.config.Tm.CreateNsxtManager { + check.Skip("NSX-T Manager is not configured and configuration is not allowed in config file") + } + if ContainsNotFound(err) { + nsxtCfg := &types.NsxtManagerOpenApi{ + Name: check.TestName(), + Username: vcd.config.Tm.NsxtManagerUsername, + Password: vcd.config.Tm.NsxtManagerPassword, + Url: vcd.config.Tm.NsxtManagerUrl, + } + // Certificate must be trusted before adding NSX-T Manager + url, err := url.Parse(nsxtCfg.Url) + check.Assert(err, IsNil) + trustedCert, err := vcd.client.AutoTrustCertificate(url) + check.Assert(err, IsNil) + if trustedCert != nil { + AddToCleanupListOpenApi(trustedCert.TrustedCertificate.ID, check.TestName()+"trusted-cert", types.OpenApiPathVersion1_0_0+types.OpenApiEndpointTrustedCertificates+trustedCert.TrustedCertificate.ID) + } + nsxtManager, err = vcd.client.CreateNsxtManagerOpenApi(nsxtCfg) + check.Assert(err, IsNil) + check.Assert(nsxtManager, NotNil) + PrependToCleanupListOpenApi(nsxtManager.NsxtManagerOpenApi.ID, check.TestName(), types.OpenApiPathVcf+types.OpenApiEndpointNsxManagers+nsxtManager.NsxtManagerOpenApi.ID) + nsxtManagerCreated = true + } + + return vc, vCenterCreated, nsxtManager, nsxtManagerCreated +} diff --git a/govcd/tm_supervisor.go b/govcd/tm_supervisor.go new file mode 100644 index 000000000..a225b6017 --- /dev/null +++ b/govcd/tm_supervisor.go @@ -0,0 +1,115 @@ +package govcd + +import ( + "fmt" + "net/url" + + "github.com/vmware/go-vcloud-director/v3/types/v56" +) + +const labelSupervisor = "Supervisor" + +// Supervisor is a type for reading available Supervisors +type Supervisor struct { + Supervisor *types.Supervisor + vcdClient *VCDClient +} + +// wrap is a hidden helper that facilitates the usage of a generic CRUD function +// +//lint:ignore U1000 this method is used in generic functions, but annoys staticcheck +func (s Supervisor) wrap(inner *types.Supervisor) *Supervisor { + s.Supervisor = inner + return &s +} + +// GetAllSupervisors retrieves all available Supervisors +func (vcdClient *VCDClient) GetAllSupervisors(queryParameters url.Values) ([]*Supervisor, error) { + c := crudConfig{ + endpoint: types.OpenApiPathVcf + types.OpenApiEndpointSupervisors, + entityLabel: labelSupervisor, + queryParameters: queryParameters, + } + + outerType := Supervisor{vcdClient: vcdClient} + return getAllOuterEntities(&vcdClient.Client, outerType, c) +} + +// GetSupervisorById retrieves supervisor by ID +func (vcdClient *VCDClient) GetSupervisorById(id string) (*Supervisor, error) { + c := crudConfig{ + entityLabel: labelSupervisor, + endpoint: types.OpenApiPathVcf + types.OpenApiEndpointSupervisors, + endpointParams: []string{id}, + } + + outerType := Supervisor{vcdClient: vcdClient} + return getOuterEntity(&vcdClient.Client, outerType, c) +} + +// GetSupervisorByName retrieves Supervisor by name +func (vcdClient *VCDClient) GetSupervisorByName(name string) (*Supervisor, error) { + if name == "" { + return nil, fmt.Errorf("%s lookup requires name", labelSupervisor) + } + + queryParams := url.Values{} + queryParams.Add("filter", "name=="+name) + + filteredEntities, err := vcdClient.GetAllSupervisors(queryParams) + if err != nil { + return nil, err + } + + singleEntity, err := oneOrError("name", name, filteredEntities) + if err != nil { + return nil, err + } + + return singleEntity, nil +} + +// GetSupervisorByNameAndVcenterId retrieves Supervisor by name and a required vCenter ID +func (vcdClient *VCDClient) GetSupervisorByNameAndVcenterId(supervisorName, vCenterId string) (*Supervisor, error) { + if supervisorName == "" || vCenterId == "" { + return nil, fmt.Errorf("%s lookup requires Name and vCenter ID", labelSupervisor) + } + + queryParams := url.Values{} + queryParams.Add("filter", fmt.Sprintf("name==%s;virtualCenter.id==%s", supervisorName, vCenterId)) + + filteredEntities, err := vcdClient.GetAllSupervisors(queryParams) + if err != nil { + return nil, err + } + + singleEntity, err := oneOrError("name", supervisorName, filteredEntities) + if err != nil { + return nil, err + } + + return singleEntity, nil +} + +// GetAllSupervisors returns all Supervisors that are available in this vCenter +func (v *VCenter) GetAllSupervisors(queryParameters url.Values) ([]*Supervisor, error) { + queryParams := copyOrNewUrlValues(queryParameters) + queryParams = queryParameterFilterAnd(fmt.Sprintf("virtualCenter.id==%s", v.VSphereVCenter.VcId), queryParams) + return v.client.GetAllSupervisors(queryParams) +} + +// GetSupervisorByName retrieves Supervisor by name in a given vCenter server +func (v *VCenter) GetSupervisorByName(name string) (*Supervisor, error) { + if name == "" { + return nil, fmt.Errorf("name is required") + } + + queryParams := copyOrNewUrlValues(nil) + queryParams = queryParameterFilterAnd(fmt.Sprintf("name==%s", name), queryParams) + s, err := v.GetAllSupervisors(queryParams) + if err != nil { + return nil, err + } + + return oneOrError("name", name, s) +} diff --git a/govcd/tm_supervisor_test.go b/govcd/tm_supervisor_test.go new file mode 100644 index 000000000..1b1239950 --- /dev/null +++ b/govcd/tm_supervisor_test.go @@ -0,0 +1,72 @@ +//go:build tm || functional || ALL + +/* + * Copyright 2024 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + . "gopkg.in/check.v1" +) + +func (vcd *TestVCD) Test_TmSupervisor(check *C) { + skipNonTm(vcd, check) + sysadminOnly(vcd, check) + + vc, vcCreated, nsxtManager, nsxtManagerCreated := getOrCreateVcAndNsxtManager(vcd, check) + + allSupervisors, err := vcd.client.GetAllSupervisors(nil) + check.Assert(err, IsNil) + check.Assert(len(allSupervisors) > 0, Equals, true) + + supervisorById, err := vcd.client.GetSupervisorById(allSupervisors[0].Supervisor.SupervisorID) + check.Assert(err, IsNil) + check.Assert(supervisorById.Supervisor, DeepEquals, allSupervisors[0].Supervisor) + + supervisorByName, err := vcd.client.GetSupervisorById(allSupervisors[0].Supervisor.SupervisorID) + check.Assert(err, IsNil) + check.Assert(supervisorByName.Supervisor, DeepEquals, allSupervisors[0].Supervisor) + + // vCenter Supervisors + allVcSupervisors, err := vc.GetAllSupervisors(nil) + check.Assert(err, IsNil) + check.Assert(len(allVcSupervisors) > 0, Equals, true) + + vcSupervisorById, err := vc.GetSupervisorByName(allSupervisors[0].Supervisor.Name) + check.Assert(err, IsNil) + check.Assert(vcSupervisorById.Supervisor, DeepEquals, allSupervisors[0].Supervisor) + + singleVcSupervisor, err := vcd.client.GetSupervisorByNameAndVcenterId(allVcSupervisors[0].Supervisor.Name, allVcSupervisors[0].Supervisor.VirtualCenter.ID) + check.Assert(err, IsNil) + check.Assert(singleVcSupervisor.Supervisor, DeepEquals, allVcSupervisors[0].Supervisor) + + // Checking Supervisor Zones + sZones, err := vcSupervisorById.GetAllSupervisorZones(nil) + check.Assert(err, IsNil) + check.Assert(len(sZones) > 0, Equals, true) + + zoneById, err := vcSupervisorById.GetSupervisorZoneById(sZones[0].SupervisorZone.ID) + check.Assert(err, IsNil) + check.Assert(zoneById, NotNil) + check.Assert(zoneById.SupervisorZone, DeepEquals, sZones[0].SupervisorZone) + + zoneByName, err := vcSupervisorById.GetSupervisorZoneByName(sZones[0].SupervisorZone.Name) + check.Assert(err, IsNil) + check.Assert(zoneByName, NotNil) + + check.Assert(zoneById.SupervisorZone, DeepEquals, zoneByName.SupervisorZone) + + // Cleanup + if vcCreated { + err = vc.Disable() + check.Assert(err, IsNil) + err = vc.Delete() + check.Assert(err, IsNil) + } + + if nsxtManagerCreated { + err = nsxtManager.Delete() + check.Assert(err, IsNil) + } +} diff --git a/govcd/tm_supervisor_zone.go b/govcd/tm_supervisor_zone.go new file mode 100644 index 000000000..ed3451d3c --- /dev/null +++ b/govcd/tm_supervisor_zone.go @@ -0,0 +1,79 @@ +package govcd + +import ( + "fmt" + "net/url" + + "github.com/vmware/go-vcloud-director/v3/types/v56" +) + +const labelSupervisorZone = "Supervisor Zone" + +// Supervisor is a type for reading Supervisor Zones +type SupervisorZone struct { + SupervisorZone *types.SupervisorZone + vcdClient *VCDClient +} + +// wrap is a hidden helper that facilitates the usage of a generic CRUD function +// +//lint:ignore U1000 this method is used in generic functions, but annoys staticcheck +func (s SupervisorZone) wrap(inner *types.SupervisorZone) *SupervisorZone { + s.SupervisorZone = inner + return &s +} + +// GetAllSupervisorZones retrieves all Supervisor Zones in a given Supervisor +func (s *Supervisor) GetAllSupervisorZones(queryParameters url.Values) ([]*SupervisorZone, error) { + c := crudConfig{ + endpoint: types.OpenApiPathVcf + types.OpenApiEndpointSupervisorZones, + entityLabel: labelSupervisorZone, + queryParameters: queryParameters, + } + + outerType := SupervisorZone{vcdClient: s.vcdClient} + return getAllOuterEntities(&s.vcdClient.Client, outerType, c) +} + +// GetSupervisorZoneById retrieves Supervisor by id +func (s *Supervisor) GetSupervisorZoneById(id string) (*SupervisorZone, error) { + if id == "" { + return nil, fmt.Errorf("'id' must be set") + } + + queryParams := copyOrNewUrlValues(nil) + queryParams = queryParameterFilterAnd("supervisor.id=="+s.Supervisor.SupervisorID, queryParams) + + c := crudConfig{ + endpoint: types.OpenApiPathVcf + types.OpenApiEndpointSupervisorZones, + entityLabel: labelSupervisorZone, + queryParameters: queryParams, + endpointParams: []string{id}, + } + + outerType := SupervisorZone{vcdClient: s.vcdClient} + return getOuterEntity(&s.vcdClient.Client, outerType, c) +} + +// GetSupervisorZoneByName retrieves Supervisor Zone by a given name +func (s *Supervisor) GetSupervisorZoneByName(name string) (*SupervisorZone, error) { + if name == "" { + return nil, fmt.Errorf("%s lookup requires name", labelSupervisor) + } + + queryParams := copyOrNewUrlValues(nil) + queryParams = queryParameterFilterAnd("supervisor.id=="+s.Supervisor.SupervisorID, queryParams) + queryParams = queryParameterFilterAnd("name=="+name, queryParams) + + filteredEntities, err := s.GetAllSupervisorZones(queryParams) + if err != nil { + return nil, err + } + + singleEntity, err := oneOrError("name", name, filteredEntities) + if err != nil { + return nil, err + } + + return singleEntity, nil +} diff --git a/govcd/vsphere_vcenter.go b/govcd/vsphere_vcenter.go index 7f3b3becf..4a9c622d1 100644 --- a/govcd/vsphere_vcenter.go +++ b/govcd/vsphere_vcenter.go @@ -71,6 +71,34 @@ func (vcdClient *VCDClient) GetVCenterByName(name string) (*VCenter, error) { return singleEntity, nil } +// GetVCenterByUrl looks up if there is an existing vCenter added with a given URL +func (vcdClient *VCDClient) GetVCenterByUrl(vcUrl string) (*VCenter, error) { + if vcUrl == "" { + return nil, fmt.Errorf("%s lookup requires URL", labelVirtualCenter) + } + + // API filtering by URL is not supported so relying on local filtering + vCenters, err := vcdClient.GetAllVCenters(nil) + if err != nil { + return nil, err + } + + filteredEntities := make([]*VCenter, 0) + for _, vc := range vCenters { + if vc.VSphereVCenter.Url == vcUrl { + filteredEntities = append(filteredEntities, vc) + } + + } + + singleEntity, err := oneOrError("Url", vcUrl, filteredEntities) + if err != nil { + return nil, err + } + + return singleEntity, nil +} + // GetVCenterById retrieves vCenter server by ID func (vcdClient *VCDClient) GetVCenterById(id string) (*VCenter, error) { c := crudConfig{ @@ -118,7 +146,7 @@ func (v VCenter) GetVimServerUrl() (string, error) { // Refresh triggers a refresh operation on vCenter that syncs up vCenter components such as // supervisors // It uses legacy endpoint as there is no OpenAPI endpoint for this operation -func (v VCenter) Refresh() error { +func (v *VCenter) Refresh() error { refreshUrl, err := url.JoinPath(v.client.Client.rootVcdHref(), "api", "admin", "extension", "vimServer", extractUuid(v.VSphereVCenter.VcId), "action", "refresh") if err != nil { return fmt.Errorf("error building refresh path: %s", err) @@ -141,3 +169,30 @@ func (v VCenter) Refresh() error { return nil } + +// RefreshStorageProfiles triggers a refresh operation on vCenter that syncs up vCenter components +// such as supervisors +// It uses legacy endpoint as there is no OpenAPI endpoint for this operation +func (v *VCenter) RefreshStorageProfiles() error { + refreshUrl, err := url.JoinPath(v.client.Client.rootVcdHref(), "api", "admin", "extension", "vimServer", extractUuid(v.VSphereVCenter.VcId), "action", "refreshStorageProfiles") + if err != nil { + return fmt.Errorf("error building storage policy refresh path: %s", err) + } + + resp, err := v.client.Client.executeJsonRequest(refreshUrl, http.MethodPost, nil, "error triggering vCenter refresh storage policy: %s") + if err != nil { + return err + } + defer closeBody(resp) + task := NewTask(&v.client.Client) + err = decodeBody(types.BodyTypeJSON, resp, task.Task) + if err != nil { + return fmt.Errorf("error triggering retrieving task: %s", err) + } + err = task.WaitTaskCompletion() + if err != nil { + return fmt.Errorf("error waiting task completion: %s", err) + } + + return nil +} diff --git a/types/v56/constants.go b/types/v56/constants.go index 0a06b3771..563cebbd8 100644 --- a/types/v56/constants.go +++ b/types/v56/constants.go @@ -523,6 +523,9 @@ const ( OpenApiEndpointRegionStoragePolicies = "regionStoragePolicies/" OpenApiEndpointContentLibraries = "contentLibraries/" OpenApiEndpointNsxManagers = "nsxManagers/" + OpenApiEndpointRegions = "regions/" + OpenApiEndpointSupervisors = "supervisors/" + OpenApiEndpointSupervisorZones = "supervisorZones/" ) // Header keys to run operations in tenant context diff --git a/types/v56/tm.go b/types/v56/tm.go index af7019557..13f58f266 100644 --- a/types/v56/tm.go +++ b/types/v56/tm.go @@ -112,3 +112,69 @@ type TmOrg struct { // VappCount contains vApp count in the Org VappCount int `json:"vappCount,omitempty"` } + +// Region represents a collection of supervisor clusters across different VCs +type Region struct { + ID string `json:"id,omitempty"` + // The name of the region. It must follow RFC 1123 Label Names to conform with Kubernetes standards. + Name string `json:"name"` + // The description of the region. + Description string `json:"description"` + // The NSX manager for the region. + NsxManager *OpenApiReference `json:"nsxManager"` + // Total CPU resources in MHz available to this Region. + CPUCapacityMHz int `json:"cpuCapacityMHz,omitempty"` + // Total CPU reservation resources in MHz available to this Region. + CPUReservationCapacityMHz int `json:"cpuReservationCapacityMHz,omitempty"` + // Whether the region is enabled or not. + IsEnabled bool `json:"isEnabled"` + // Total memory resources (in mebibytes) available to this Region. + MemoryCapacityMiB int `json:"memoryCapacityMiB,omitempty"` + // Total memory reservation resources (in mebibytes) available to this Region. + MemoryReservationCapacityMiB int `json:"memoryReservationCapacityMiB,omitempty"` + // The creation status of the Provider VDC. Possible values are READY, NOT_READY, ERROR, FAILED. + // A Region needs to be ready and enabled to be usable. + Status string `json:"status,omitempty"` + // A list of supervisors in a region + Supervisors []OpenApiReference `json:"supervisors,omitempty"` + // A list of distinct vCenter storage policy names from the vCenters taking part in this region. + // A storage policy with the given name must exist in all the vCenters of this region otherwise + // it will not be accepted. Only the storage policies added to a region can be published to the + // tenant Virtual Datacenters. + StoragePolicies []string `json:"storagePolicies,omitempty"` +} + +// Supervisor represents a single Supervisor within vCenter +type Supervisor struct { + // The immutable identifier of this supervisor. + SupervisorID string `json:"supervisorId"` + // The name of this supervisor. + Name string `json:"name"` + // The Region this Supervisor is associated with. If null, it has not been associated with a Region. + Region *OpenApiReference `json:"region,omitempty"` + // The vCenter this supervisor is associated with. + VirtualCenter *OpenApiReference `json:"virtualCenter"` +} + +// SupervisorZone represents a single zone within Supervisor +type SupervisorZone struct { + ID string `json:"id"` + // The name of this zone. + Name string `json:"name"` + // The supervisor this zone belongs to. + Supervisor *OpenApiReference `json:"supervisor"` + // The vCenter this supervisor zone is associated with. + VirtualCenter *OpenApiReference `json:"virtualCenter"` + // TotalMemoryCapacityMiB - the memory capacity (in mebibytes) in this zone. Total memory + // consumption in this zone cannot cross this limit + TotalMemoryCapacityMiB int64 `json:"totalMemoryCapacityMiB"` + // TotalCPUCapacityMHz - the CPU capacity (in MHz) in this zone. Total CPU consumption in this + // zone cannot cross this limit + TotalCPUCapacityMHz int64 `json:"totalCPUCapacityMHz"` + // MemoryUsedMiB - total memory used (in mebibytes) in this zone + MemoryUsedMiB int64 `json:"memoryUsedMiB"` + // CpuUsedMHz - total CPU used (in MHz) in this zone + CpuUsedMHz int64 `json:"cpuUsedMHz"` + // Region contains a reference to parent region + Region *OpenApiReference `json:"region"` +}