github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/api_vcd_test.go (about) 1 //go:build api || openapi || functional || catalog || vapp || gateway || network || org || query || extnetwork || task || vm || vdc || system || disk || lb || lbAppRule || lbAppProfile || lbServerPool || lbServiceMonitor || lbVirtualServer || user || search || nsxv || nsxt || auth || affinity || role || alb || certificate || vdcGroup || metadata || providervdc || rde || vsphere || uiPlugin || cse || ALL 2 3 /* 4 * Copyright 2022 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 5 */ 6 7 package govcd 8 9 import ( 10 "bufio" 11 "encoding/json" 12 "flag" 13 "fmt" 14 "gopkg.in/yaml.v2" 15 "net/http" 16 "net/url" 17 "os" 18 "path/filepath" 19 "regexp" 20 "runtime" 21 "strings" 22 "sync" 23 "testing" 24 "time" 25 26 . "gopkg.in/check.v1" 27 28 "github.com/vmware/go-vcloud-director/v2/types/v56" 29 "github.com/vmware/go-vcloud-director/v2/util" 30 ) 31 32 func init() { 33 testingTags["api"] = "api_vcd_test.go" 34 35 // To list the flags when we run "go test -tags functional -vcd-help", the flag name must start with "vcd" 36 // They will all appear alongside the native flags when we use an invalid one 37 setBoolFlag(&vcdHelp, "vcd-help", "VCD_HELP", "Show vcd flags") 38 setBoolFlag(&enableDebug, "vcd-debug", "GOVCD_DEBUG", "enables debug output") 39 setBoolFlag(&testVerbose, "vcd-verbose", "GOVCD_TEST_VERBOSE", "enables verbose output") 40 setBoolFlag(&skipVappCreation, "vcd-skip-vapp-creation", "GOVCD_SKIP_VAPP_CREATION", "Skips vApp creation") 41 setBoolFlag(&ignoreCleanupFile, "vcd-ignore-cleanup-file", "GOVCD_IGNORE_CLEANUP_FILE", "Does not process previous cleanup file") 42 setBoolFlag(&debugShowRequestEnabled, "vcd-show-request", "GOVCD_SHOW_REQ", "Shows API request") 43 setBoolFlag(&debugShowResponseEnabled, "vcd-show-response", "GOVCD_SHOW_RESP", "Shows API response") 44 setBoolFlag(&connectAsOrgUser, "vcd-as-org-user", "VCD_TEST_ORG_USER", "Connect as Org user") 45 setBoolFlag(&connectAsOrgUser, "vcd-test-org-user", "VCD_TEST_ORG_USER", "Connect as Org user") 46 flag.IntVar(&connectTenantNum, "vcd-connect-tenant", connectTenantNum, "change index of tenant to use (0=first)") 47 } 48 49 const ( 50 // Names for entities created by the tests 51 TestCreateOrg = "TestCreateOrg" 52 TestDeleteOrg = "TestDeleteOrg" 53 TestUpdateOrg = "TestUpdateOrg" 54 TestCreateCatalog = "TestCreateCatalog" 55 TestCreateCatalogDesc = "Catalog created by tests" 56 TestRefreshOrgFullName = "TestRefreshOrgFullName" 57 TestUpdateCatalog = "TestUpdateCatalog" 58 TestDeleteCatalog = "TestDeleteCatalog" 59 TestRefreshOrg = "TestRefreshOrg" 60 TestComposeVapp = "TestComposeVapp" 61 TestComposeVappDesc = "vApp created by tests" 62 TestSetUpSuite = "TestSetUpSuite" 63 TestUploadOvf = "TestUploadOvf" 64 TestDeleteCatalogItem = "TestDeleteCatalogItem" 65 TestCreateOrgVdc = "TestCreateOrgVdc" 66 TestRefreshOrgVdc = "TestRefreshOrgVdc" 67 TestCreateOrgVdcNetworkRouted = "TestCreateOrgVdcNetworkRouted" 68 TestCreateOrgVdcNetworkDhcp = "TestCreateOrgVdcNetworkDhcp" 69 TestCreateOrgVdcNetworkIso = "TestCreateOrgVdcNetworkIso" 70 TestCreateOrgVdcNetworkDirect = "TestCreateOrgVdcNetworkDirect" 71 TestUploadMedia = "TestUploadMedia" 72 TestCatalogUploadMedia = "TestCatalogUploadMedia" 73 TestCreateDisk = "TestCreateDisk" 74 TestUpdateDisk = "TestUpdateDisk" 75 TestDeleteDisk = "TestDeleteDisk" 76 TestRefreshDisk = "TestRefreshDisk" 77 TestAttachedVMDisk = "TestAttachedVMDisk" 78 TestVdcFindDiskByHREF = "TestVdcFindDiskByHREF" 79 TestFindDiskByHREF = "TestFindDiskByHREF" 80 TestDisk = "TestDisk" 81 // #nosec G101 -- Not a credential 82 TestVMAttachOrDetachDisk = "TestVMAttachOrDetachDisk" 83 TestVMAttachDisk = "TestVMAttachDisk" 84 TestVMDetachDisk = "TestVMDetachDisk" 85 TestCreateExternalNetwork = "TestCreateExternalNetwork" 86 TestDeleteExternalNetwork = "TestDeleteExternalNetwork" 87 TestLbServiceMonitor = "TestLbServiceMonitor" 88 TestLbServerPool = "TestLbServerPool" 89 TestLbAppProfile = "TestLbAppProfile" 90 TestLbAppRule = "TestLbAppRule" 91 TestLbVirtualServer = "TestLbVirtualServer" 92 TestLb = "TestLb" 93 TestNsxvSnatRule = "TestNsxvSnatRule" 94 TestNsxvDnatRule = "TestNsxvDnatRule" 95 ) 96 97 const ( 98 TestRequiresSysAdminPrivileges = "Test %s requires system administrator privileges" 99 ) 100 101 type Tenant struct { 102 User string `yaml:"user,omitempty"` 103 Password string `yaml:"password,omitempty"` 104 Token string `yaml:"token,omitempty"` 105 ApiToken string `yaml:"api_token,omitempty"` 106 SysOrg string `yaml:"sysOrg,omitempty"` 107 } 108 109 // Struct to get info from a config yaml file that the user 110 // specifies 111 type TestConfig struct { 112 Provider struct { 113 User string `yaml:"user"` 114 Password string `yaml:"password"` 115 Token string `yaml:"token"` 116 ApiToken string `yaml:"api_token"` 117 VcdVersion string `yaml:"vcdVersion,omitempty"` 118 ApiVersion string `yaml:"apiVersion,omitempty"` 119 120 // UseSamlAdfs specifies if SAML auth is used for authenticating vCD instead of local login. 121 // The above `User` and `Password` will be used to authenticate against ADFS IdP when true. 122 UseSamlAdfs bool `yaml:"useSamlAdfs"` 123 124 // CustomAdfsRptId allows to set custom Relaying Party Trust identifier if needed. Only has 125 // effect if `UseSamlAdfs` is true. 126 CustomAdfsRptId string `yaml:"customAdfsRptId"` 127 128 // The variables `SamlUser`, `SamlPassword` and `SamlCustomRptId` are optional and are 129 // related to an additional test run specifically with SAML user/password. It is useful in 130 // case local user is used for test run (defined by above 'User', 'Password' variables). 131 // SamlUser takes ADFS friendly format ('contoso.com\username' or 'username@contoso.com') 132 SamlUser string `yaml:"samlUser,omitempty"` 133 SamlPassword string `yaml:"samlPassword,omitempty"` 134 SamlCustomRptId string `yaml:"samlCustomRptId,omitempty"` 135 136 Url string `yaml:"url"` 137 SysOrg string `yaml:"sysOrg"` 138 MaxRetryTimeout int `yaml:"maxRetryTimeout,omitempty"` 139 HttpTimeout int64 `yaml:"httpTimeout,omitempty"` 140 } 141 Tenants []Tenant `yaml:"tenants,omitempty"` 142 VCD struct { 143 Org string `yaml:"org"` 144 Vdc string `yaml:"vdc"` 145 ProviderVdc struct { 146 Name string `yaml:"name"` 147 StorageProfile string `yaml:"storage_profile"` 148 NetworkPool string `yaml:"network_pool"` 149 } `yaml:"provider_vdc"` 150 NsxtProviderVdc struct { 151 Name string `yaml:"name"` 152 StorageProfile string `yaml:"storage_profile"` 153 StorageProfile2 string `yaml:"storage_profile_2"` 154 NetworkPool string `yaml:"network_pool"` 155 PlacementPolicyVmGroup string `yaml:"placementPolicyVmGroup,omitempty"` 156 } `yaml:"nsxt_provider_vdc"` 157 Catalog struct { 158 Name string `yaml:"name,omitempty"` 159 NsxtBackedCatalogName string `yaml:"nsxtBackedCatalogName,omitempty"` 160 Description string `yaml:"description,omitempty"` 161 CatalogItem string `yaml:"catalogItem,omitempty"` 162 CatalogItemWithEfiSupport string `yaml:"catalogItemWithEfiSupport,omitempty"` 163 NsxtCatalogItem string `yaml:"nsxtCatalogItem,omitempty"` 164 CatalogItemDescription string `yaml:"catalogItemDescription,omitempty"` 165 CatalogItemWithMultiVms string `yaml:"catalogItemWithMultiVms,omitempty"` 166 VmNameInMultiVmItem string `yaml:"vmNameInMultiVmItem,omitempty"` 167 } `yaml:"catalog"` 168 Network struct { 169 Net1 string `yaml:"network1"` 170 Net2 string `yaml:"network2,omitempty"` 171 } `yaml:"network"` 172 StorageProfile struct { 173 SP1 string `yaml:"storageProfile1"` 174 SP2 string `yaml:"storageProfile2,omitempty"` 175 } `yaml:"storageProfile"` 176 ExternalIp string `yaml:"externalIp,omitempty"` 177 ExternalNetmask string `yaml:"externalNetmask,omitempty"` 178 InternalIp string `yaml:"internalIp,omitempty"` 179 InternalNetmask string `yaml:"internalNetmask,omitempty"` 180 EdgeGateway string `yaml:"edgeGateway,omitempty"` 181 ExternalNetwork string `yaml:"externalNetwork,omitempty"` 182 ExternalNetworkPortGroup string `yaml:"externalNetworkPortGroup,omitempty"` 183 ExternalNetworkPortGroupType string `yaml:"externalNetworkPortGroupType,omitempty"` 184 VimServer string `yaml:"vimServer,omitempty"` 185 LdapServer string `yaml:"ldapServer,omitempty"` 186 Nsxt struct { 187 Manager string `yaml:"manager"` 188 Tier0router string `yaml:"tier0router"` 189 Tier0routerVrf string `yaml:"tier0routerVrf"` 190 NsxtDvpg string `yaml:"nsxtDvpg"` 191 GatewayQosProfile string `yaml:"gatewayQosProfile"` 192 Vdc string `yaml:"vdc"` 193 ExternalNetwork string `yaml:"externalNetwork"` 194 EdgeGateway string `yaml:"edgeGateway"` 195 NsxtImportSegment string `yaml:"nsxtImportSegment"` 196 NsxtImportSegment2 string `yaml:"nsxtImportSegment2"` 197 VdcGroup string `yaml:"vdcGroup"` 198 VdcGroupEdgeGateway string `yaml:"vdcGroupEdgeGateway"` 199 NsxtEdgeCluster string `yaml:"nsxtEdgeCluster"` 200 RoutedNetwork string `yaml:"routedNetwork"` 201 NsxtAlbControllerUrl string `yaml:"nsxtAlbControllerUrl"` 202 NsxtAlbControllerUser string `yaml:"nsxtAlbControllerUser"` 203 NsxtAlbControllerPassword string `yaml:"nsxtAlbControllerPassword"` 204 NsxtAlbImportableCloud string `yaml:"nsxtAlbImportableCloud"` 205 NsxtAlbServiceEngineGroup string `yaml:"nsxtAlbServiceEngineGroup"` 206 IpDiscoveryProfile string `yaml:"ipDiscoveryProfile"` 207 MacDiscoveryProfile string `yaml:"macDiscoveryProfile"` 208 SpoofGuardProfile string `yaml:"spoofGuardProfile"` 209 QosProfile string `yaml:"qosProfile"` 210 SegmentSecurityProfile string `yaml:"segmentSecurityProfile"` 211 } `yaml:"nsxt"` 212 } `yaml:"vcd"` 213 Vsphere struct { 214 ResourcePoolForVcd1 string `yaml:"resourcePoolForVcd1,omitempty"` 215 ResourcePoolForVcd2 string `yaml:"resourcePoolForVcd2,omitempty"` 216 } `yaml:"vsphere,omitempty"` 217 Logging struct { 218 Enabled bool `yaml:"enabled,omitempty"` 219 LogFileName string `yaml:"logFileName,omitempty"` 220 LogHttpRequest bool `yaml:"logHttpRequest,omitempty"` 221 LogHttpResponse bool `yaml:"logHttpResponse,omitempty"` 222 SkipResponseTags string `yaml:"skipResponseTags,omitempty"` 223 ApiLogFunctions string `yaml:"apiLogFunctions,omitempty"` 224 VerboseCleanup bool `yaml:"verboseCleanup,omitempty"` 225 } `yaml:"logging"` 226 OVA struct { 227 OvaPath string `yaml:"ovaPath,omitempty"` 228 OvaChunkedPath string `yaml:"ovaChunkedPath,omitempty"` 229 OvaMultiVmPath string `yaml:"ovaMultiVmPath,omitempty"` 230 OvaWithoutSizePath string `yaml:"ovaWithoutSizePath,omitempty"` 231 OvfPath string `yaml:"ovfPath,omitempty"` 232 OvfUrl string `yaml:"ovfUrl,omitempty"` 233 } `yaml:"ova"` 234 Media struct { 235 MediaPath string `yaml:"mediaPath,omitempty"` 236 Media string `yaml:"mediaName,omitempty"` 237 NsxtMedia string `yaml:"nsxtBackedMediaName,omitempty"` 238 PhotonOsOvaPath string `yaml:"photonOsOvaPath,omitempty"` 239 MediaUdfTypePath string `yaml:"mediaUdfTypePath,omitempty"` 240 UiPluginPath string `yaml:"uiPluginPath,omitempty"` 241 } `yaml:"media"` 242 Cse struct { 243 Version string `yaml:"version,omitempty"` 244 SolutionsOrg string `yaml:"solutionsOrg,omitempty"` 245 TenantOrg string `yaml:"tenantOrg,omitempty"` 246 TenantVdc string `yaml:"tenantVdc,omitempty"` 247 RoutedNetwork string `yaml:"routedNetwork,omitempty"` 248 EdgeGateway string `yaml:"edgeGateway,omitempty"` 249 StorageProfile string `yaml:"storageProfile,omitempty"` 250 OvaCatalog string `yaml:"ovaCatalog,omitempty"` 251 OvaName string `yaml:"ovaName,omitempty"` 252 } `yaml:"cse,omitempty"` 253 } 254 255 // Test struct for vcloud-director. 256 // Test functions use the struct to get 257 // an org, vdc, vapp, and client to run 258 // tests on 259 type TestVCD struct { 260 client *VCDClient 261 org *Org 262 vdc *Vdc 263 nsxtVdc *Vdc 264 vapp *VApp 265 config TestConfig 266 skipVappTests bool 267 skipAdminTests bool 268 } 269 270 // Cleanup entity structure used by the tear-down procedure 271 // at the end of the tests to remove leftover entities 272 type CleanupEntity struct { 273 Name string 274 EntityType string 275 Parent string 276 CreatedBy string 277 OpenApiEndpoint string 278 } 279 280 // CleanupInfo is the data used to persist an entity list in a file 281 type CleanupInfo struct { 282 VcdIp string // IP of the vCD where the test runs 283 Info string // Information about this file 284 EntityList []CleanupEntity // List of entities to remove 285 } 286 287 // Internally used by the test suite to run tests based on TestVCD structures 288 var _ = Suite(&TestVCD{}) 289 290 // The list holding the entities to be examined and eventually removed 291 // at the end of the tests 292 var cleanupEntityList []CleanupEntity 293 294 // Lock for of cleanup entities persistent file 295 var persistentCleanupListLock sync.Mutex 296 297 // IP of the vCD being tested. It is initialized at the first client authentication 298 var persistentCleanupIp string 299 300 // Use this value to run a specific test that does not need a pre-created vApp. 301 var skipVappCreation bool = os.Getenv("GOVCD_SKIP_VAPP_CREATION") != "" 302 303 // vcdHelp shows command line options 304 var vcdHelp bool 305 306 // enableDebug enables debug output 307 var enableDebug bool 308 309 // ignoreCleanupFile prevents processing a previous cleanup file 310 var ignoreCleanupFile bool 311 312 // connectAsOrgUser connects as Org user instead of System administrator 313 var connectAsOrgUser bool 314 var connectTenantNum int 315 316 // Makes the name for the cleanup entities persistent file 317 // Using a name for each vCD allows us to run tests with different servers 318 // and persist the cleanup list for all. 319 func makePersistentCleanupFileName() string { 320 var persistentCleanupListMask = "test_cleanup_list-%s.%s" 321 if persistentCleanupIp == "" { 322 fmt.Printf("######## Persistent Cleanup IP was not set ########\n") 323 os.Exit(1) 324 } 325 reForbiddenChars := regexp.MustCompile(`[/]`) 326 fileName := fmt.Sprintf(persistentCleanupListMask, 327 reForbiddenChars.ReplaceAllString(persistentCleanupIp, ""), "json") 328 return fileName 329 330 } 331 332 // Removes the list of cleanup entities 333 // To be called only after the list has been processed 334 func removePersistentCleanupList() { 335 persistentCleanupListFile := makePersistentCleanupFileName() 336 persistentCleanupListLock.Lock() 337 defer persistentCleanupListLock.Unlock() 338 _, err := os.Stat(persistentCleanupListFile) 339 if os.IsNotExist(err) { 340 return 341 } 342 _ = os.Remove(persistentCleanupListFile) 343 } 344 345 // Reads a cleanup list from file 346 func readCleanupList() ([]CleanupEntity, error) { 347 persistentCleanupListFile := makePersistentCleanupFileName() 348 persistentCleanupListLock.Lock() 349 defer persistentCleanupListLock.Unlock() 350 var cleanupInfo CleanupInfo 351 _, err := os.Stat(persistentCleanupListFile) 352 if os.IsNotExist(err) { 353 return nil, err 354 } 355 listText, err := os.ReadFile(filepath.Clean(persistentCleanupListFile)) 356 if err != nil { 357 return nil, err 358 } 359 err = json.Unmarshal(listText, &cleanupInfo) 360 return cleanupInfo.EntityList, err 361 } 362 363 // Writes a cleanup list to file. 364 // If the test suite terminates without having a chance to 365 // clean up properly, the next run of the test suite will try to 366 // remove the leftovers 367 func writeCleanupList(cleanupList []CleanupEntity) error { 368 persistentCleanupListFile := makePersistentCleanupFileName() 369 persistentCleanupListLock.Lock() 370 defer persistentCleanupListLock.Unlock() 371 cleanupInfo := CleanupInfo{ 372 VcdIp: persistentCleanupIp, 373 Info: "Persistent list of entities to be destroyed. " + 374 " If this file is found when starting the tests, its entities will be " + 375 " processed for removal before any other operation.", 376 EntityList: cleanupList, 377 } 378 listText, err := json.MarshalIndent(cleanupInfo, " ", " ") 379 if err != nil { 380 return err 381 } 382 file, err := os.Create(filepath.Clean(persistentCleanupListFile)) 383 if err != nil { 384 return err 385 } 386 writer := bufio.NewWriter(file) 387 count, err := writer.Write(listText) 388 if err != nil || count == 0 { 389 return err 390 } 391 err = writer.Flush() 392 if err != nil { 393 return err 394 } 395 return file.Close() 396 } 397 398 // AddToCleanupList adds an entity to the cleanup list. 399 // To be called by all tests when a new entity has been created, before 400 // running any other operation. 401 // Items in the list will be deleted at the end of the tests if they still exist. 402 func AddToCleanupList(name, entityType, parent, createdBy string) { 403 for _, item := range cleanupEntityList { 404 // avoid adding the same item twice 405 if item.Name == name && item.EntityType == entityType { 406 return 407 } 408 } 409 cleanupEntityList = append(cleanupEntityList, CleanupEntity{Name: name, EntityType: entityType, Parent: parent, CreatedBy: createdBy}) 410 err := writeCleanupList(cleanupEntityList) 411 if err != nil { 412 fmt.Printf("################ error writing cleanup list %s\n", err) 413 } 414 } 415 416 // PrependToCleanupList prepends an entity to the cleanup list. 417 // To be called by all tests when a new entity has been created, before 418 // running any other operation. 419 // Items in the list will be deleted at the end of the tests if they still exist. 420 func PrependToCleanupList(name, entityType, parent, createdBy string) { 421 for _, item := range cleanupEntityList { 422 // avoid adding the same item twice 423 if item.Name == name && item.EntityType == entityType { 424 return 425 } 426 } 427 cleanupEntityList = append([]CleanupEntity{{Name: name, EntityType: entityType, Parent: parent, CreatedBy: createdBy}}, cleanupEntityList...) 428 err := writeCleanupList(cleanupEntityList) 429 if err != nil { 430 fmt.Printf("################ error writing cleanup list %s\n", err) 431 } 432 } 433 434 // AddToCleanupListOpenApi adds an OpenAPI entity OpenApi objects `entityType=OpenApiEntity` and `openApiEndpoint`should 435 // be set in format "types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointOrgVdcNetworks + ID" 436 func AddToCleanupListOpenApi(name, createdBy, openApiEndpoint string) { 437 for _, item := range cleanupEntityList { 438 // avoid adding the same item twice 439 if item.OpenApiEndpoint == openApiEndpoint { 440 return 441 } 442 } 443 cleanupEntityList = append(cleanupEntityList, CleanupEntity{Name: name, EntityType: "OpenApiEntity", CreatedBy: createdBy, OpenApiEndpoint: openApiEndpoint}) 444 err := writeCleanupList(cleanupEntityList) 445 if err != nil { 446 fmt.Printf("################ error writing cleanup list %s\n", err) 447 } 448 } 449 450 // PrependToCleanupListOpenApi prepends an OpenAPI entity OpenApi objects `entityType=OpenApiEntity` and 451 // `openApiEndpoint`should be set in format "types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointOrgVdcNetworks + ID" 452 func PrependToCleanupListOpenApi(name, createdBy, openApiEndpoint string) { 453 for _, item := range cleanupEntityList { 454 // avoid adding the same item twice 455 if item.OpenApiEndpoint == openApiEndpoint { 456 return 457 } 458 } 459 cleanupEntityList = append([]CleanupEntity{{Name: name, EntityType: "OpenApiEntity", CreatedBy: createdBy, OpenApiEndpoint: openApiEndpoint}}, cleanupEntityList...) 460 err := writeCleanupList(cleanupEntityList) 461 if err != nil { 462 fmt.Printf("################ error writing cleanup list %s\n", err) 463 } 464 } 465 466 // Users use the environmental variable GOVCD_CONFIG as 467 // a config file for testing. Otherwise the default is govcd_test_config.yaml 468 // in the current directory. Throws an error if it cannot find your 469 // yaml file or if it cannot read it. 470 func GetConfigStruct() (TestConfig, error) { 471 config := os.Getenv("GOVCD_CONFIG") 472 var configStruct TestConfig 473 if config == "" { 474 // Finds the current directory, through the path of this running test 475 _, currentFilename, _, _ := runtime.Caller(0) 476 currentDirectory := filepath.Dir(currentFilename) 477 config = currentDirectory + "/govcd_test_config.yaml" 478 } 479 // Looks if the configuration file exists before attempting to read it 480 _, err := os.Stat(config) 481 if os.IsNotExist(err) { 482 return TestConfig{}, fmt.Errorf("Configuration file %s not found: %s", config, err) 483 } 484 yamlFile, err := os.ReadFile(filepath.Clean(config)) 485 if err != nil { 486 return TestConfig{}, fmt.Errorf("could not read config file %s: %s", config, err) 487 } 488 err = yaml.Unmarshal(yamlFile, &configStruct) 489 if err != nil { 490 return TestConfig{}, fmt.Errorf("could not unmarshal yaml file: %s", err) 491 } 492 if connectAsOrgUser { 493 if len(configStruct.Tenants) == 0 { 494 return TestConfig{}, fmt.Errorf("org user connection required, but 'tenants[%d]' is empty", connectTenantNum) 495 } 496 if connectTenantNum > len(configStruct.Tenants)-1 { 497 return TestConfig{}, fmt.Errorf("org user connection required, but tenant number %d is higher than the number of tenants ", connectTenantNum) 498 } 499 // Change configStruct.Provider, to reuse the global fields, such as URL 500 configStruct.Provider.User = configStruct.Tenants[connectTenantNum].User 501 configStruct.Provider.Password = configStruct.Tenants[connectTenantNum].Password 502 configStruct.Provider.SysOrg = configStruct.Tenants[connectTenantNum].SysOrg 503 configStruct.Provider.Token = configStruct.Tenants[connectTenantNum].Token 504 configStruct.Provider.ApiToken = configStruct.Tenants[connectTenantNum].ApiToken 505 } 506 return configStruct, nil 507 } 508 509 // Creates a VCDClient based on the endpoint given in the TestConfig argument. 510 // TestConfig struct can be obtained by calling GetConfigStruct. Throws an error 511 // if endpoint given is not a valid url. 512 func GetTestVCDFromYaml(testConfig TestConfig, options ...VCDClientOption) (*VCDClient, error) { 513 configUrl, err := url.ParseRequestURI(testConfig.Provider.Url) 514 if err != nil { 515 return &VCDClient{}, fmt.Errorf("could not parse Url: %s", err) 516 } 517 518 if testConfig.Provider.MaxRetryTimeout != 0 { 519 options = append(options, WithMaxRetryTimeout(testConfig.Provider.MaxRetryTimeout)) 520 } 521 522 if testConfig.Provider.HttpTimeout != 0 { 523 options = append(options, WithHttpTimeout(testConfig.Provider.HttpTimeout)) 524 } 525 526 if testConfig.Provider.UseSamlAdfs { 527 options = append(options, WithSamlAdfs(true, testConfig.Provider.CustomAdfsRptId)) 528 } 529 530 return NewVCDClient(*configUrl, true, options...), nil 531 } 532 533 // Necessary to enable the suite tests with TestVCD 534 func Test(t *testing.T) { TestingT(t) } 535 536 // Sets the org, vdc, vapp, and vcdClient for a 537 // TestVCD struct. An error is thrown if something goes wrong 538 // getting config file, creating vcd, during authentication, or 539 // when creating a new vapp. If this method panics, no test 540 // case that uses the TestVCD struct is run. 541 func (vcd *TestVCD) SetUpSuite(check *C) { 542 flag.Parse() 543 setTestEnv() 544 if vcdHelp { 545 fmt.Println("vcd flags:") 546 fmt.Println() 547 // Prints only the flags defined in this package 548 flag.CommandLine.VisitAll(func(f *flag.Flag) { 549 if strings.HasPrefix(f.Name, "vcd-") { 550 fmt.Printf(" -%-40s %s (%v)\n", f.Name, f.Usage, f.Value) 551 } 552 }) 553 fmt.Println() 554 // This will skip the whole suite. 555 // Instead, running os.Exit(0) will panic 556 check.Skip("--- showing help ---") 557 } 558 config, err := GetConfigStruct() 559 if config.Provider.Url == "" || err != nil { 560 panic(err) 561 } 562 vcd.config = config 563 564 // This library sets HTTP User-Agent to be `go-vcloud-director` by default and all HTTP calls 565 // expected to contain this header. An explicit test cannot capture future HTTP requests, but 566 // of them should use logging so this should be a good 'gate' to ensure ALL HTTP calls going out 567 // of this library do include HTTP User-Agent. 568 util.TogglePanicEmptyUserAgent(true) 569 570 if vcd.config.Logging.Enabled { 571 util.EnableLogging = true 572 if vcd.config.Logging.LogFileName != "" { 573 util.ApiLogFileName = vcd.config.Logging.LogFileName 574 } 575 if vcd.config.Logging.LogHttpRequest { 576 util.LogHttpRequest = true 577 } 578 if vcd.config.Logging.LogHttpResponse { 579 util.LogHttpResponse = true 580 } 581 if vcd.config.Logging.SkipResponseTags != "" { 582 util.SetSkipTags(vcd.config.Logging.SkipResponseTags) 583 } 584 if vcd.config.Logging.ApiLogFunctions != "" { 585 util.SetApiLogFunctions(vcd.config.Logging.ApiLogFunctions) 586 } 587 } else { 588 util.EnableLogging = false 589 } 590 util.SetLog() 591 vcdClient, err := GetTestVCDFromYaml(config) 592 if vcdClient == nil || err != nil { 593 panic(err) 594 } 595 vcd.client = vcdClient 596 token := os.Getenv("VCD_TOKEN") 597 if token == "" { 598 token = config.Provider.Token 599 } 600 601 apiToken := os.Getenv("VCD_API_TOKEN") 602 if apiToken == "" { 603 apiToken = config.Provider.ApiToken 604 } 605 606 authenticationMode := "password" 607 if apiToken != "" { 608 authenticationMode = "API-token" 609 err = vcd.client.SetToken(config.Provider.SysOrg, ApiTokenHeader, apiToken) 610 } else { 611 if token != "" { 612 authenticationMode = "token" 613 err = vcd.client.SetToken(config.Provider.SysOrg, AuthorizationHeader, token) 614 } else { 615 err = vcd.client.Authenticate(config.Provider.User, config.Provider.Password, config.Provider.SysOrg) 616 } 617 } 618 if config.Provider.UseSamlAdfs { 619 authenticationMode = "SAML password" 620 } 621 if err != nil { 622 panic(err) 623 } 624 versionInfo := "" 625 version, versionTime, err := vcd.client.Client.GetVcdVersion() 626 if err == nil { 627 versionInfo = fmt.Sprintf("version %s built at %s", version, versionTime) 628 } 629 fmt.Printf("Running on VCD %s (%s)\nas user %s@%s (using %s)\n", vcd.config.Provider.Url, versionInfo, 630 vcd.config.Provider.User, vcd.config.Provider.SysOrg, authenticationMode) 631 if !vcd.client.Client.IsSysAdmin { 632 vcd.skipAdminTests = true 633 } 634 635 // Sets the vCD IP value, removing the elements that would 636 // not be appropriate in a file name 637 reHttp := regexp.MustCompile(`^https?://`) 638 reApi := regexp.MustCompile(`/api/?`) 639 persistentCleanupIp = vcd.config.Provider.Url 640 persistentCleanupIp = reHttp.ReplaceAllString(persistentCleanupIp, "") 641 persistentCleanupIp = reApi.ReplaceAllString(persistentCleanupIp, "") 642 // set org 643 vcd.org, err = vcd.client.GetOrgByName(config.VCD.Org) 644 if err != nil { 645 fmt.Printf("error retrieving org %s: %s\n", config.VCD.Org, err) 646 os.Exit(1) 647 } 648 // set vdc 649 vcd.vdc, err = vcd.org.GetVDCByName(config.VCD.Vdc, false) 650 if err != nil || vcd.vdc == nil { 651 panic(err) 652 } 653 654 // configure NSX-T VDC for convenience if it is specified in configuration 655 if config.VCD.Nsxt.Vdc != "" { 656 vcd.nsxtVdc, err = vcd.org.GetVDCByName(config.VCD.Nsxt.Vdc, false) 657 if err != nil { 658 panic(fmt.Errorf("error geting NSX-T VDC '%s': %s", config.VCD.Nsxt.Vdc, err)) 659 } 660 } 661 662 // If neither the vApp or VM tags are set, we also skip the 663 // creation of the default vApp 664 if !isTagSet("vapp") && !isTagSet("vm") { 665 // vcd.skipVappTests = true 666 skipVappCreation = true 667 } 668 669 // Gets the persistent cleanup list from file, if exists. 670 cleanupList, err := readCleanupList() 671 if len(cleanupList) > 0 && err == nil { 672 if !ignoreCleanupFile { 673 // If we found a cleanup file and we want to process it (default) 674 // We proceed to cleanup the leftovers before any other operation 675 fmt.Printf("*** Found cleanup file %s\n", makePersistentCleanupFileName()) 676 for i, cleanupEntity := range cleanupList { 677 fmt.Printf("# %d ", i+1) 678 vcd.removeLeftoverEntities(cleanupEntity) 679 } 680 } 681 removePersistentCleanupList() 682 } 683 684 // creates a new VApp for vapp tests 685 if !skipVappCreation && config.VCD.Network.Net1 != "" && config.VCD.StorageProfile.SP1 != "" && 686 config.VCD.Catalog.Name != "" && config.VCD.Catalog.CatalogItem != "" { 687 // deployVappForTest replaces the old createTestVapp() because it was using bad implemented method vdc.ComposeVApp 688 vcd.vapp, err = deployVappForTest(vcd, TestSetUpSuite) 689 // If no vApp is created, we skip all vApp tests 690 if err != nil { 691 fmt.Printf("%s\n", err) 692 panic("Creation failed - Bailing out") 693 } 694 if vcd.vapp == nil { 695 fmt.Printf("Creation of vApp %s failed unexpectedly. No error was reported, but vApp is empty\n", TestSetUpSuite) 696 panic("initial vApp is empty - bailing out") 697 } 698 } else { 699 vcd.skipVappTests = true 700 fmt.Println("Skipping all vapp tests because one of the following wasn't given: Network, StorageProfile, Catalog, Catalogitem") 701 } 702 } 703 704 // Shows the detail of cleanup operations only if the relevant verbosity 705 // has been enabled 706 func (vcd *TestVCD) infoCleanup(format string, args ...interface{}) { 707 if vcd.config.Logging.VerboseCleanup { 708 fmt.Printf(format, args...) 709 } 710 } 711 712 func getOrgVdcByNames(vcd *TestVCD, orgName, vdcName string) (*Org, *Vdc, error) { 713 if orgName == "" || vdcName == "" { 714 return nil, nil, fmt.Errorf("orgName, vdcName cant be empty") 715 } 716 717 org, _ := vcd.client.GetOrgByName(orgName) 718 if org == nil { 719 vcd.infoCleanup("could not find org '%s'", orgName) 720 return nil, nil, fmt.Errorf("can't find org") 721 } 722 vdc, err := org.GetVDCByName(vdcName, false) 723 if err != nil { 724 vcd.infoCleanup("could not find vdc '%s'", vdcName) 725 return nil, nil, fmt.Errorf("can't find vdc") 726 } 727 728 return org, vdc, nil 729 } 730 731 func getOrgVdcEdgeByNames(vcd *TestVCD, orgName, vdcName, edgeName string) (*Org, *Vdc, *EdgeGateway, error) { 732 if orgName == "" || vdcName == "" || edgeName == "" { 733 return nil, nil, nil, fmt.Errorf("orgName, vdcName, edgeName cant be empty") 734 } 735 736 org, vdc, err := getOrgVdcByNames(vcd, orgName, vdcName) 737 if err != nil { 738 return nil, nil, nil, err 739 } 740 741 edge, err := vdc.GetEdgeGatewayByName(edgeName, false) 742 743 if err != nil { 744 vcd.infoCleanup("could not find edge '%s': %s", edgeName, err) 745 } 746 return org, vdc, edge, nil 747 } 748 749 var splitParentNotFound string = "removeLeftoverEntries: [ERROR] missing parent info (%s). The parent fields must be defined with a separator '|'\n" 750 var notFoundMsg string = "removeLeftoverEntries: [INFO] No action for %s '%s'\n" 751 752 func (vcd *TestVCD) getAdminOrgAndVdcFromCleanupEntity(entity CleanupEntity) (org *AdminOrg, vdc *Vdc, err error) { 753 orgName, vdcName, _ := splitParent(entity.Parent, "|") 754 if orgName == "" || vdcName == "" { 755 vcd.infoCleanup(splitParentNotFound, entity.Parent) 756 return nil, nil, fmt.Errorf("can't find parents names") 757 } 758 org, err = vcd.client.GetAdminOrgByName(orgName) 759 if err != nil { 760 vcd.infoCleanup(notFoundMsg, "org", orgName) 761 return nil, nil, fmt.Errorf("can't find org") 762 } 763 vdc, err = org.GetVDCByName(vdcName, false) 764 if vdc == nil || err != nil { 765 vcd.infoCleanup(notFoundMsg, "vdc", vdcName) 766 return nil, nil, fmt.Errorf("can't find vdc") 767 } 768 return org, vdc, nil 769 } 770 771 // Removes leftover entities that may still exist after failed tests 772 // or the ones that were explicitly created for several tests and 773 // were relying on this procedure to clean up at the end. 774 func (vcd *TestVCD) removeLeftoverEntities(entity CleanupEntity) { 775 var introMsg string = "removeLeftoverEntries: [INFO] Attempting cleanup of %s '%s' instantiated by %s\n" 776 var removedMsg string = "removeLeftoverEntries: [INFO] Removed %s '%s' created by %s\n" 777 var notDeletedMsg string = "removeLeftoverEntries: [ERROR] Error deleting %s '%s': %s\n" 778 // NOTE: this is a cleanup function that should continue even if errors are found. 779 // For this reason, the [ERROR] messages won't be followed by a program termination 780 vcd.infoCleanup(introMsg, entity.EntityType, entity.Name, entity.CreatedBy) 781 switch entity.EntityType { 782 783 // openApiEntity can be used to delete any OpenAPI entity due to the API being uniform and allowing the same 784 // low level OpenApiDeleteItem() 785 case "OpenApiEntity": 786 // entity.OpenApiEndpoint contains "endpoint/{ID}" 787 // (in format types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointOrgVdcNetworks + ID) but 788 // to lookup used API version this ID must not be present therefore below we remove suffix ID. 789 // This is done by splitting whole path by "/" and rebuilding path again without last element in slice (which is 790 // expected to be the ID) 791 // Sometimes API endpoint path might contain URNs in the middle (e.g. OpenApiEndpointNsxtNatRules). They are 792 // replaced back to string placeholder %s to match original definitions 793 endpointSlice := strings.Split(entity.OpenApiEndpoint, "/") 794 endpointWithUuid := strings.Join(endpointSlice[:len(endpointSlice)-1], "/") + "/" 795 // replace any "urns" (e.g. 'urn:vcloud:gateway:64966c36-e805-44e2-980b-c1077ab54956') with '%s' to match API definitions 796 re := regexp.MustCompile(`urn[^\/]+`) // Regexp matches from 'urn' up to next '/' in the path 797 endpointRemovedUuids := re.ReplaceAllString(endpointWithUuid, "%s") 798 apiVersion, _ := vcd.client.Client.checkOpenApiEndpointCompatibility(endpointRemovedUuids) 799 800 // Build UP complete endpoint address 801 urlRef, err := vcd.client.Client.OpenApiBuildEndpoint(entity.OpenApiEndpoint) 802 if err != nil { 803 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 804 return 805 } 806 807 // Validate if the resource still exists 808 err = vcd.client.Client.OpenApiGetItem(apiVersion, urlRef, nil, nil, nil) 809 810 // RDE Framework has a bug in VCD 10.3.0 that causes "not found" errors to return as "400 bad request", 811 // so we need to amend them 812 if strings.Contains(entity.OpenApiEndpoint, types.OpenApiEndpointRdeInterfaces) { 813 err = amendRdeApiError(&vcd.client.Client, err) 814 } 815 // UI Plugin has a bug in VCD 10.4.x that causes "not found" errors to return a NullPointerException, 816 // so we need to amend them 817 if strings.Contains(entity.OpenApiEndpoint, types.OpenApiEndpointExtensionsUi) { 818 err = amendUIPluginGetByIdError(entity.Name, err) 819 } 820 821 if ContainsNotFound(err) { 822 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 823 return 824 } 825 826 if err != nil { 827 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 828 return 829 } 830 831 if err != nil { 832 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 833 return 834 } 835 836 // Attempt to use supplied path in entity.Parent for element deletion 837 err = vcd.client.Client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil) 838 if err != nil { 839 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 840 return 841 } 842 843 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 844 // OpenApiEntityFirewall has different API structure therefore generic `OpenApiEntity` case does not fit cleanup 845 case "OpenApiEntityFirewall": 846 apiVersion, err := vcd.client.Client.checkOpenApiEndpointCompatibility(types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNsxtFirewallRules) 847 if err != nil { 848 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 849 return 850 } 851 852 urlRef, err := vcd.client.Client.OpenApiBuildEndpoint(entity.Name) 853 if err != nil { 854 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 855 return 856 } 857 858 // Attempt to use supplied path in entity.Parent for element deletion 859 err = vcd.client.Client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil) 860 if err != nil { 861 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 862 return 863 } 864 865 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 866 case "OpenApiEntityGlobalDefaultSegmentProfileTemplate": 867 // Check if any default settings are applied 868 gdSpt, err := vcd.client.GetGlobalDefaultSegmentProfileTemplates() 869 if err != nil { 870 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 871 return 872 } 873 874 if gdSpt.VappNetworkSegmentProfileTemplateRef == nil && gdSpt.VdcNetworkSegmentProfileTemplateRef == nil { 875 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 876 return 877 } 878 879 _, err = vcd.client.UpdateGlobalDefaultSegmentProfileTemplates(&types.NsxtGlobalDefaultSegmentProfileTemplate{}) 880 if err != nil { 881 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 882 return 883 } 884 885 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 886 // OpenApiEntityAlbSettingsDisable has different API structure therefore generic `OpenApiEntity` case does not fit cleanup 887 case "OpenApiEntityAlbSettingsDisable": 888 edge, err := vcd.nsxtVdc.GetNsxtEdgeGatewayByName(entity.Parent) 889 if err != nil { 890 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 891 return 892 } 893 894 edgeAlbSettingsConfig, err := edge.GetAlbSettings() 895 if err != nil { 896 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 897 return 898 } 899 if edgeAlbSettingsConfig.Enabled == false { 900 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 901 return 902 } 903 904 err = edge.DisableAlb() 905 if err != nil { 906 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 907 return 908 } 909 910 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 911 case "vapp": 912 vdc := vcd.vdc 913 var err error 914 915 // Check if parent VDC was specified. If not - use the default NSX-V VDC 916 if entity.Parent != "" { 917 vdc, err = vcd.org.GetVDCByName(entity.Parent, true) 918 if err != nil { 919 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 920 return 921 } 922 } 923 924 vapp, err := vdc.GetVAppByName(entity.Name, true) 925 if err != nil { 926 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 927 return 928 } 929 930 task, _ := vapp.Undeploy() 931 _ = task.WaitTaskCompletion() 932 // Detach all Org networks during vApp removal because network removal errors if it happens 933 // very quickly (as the next task) after vApp removal 934 task, _ = vapp.RemoveAllNetworks() 935 _ = task.WaitTaskCompletion() 936 task, err = vapp.Delete() 937 _ = task.WaitTaskCompletion() 938 939 if err != nil { 940 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 941 return 942 } 943 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 944 return 945 946 case "catalog": 947 if entity.Parent == "" { 948 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No Org provided for catalog '%s'\n", entity.Name) 949 return 950 } 951 org, err := vcd.client.GetAdminOrgByName(entity.Parent) 952 if err != nil { 953 vcd.infoCleanup("removeLeftoverEntries: [INFO] organization '%s' not found\n", entity.Parent) 954 return 955 } 956 catalog, err := org.GetAdminCatalogByName(entity.Name, false) 957 if catalog == nil || err != nil { 958 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 959 return 960 } 961 err = catalog.Delete(true, true) 962 if err != nil { 963 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 964 return 965 } 966 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 967 return 968 969 case "org": 970 org, err := vcd.client.GetAdminOrgByName(entity.Name) 971 if err != nil { 972 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 973 return 974 } 975 err = org.Delete(true, true) 976 if err != nil { 977 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 978 return 979 } 980 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 981 return 982 case "provider_vdc": 983 pvdc, err := vcd.client.GetProviderVdcExtendedByName(entity.Name) 984 if err != nil { 985 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 986 return 987 } 988 err = pvdc.Disable() 989 if err != nil { 990 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 991 return 992 } 993 task, err := pvdc.Delete() 994 if err != nil { 995 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 996 return 997 } 998 err = task.WaitTaskCompletion() 999 if err != nil { 1000 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1001 return 1002 } 1003 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1004 return 1005 case "catalogItem": 1006 if entity.Parent == "" { 1007 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No Org provided for catalogItem '%s'\n", strings.Split(entity.Parent, "|")[0]) 1008 return 1009 } 1010 org, err := vcd.client.GetAdminOrgByName(strings.Split(entity.Parent, "|")[0]) 1011 if err != nil { 1012 vcd.infoCleanup("removeLeftoverEntries: [INFO] organization '%s' not found\n", entity.Parent) 1013 return 1014 } 1015 catalog, err := org.GetCatalogByName(strings.Split(entity.Parent, "|")[1], false) 1016 if catalog == nil || err != nil { 1017 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1018 return 1019 } 1020 catalogItem, err := catalog.GetCatalogItemByName(entity.Name, false) 1021 if err != nil { 1022 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1023 return 1024 } 1025 err = catalogItem.Delete() 1026 if err != nil { 1027 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1028 } 1029 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1030 return 1031 case "edgegateway": 1032 _, vdc, err := vcd.getAdminOrgAndVdcFromCleanupEntity(entity) 1033 if err != nil { 1034 return 1035 } 1036 edge, err := vdc.GetEdgeGatewayByName(entity.Name, false) 1037 if err != nil { 1038 vcd.infoCleanup("removeLeftoverEntries: [INFO] edge gateway '%s' not found\n", entity.Name) 1039 return 1040 } 1041 err = edge.Delete(true, true) 1042 if err != nil { 1043 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1044 return 1045 } 1046 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1047 return 1048 case "network": 1049 _, vdc, err := vcd.getAdminOrgAndVdcFromCleanupEntity(entity) 1050 if err != nil { 1051 vcd.infoCleanup("%s", err) 1052 } 1053 _, errExists := vdc.GetOrgVdcNetworkByName(entity.Name, false) 1054 networkExists := errExists == nil 1055 1056 err = RemoveOrgVdcNetworkIfExists(*vdc, entity.Name) 1057 if err == nil { 1058 if networkExists { 1059 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1060 } else { 1061 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1062 } 1063 } else { 1064 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1065 } 1066 return 1067 case "affinity_rule": 1068 _, vdc, err := vcd.getAdminOrgAndVdcFromCleanupEntity(entity) 1069 if err != nil { 1070 vcd.infoCleanup("adminOrg + VDC: %s", err) 1071 return 1072 } 1073 affinityRule, err := vdc.GetVmAffinityRuleById(entity.Name) 1074 if err != nil { 1075 if ContainsNotFound(err) { 1076 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1077 return 1078 } else { 1079 vcd.infoCleanup("retrieving affinity rule %s", err) 1080 return 1081 } 1082 } 1083 err = affinityRule.Delete() 1084 if err != nil { 1085 vcd.infoCleanup("affinity rule deletion: %s", err) 1086 } 1087 return 1088 1089 case "externalNetwork": 1090 externalNetwork, err := vcd.client.GetExternalNetworkByName(entity.Name) 1091 if err != nil { 1092 vcd.infoCleanup(notFoundMsg, "externalNetwork", entity.Name) 1093 return 1094 } 1095 err = externalNetwork.DeleteWait() 1096 if err == nil { 1097 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1098 } else { 1099 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1100 } 1101 return 1102 case "mediaImage": 1103 if entity.Parent == "" { 1104 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No VDC and ORG provided for media '%s'\n", entity.Name) 1105 return 1106 } 1107 _, vdc, err := vcd.getAdminOrgAndVdcFromCleanupEntity(entity) 1108 if err != nil { 1109 vcd.infoCleanup("%s", err) 1110 } 1111 err = RemoveMediaImageIfExists(*vdc, entity.Name) 1112 if err == nil { 1113 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1114 } else { 1115 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1116 } 1117 return 1118 case "mediaCatalogImage": 1119 if entity.Parent == "" { 1120 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No Catalog provided for media '%s'\n", entity.Name) 1121 return 1122 } 1123 orgName, catalogName, _ := splitParent(entity.Parent, "|") 1124 if orgName == "" || catalogName == "" { 1125 vcd.infoCleanup(splitParentNotFound, entity.Parent) 1126 } 1127 1128 org, err := vcd.client.GetAdminOrgByName(orgName) 1129 if err != nil { 1130 vcd.infoCleanup("removeLeftoverEntries: [INFO] organization '%s' not found\n", entity.Parent) 1131 return 1132 } 1133 adminCatalog, err := org.GetAdminCatalogByName(catalogName, false) 1134 if err != nil { 1135 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1136 return 1137 } 1138 1139 _, err = adminCatalog.GetMediaByName(entity.Name, true) 1140 if ContainsNotFound(err) { 1141 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1142 return 1143 } 1144 err = adminCatalog.RemoveMediaIfExists(entity.Name) 1145 if err == nil { 1146 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1147 } else { 1148 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1149 } 1150 return 1151 case "user": 1152 if entity.Parent == "" { 1153 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No ORG provided for user '%s'\n", entity.Name) 1154 return 1155 } 1156 org, err := vcd.client.GetAdminOrgByName(entity.Parent) 1157 if err != nil { 1158 vcd.infoCleanup(notFoundMsg, "org", entity.Parent) 1159 return 1160 } 1161 user, err := org.GetUserByName(entity.Name, true) 1162 if err != nil { 1163 vcd.infoCleanup(notFoundMsg, "user", entity.Name) 1164 return 1165 } 1166 err = user.Delete(true) 1167 if err == nil { 1168 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1169 } else { 1170 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1171 } 1172 return 1173 case "group": 1174 if entity.Parent == "" { 1175 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No ORG provided for group '%s'\n", entity.Name) 1176 return 1177 } 1178 org, err := vcd.client.GetAdminOrgByName(entity.Parent) 1179 if err != nil { 1180 vcd.infoCleanup(notFoundMsg, "org", entity.Parent) 1181 return 1182 } 1183 group, err := org.GetGroupByName(entity.Name, true) 1184 if err != nil { 1185 vcd.infoCleanup(notFoundMsg, "group", entity.Name) 1186 return 1187 } 1188 err = group.Delete() 1189 if err == nil { 1190 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1191 } else { 1192 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1193 } 1194 return 1195 case "vdc": 1196 if entity.Parent == "" { 1197 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No ORG provided for VDC '%s'\n", entity.Name) 1198 return 1199 } 1200 org, err := vcd.client.GetAdminOrgByName(entity.Parent) 1201 if err != nil { 1202 vcd.infoCleanup(notFoundMsg, "org", entity.Parent) 1203 return 1204 } 1205 vdc, err := org.GetVDCByName(entity.Name, false) 1206 if vdc == nil || err != nil { 1207 vcd.infoCleanup(notFoundMsg, "vdc", entity.Name) 1208 return 1209 } 1210 err = vdc.DeleteWait(true, true) 1211 if err == nil { 1212 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1213 } else { 1214 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1215 } 1216 return 1217 case "nsxv_dfw": 1218 if entity.Parent == "" { 1219 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No ORG provided for VDC '%s'\n", entity.Name) 1220 return 1221 } 1222 org, err := vcd.client.GetAdminOrgByName(entity.Parent) 1223 if err != nil { 1224 vcd.infoCleanup(notFoundMsg, "org", entity.Parent) 1225 return 1226 } 1227 vdc, err := org.GetVDCByName(entity.Name, false) 1228 if vdc == nil || err != nil { 1229 vcd.infoCleanup(notFoundMsg, "vdc", entity.Name) 1230 return 1231 } 1232 dfw := NewNsxvDistributedFirewall(vdc.client, vdc.Vdc.ID) 1233 enabled, err := dfw.IsEnabled() 1234 if err != nil { 1235 vcd.infoCleanup("removeLeftoverEntries: [ERROR] checking distributed firewall from VCD '%s': %s", entity.Name, err) 1236 return 1237 } 1238 if !enabled { 1239 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1240 return 1241 } 1242 err = dfw.Disable() 1243 if err == nil { 1244 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1245 } else { 1246 vcd.infoCleanup("removeLeftoverEntries: [ERROR] removing distributed firewall from VCD '%s': %s", entity.Name, err) 1247 return 1248 } 1249 case "standaloneVm": 1250 vm, err := vcd.org.QueryVmById(entity.Name) // The VM ID must be passed as Name 1251 if IsNotFound(err) { 1252 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1253 return 1254 } 1255 if err != nil { 1256 vcd.infoCleanup("removeLeftoverEntries: [ERROR] retrieving standalone VM '%s'. %s\n", 1257 entity.Name, err) 1258 return 1259 } 1260 err = vm.Delete() 1261 if err != nil { 1262 vcd.infoCleanup("removeLeftoverEntries: [ERROR] deleting VM '%s' : %s\n", 1263 entity.Name, err) 1264 return 1265 } 1266 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1267 case "vm": 1268 vapp, err := vcd.vdc.GetVAppByName(entity.Parent, true) 1269 if err != nil { 1270 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1271 return 1272 } 1273 1274 vm, err := vapp.GetVMByName(entity.Name, false) 1275 1276 if err != nil { 1277 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1278 return 1279 } 1280 1281 // Try to undeploy and ignore errors if it doesn't work (VM may already be powered off) 1282 task, _ := vm.Undeploy() 1283 _ = task.WaitTaskCompletion() 1284 1285 err = vapp.RemoveVM(*vm) 1286 if err != nil { 1287 vcd.infoCleanup("removeLeftoverEntries: [ERROR] Deleting VM '%s' in vApp '%s': %s\n", 1288 entity.Name, vapp.VApp.Name, err) 1289 return 1290 } 1291 1292 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1293 return 1294 1295 case "disk": 1296 // Find disk by href rather than find disk by name, because disk name can be duplicated in VDC, 1297 // so the unique href is required for finding the disk. 1298 disk, err := vcd.vdc.GetDiskByHref(entity.Name) 1299 if err != nil { 1300 // If the disk is not found, we just need to show that it was not found, as 1301 // it was likely deleted during the regular tests 1302 vcd.infoCleanup(notFoundMsg, entity.Name, err) 1303 return 1304 } 1305 1306 // See if the disk is attached to the VM 1307 vmRef, err := disk.AttachedVM() 1308 if err != nil { 1309 vcd.infoCleanup("removeLeftoverEntries: [ERROR] Deleting %s '%s', cannot find attached VM: %s\n", 1310 entity.EntityType, entity.Name, err) 1311 return 1312 } 1313 // If the disk is attached to the VM, detach disk from the VM 1314 if vmRef != nil { 1315 vcd.infoCleanup("removeLeftoverEntries: [INFO] Deleting %s '%s', VM: '%s|%s', disk is attached, detaching disk\n", 1316 entity.EntityType, entity.Name, vmRef.Name, vmRef.HREF) 1317 1318 vm, err := vcd.client.Client.GetVMByHref(vmRef.HREF) 1319 if err != nil { 1320 vcd.infoCleanup( 1321 "removeLeftoverEntries: [ERROR] Deleting %s '%s', VM: '%s|%s', cannot find the VM details: %s\n", 1322 entity.EntityType, entity.Name, vmRef.Name, vmRef.HREF, err) 1323 return 1324 } 1325 1326 // Detach the disk from VM 1327 task, err := vm.DetachDisk(&types.DiskAttachOrDetachParams{ 1328 Disk: &types.Reference{ 1329 HREF: disk.Disk.HREF, 1330 }, 1331 }) 1332 if err != nil { 1333 vcd.infoCleanup( 1334 "removeLeftoverEntries: [ERROR] Detaching %s '%s', VM: '%s|%s': %s\n", 1335 entity.EntityType, entity.Name, vmRef.Name, vmRef.HREF, err) 1336 return 1337 } 1338 err = task.WaitTaskCompletion() 1339 if err != nil { 1340 vcd.infoCleanup( 1341 "removeLeftoverEntries: [ERROR] Deleting %s '%s', VM: '%s|%s', waitTaskCompletion of detach disk is failed: %s\n", 1342 entity.EntityType, entity.Name, vmRef.Name, vmRef.HREF, err) 1343 return 1344 } 1345 1346 // We need to refresh the disk info to obtain remove href for delete disk 1347 // because attached disk is not showing remove disk href in disk.Disk.Link 1348 err = disk.Refresh() 1349 if err != nil { 1350 vcd.infoCleanup( 1351 "removeLeftoverEntries: [ERROR] Deleting %s '%s', cannot refresh disk: %s\n", 1352 entity.EntityType, entity.Name, err) 1353 return 1354 } 1355 } 1356 1357 // Delete disk 1358 deleteDiskTask, err := disk.Delete() 1359 if err != nil { 1360 vcd.infoCleanup("removeLeftoverEntries: [ERROR] Deleting %s '%s', cannot delete disk: %s\n", 1361 entity.EntityType, entity.Name, err) 1362 return 1363 } 1364 err = deleteDiskTask.WaitTaskCompletion() 1365 if err != nil { 1366 vcd.infoCleanup("removeLeftoverEntries: [ERROR] Deleting %s '%s', waitTaskCompletion of delete disk is failed: %s\n", 1367 entity.EntityType, entity.Name, err) 1368 return 1369 } 1370 1371 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1372 return 1373 case "lbServiceMonitor": 1374 if entity.Parent == "" { 1375 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No parent specified '%s'\n", entity.Name) 1376 return 1377 } 1378 1379 orgName, vdcName, edgeName := splitParent(entity.Parent, "|") 1380 1381 _, _, edge, err := getOrgVdcEdgeByNames(vcd, orgName, vdcName, edgeName) 1382 if err != nil { 1383 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1384 } 1385 1386 err = edge.DeleteLbServiceMonitorByName(entity.Name) 1387 if err != nil && strings.Contains(err.Error(), ErrorEntityNotFound.Error()) { 1388 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1389 return 1390 } 1391 if err != nil { 1392 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1393 } 1394 1395 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1396 return 1397 1398 case "lbServerPool": 1399 if entity.Parent == "" { 1400 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No parent specified '%s'\n", entity.Name) 1401 return 1402 } 1403 1404 orgName, vdcName, edgeName := splitParent(entity.Parent, "|") 1405 1406 _, _, edge, err := getOrgVdcEdgeByNames(vcd, orgName, vdcName, edgeName) 1407 if err != nil { 1408 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1409 } 1410 1411 err = edge.DeleteLbServerPoolByName(entity.Name) 1412 if err != nil && strings.Contains(err.Error(), ErrorEntityNotFound.Error()) { 1413 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1414 return 1415 } 1416 if err != nil { 1417 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1418 } 1419 1420 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1421 return 1422 case "lbAppProfile": 1423 if entity.Parent == "" { 1424 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No parent specified '%s'\n", entity.Name) 1425 return 1426 } 1427 1428 orgName, vdcName, edgeName := splitParent(entity.Parent, "|") 1429 1430 _, _, edge, err := getOrgVdcEdgeByNames(vcd, orgName, vdcName, edgeName) 1431 if err != nil { 1432 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1433 } 1434 1435 err = edge.DeleteLbAppProfileByName(entity.Name) 1436 if err != nil && strings.Contains(err.Error(), ErrorEntityNotFound.Error()) { 1437 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1438 return 1439 } 1440 if err != nil { 1441 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1442 } 1443 1444 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1445 return 1446 1447 case "lbVirtualServer": 1448 if entity.Parent == "" { 1449 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No parent specified '%s'\n", entity.Name) 1450 return 1451 } 1452 1453 orgName, vdcName, edgeName := splitParent(entity.Parent, "|") 1454 1455 _, _, edge, err := getOrgVdcEdgeByNames(vcd, orgName, vdcName, edgeName) 1456 if err != nil { 1457 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1458 } 1459 1460 err = edge.DeleteLbVirtualServerByName(entity.Name) 1461 if err != nil && strings.Contains(err.Error(), ErrorEntityNotFound.Error()) { 1462 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1463 return 1464 } 1465 if err != nil { 1466 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1467 } 1468 1469 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1470 return 1471 case "lbAppRule": 1472 if entity.Parent == "" { 1473 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No parent specified '%s'\n", entity.Name) 1474 return 1475 } 1476 1477 orgName, vdcName, edgeName := splitParent(entity.Parent, "|") 1478 1479 _, _, edge, err := getOrgVdcEdgeByNames(vcd, orgName, vdcName, edgeName) 1480 if err != nil { 1481 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1482 } 1483 1484 err = edge.DeleteLbAppRuleByName(entity.Name) 1485 if err != nil && strings.Contains(err.Error(), ErrorEntityNotFound.Error()) { 1486 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1487 return 1488 } 1489 if err != nil { 1490 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1491 } 1492 1493 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1494 return 1495 // Edge gateway DHCP relay settings cannot actually be "deleted". They can only be unset and this is 1496 // what this cleanup case does. 1497 case "dhcpRelayConfig": 1498 if entity.Parent == "" { 1499 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No parent specified '%s'\n", entity.Name) 1500 return 1501 } 1502 1503 orgName, vdcName, edgeName := splitParent(entity.Parent, "|") 1504 1505 _, _, edge, err := getOrgVdcEdgeByNames(vcd, orgName, vdcName, edgeName) 1506 if err != nil { 1507 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1508 } 1509 1510 err = edge.ResetDhcpRelay() 1511 if err != nil { 1512 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1513 } 1514 1515 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1516 return 1517 1518 case "vdcMetaData": 1519 if entity.Parent == "" { 1520 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No VDC and ORG provided for vDC meta data '%s'\n", entity.Name) 1521 return 1522 } 1523 _, vdc, err := vcd.getAdminOrgAndVdcFromCleanupEntity(entity) 1524 if err != nil { 1525 vcd.infoCleanup("%s", err) 1526 } 1527 _, err = vdc.DeleteMetadata(entity.Name) 1528 if err == nil { 1529 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1530 } else { 1531 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1532 } 1533 return 1534 1535 case "nsxvNatRule": 1536 if entity.Parent == "" { 1537 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No parent specified '%s'\n", entity.Name) 1538 return 1539 } 1540 1541 orgName, vdcName, edgeName := splitParent(entity.Parent, "|") 1542 1543 _, _, edge, err := getOrgVdcEdgeByNames(vcd, orgName, vdcName, edgeName) 1544 if err != nil { 1545 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1546 } 1547 1548 err = edge.DeleteNsxvNatRuleById(entity.Name) 1549 if IsNotFound(err) { 1550 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1551 return 1552 } 1553 if err != nil { 1554 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1555 } 1556 1557 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1558 return 1559 1560 case "nsxvFirewallRule": 1561 if entity.Parent == "" { 1562 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No parent specified '%s'\n", entity.Name) 1563 return 1564 } 1565 1566 orgName, vdcName, edgeName := splitParent(entity.Parent, "|") 1567 1568 _, _, edge, err := getOrgVdcEdgeByNames(vcd, orgName, vdcName, edgeName) 1569 if err != nil { 1570 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1571 } 1572 1573 err = edge.DeleteNsxvFirewallRuleById(entity.Name) 1574 if IsNotFound(err) { 1575 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1576 return 1577 } 1578 if err != nil { 1579 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1580 } 1581 1582 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1583 return 1584 case "ipSet": 1585 if entity.Parent == "" { 1586 vcd.infoCleanup("removeLeftoverEntries: [ERROR] No parent specified '%s'\n", entity.Name) 1587 return 1588 } 1589 1590 orgName, vdcName, _ := splitParent(entity.Parent, "|") 1591 1592 _, vdc, err := getOrgVdcByNames(vcd, orgName, vdcName) 1593 if err != nil { 1594 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1595 } 1596 1597 err = vdc.DeleteNsxvIpSetByName(entity.Name) 1598 if IsNotFound(err) { 1599 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1600 return 1601 } 1602 if err != nil { 1603 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1604 } 1605 case "fastProvisioning": 1606 orgName, vdcName, _ := splitParent(entity.Parent, "|") 1607 if orgName == "" || vdcName == "" { 1608 vcd.infoCleanup(splitParentNotFound, entity.Parent) 1609 } 1610 org, err := vcd.client.GetAdminOrgByName(orgName) 1611 if err != nil { 1612 vcd.infoCleanup(notFoundMsg, "org", orgName) 1613 } 1614 adminVdc, err := org.GetAdminVDCByName(vdcName, false) 1615 if adminVdc == nil || err != nil { 1616 vcd.infoCleanup(notFoundMsg, "vdc", vdcName) 1617 } 1618 fastProvisioningValue := false 1619 if entity.Name == "enable" { 1620 fastProvisioningValue = true 1621 } 1622 1623 if adminVdc != nil && *adminVdc.AdminVdc.UsesFastProvisioning != fastProvisioningValue { 1624 adminVdc.AdminVdc.UsesFastProvisioning = &fastProvisioningValue 1625 _, err = adminVdc.Update() 1626 if err != nil { 1627 vcd.infoCleanup("updateLeftoverEntries: [INFO] revert back VDC fast provisioning value % s failed\n", entity.Name) 1628 return 1629 } 1630 vcd.infoCleanup("updateLeftoverEntries: [INFO] reverted back VDC fast provisioning value %s \n", entity.Name) 1631 } else { 1632 vcd.infoCleanup("updateLeftoverEntries: [INFO] VDC fast provisioning left as it is %s \n", entity.Name) 1633 } 1634 return 1635 case "orgLdapSettings": 1636 org, err := vcd.client.GetAdminOrgByName(entity.Parent) 1637 if err != nil { 1638 vcd.infoCleanup("removeLeftoverEntries: [ERROR] Clearing LDAP settings for Org '%s': %s", 1639 entity.Parent, err) 1640 return 1641 } 1642 1643 ldapConfig, err := org.GetLdapConfiguration() 1644 if err != nil { 1645 vcd.infoCleanup("removeLeftoverEntries: [ERROR] Couldn't get LDAP settings for Org '%s': %s", 1646 entity.Parent, err) 1647 return 1648 } 1649 1650 // This is done to avoid calling LdapDisable() if it has been unconfigured, due to bug with Org catalog publish settings 1651 if ldapConfig.OrgLdapMode != types.LdapModeNone { 1652 err = org.LdapDisable() 1653 if err != nil { 1654 vcd.infoCleanup("removeLeftoverEntries: [ERROR] Could not clear LDAP settings for Org '%s': %s", 1655 entity.Parent, err) 1656 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1657 return 1658 } 1659 } 1660 1661 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1662 return 1663 1664 case "vdcComputePolicy": 1665 policy, err := vcd.client.GetVdcComputePolicyV2ById(entity.Name) 1666 if policy == nil || err != nil { 1667 vcd.infoCleanup(notFoundMsg, "vdcComputePolicy", entity.Name) 1668 return 1669 } 1670 err = policy.Delete() 1671 if err == nil { 1672 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1673 } else { 1674 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1675 } 1676 return 1677 1678 case "logicalVmGroup": 1679 logicalVmGroup, err := vcd.client.GetLogicalVmGroupById(entity.Name) 1680 if logicalVmGroup == nil || err != nil { 1681 vcd.infoCleanup(notFoundMsg, "logicalVmGroup", entity.Name) 1682 return 1683 } 1684 err = logicalVmGroup.Delete() 1685 if err == nil { 1686 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1687 } else { 1688 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1689 } 1690 return 1691 1692 case "nsxtDhcpForwarder": 1693 edge, err := vcd.nsxtVdc.GetNsxtEdgeGatewayByName(entity.Name) 1694 if err != nil { 1695 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1696 } 1697 1698 dhcpForwarder, err := edge.GetDhcpForwarder() 1699 if err != nil { 1700 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1701 } 1702 1703 if dhcpForwarder.Enabled == false && len(dhcpForwarder.DhcpServers) == 0 { 1704 vcd.infoCleanup(notFoundMsg, "dhcpForwarder", entity.Name) 1705 return 1706 } 1707 1708 _, err = edge.UpdateDhcpForwarder(&types.NsxtEdgeGatewayDhcpForwarder{}) 1709 if err != nil { 1710 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1711 } 1712 1713 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1714 return 1715 case "nsxtEdgeGatewayDns": 1716 edge, err := vcd.nsxtVdc.GetNsxtEdgeGatewayByName(entity.Name) 1717 if err != nil { 1718 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1719 } 1720 1721 dns, err := edge.GetDnsConfig() 1722 if err != nil { 1723 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1724 } 1725 1726 if dns.NsxtEdgeGatewayDns.Enabled == false && dns.NsxtEdgeGatewayDns.DefaultForwarderZone == nil { 1727 vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name) 1728 return 1729 } 1730 1731 err = dns.Delete() 1732 if err != nil { 1733 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1734 } 1735 1736 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1737 return 1738 case "slaacProfile": 1739 edge, err := vcd.nsxtVdc.GetNsxtEdgeGatewayByName(entity.Name) 1740 if err != nil { 1741 vcd.infoCleanup("removeLeftoverEntries: [ERROR] %s \n", err) 1742 } 1743 1744 _, err = edge.UpdateSlaacProfile(&types.NsxtEdgeGatewaySlaacProfile{Enabled: false, Mode: "SLAAC"}) 1745 if err != nil { 1746 vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) 1747 } 1748 1749 vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) 1750 return 1751 1752 default: 1753 // If we reach this point, we are trying to clean up an entity that 1754 // we aren't prepared for yet. 1755 fmt.Printf("removeLeftoverEntries: [ERROR] Unrecognized type %s for entity '%s'\n", 1756 entity.EntityType, entity.Name) 1757 } 1758 } 1759 1760 func (vcd *TestVCD) TearDownSuite(check *C) { 1761 // We will try to remove every entity that has been registered into 1762 // CleanupEntityList. Entities that have already been cleaned up by their 1763 // functions will be ignored. 1764 for i, cleanupEntity := range cleanupEntityList { 1765 fmt.Printf("# %d of %d - ", i+1, len(cleanupEntityList)) 1766 vcd.removeLeftoverEntities(cleanupEntity) 1767 removePersistentCleanupList() 1768 } 1769 } 1770 1771 // Tests getloginurl with the endpoint given 1772 // in the config file. 1773 func (vcd *TestVCD) TestClient_getloginurl(check *C) { 1774 if os.Getenv("GOVCD_API_VERSION") != "" { 1775 check.Skip("custom API version is being used") 1776 } 1777 config, err := GetConfigStruct() 1778 if err != nil { 1779 check.Fatalf("err: %s", err) 1780 } 1781 client, err := GetTestVCDFromYaml(config) 1782 if err != nil { 1783 check.Fatalf("err: %s", err) 1784 } 1785 1786 err = client.vcdloginurl() 1787 if err != nil { 1788 check.Fatalf("err: %s", err) 1789 } 1790 1791 if client.sessionHREF.Path != "/cloudapi/1.0.0/sessions" { 1792 check.Fatalf("Getting LoginUrl failed, url: %s", client.sessionHREF.Path) 1793 } 1794 } 1795 1796 // Tests Authenticate with the vcd credentials (or token) given in the config file 1797 func (vcd *TestVCD) TestVCDClient_Authenticate(check *C) { 1798 config, err := GetConfigStruct() 1799 if err != nil { 1800 check.Fatalf("err: %s", err) 1801 } 1802 client, err := GetTestVCDFromYaml(config) 1803 if err != nil { 1804 check.Fatalf("err: %s", err) 1805 } 1806 apiToken := os.Getenv("VCD_API_TOKEN") 1807 if apiToken == "" { 1808 apiToken = config.Provider.ApiToken 1809 } 1810 if apiToken != "" { 1811 err = client.SetToken(config.Provider.SysOrg, ApiTokenHeader, apiToken) 1812 } else { 1813 token := os.Getenv("VCD_TOKEN") 1814 if token == "" { 1815 token = config.Provider.Token 1816 } 1817 if token != "" { 1818 err = client.SetToken(config.Provider.SysOrg, AuthorizationHeader, token) 1819 } else { 1820 err = client.Authenticate(config.Provider.User, config.Provider.Password, config.Provider.SysOrg) 1821 } 1822 } 1823 1824 if err != nil { 1825 check.Fatalf("Error authenticating: %s", err) 1826 } 1827 } 1828 1829 func (vcd *TestVCD) TestVCDClient_AuthenticateInvalidPassword(check *C) { 1830 config, err := GetConfigStruct() 1831 if err != nil { 1832 check.Fatalf("err: %s", err) 1833 } 1834 client, err := GetTestVCDFromYaml(config) 1835 if err != nil { 1836 check.Fatalf("error getting client structure: %s", err) 1837 } 1838 1839 err = client.Authenticate(config.Provider.User, "INVALID-PASSWORD", config.Provider.SysOrg) 1840 if err == nil || !strings.Contains(err.Error(), "401") { 1841 check.Fatalf("expected error for invalid credentials") 1842 } 1843 } 1844 1845 func (vcd *TestVCD) TestVCDClient_AuthenticateInvalidToken(check *C) { 1846 config, err := GetConfigStruct() 1847 if err != nil { 1848 check.Fatalf("err: %s", err) 1849 } 1850 client, err := GetTestVCDFromYaml(config) 1851 if err != nil { 1852 check.Fatalf("error getting client structure: %s", err) 1853 } 1854 1855 err = client.SetToken(config.Provider.SysOrg, AuthorizationHeader, "invalid-token") 1856 if err == nil || !strings.Contains(err.Error(), "401") { 1857 check.Fatalf("expected error for invalid credentials") 1858 } 1859 } 1860 1861 func (vcd *TestVCD) findFirstVm(vapp VApp) (types.Vm, string) { 1862 for _, vm := range vapp.VApp.Children.VM { 1863 if vm.Name != "" { 1864 return *vm, vm.Name 1865 } 1866 } 1867 return types.Vm{}, "" 1868 } 1869 1870 func (vcd *TestVCD) findFirstVapp() VApp { 1871 client := vcd.client 1872 config := vcd.config 1873 org, err := client.GetOrgByName(config.VCD.Org) 1874 if err != nil { 1875 fmt.Println(err) 1876 return VApp{} 1877 } 1878 vdc, err := org.GetVDCByName(config.VCD.Vdc, false) 1879 if err != nil { 1880 fmt.Println(err) 1881 return VApp{} 1882 } 1883 wantedVapp := vcd.vapp.VApp.Name 1884 if wantedVapp == "" { 1885 // As no vApp is defined in config, we search for one randomly 1886 for _, res := range vdc.Vdc.ResourceEntities { 1887 for _, item := range res.ResourceEntity { 1888 if item.Type == "application/vnd.vmware.vcloud.vApp+xml" { 1889 wantedVapp = item.Name 1890 break 1891 } 1892 } 1893 } 1894 } 1895 vapp, err := vdc.GetVAppByName(wantedVapp, false) 1896 if err != nil { 1897 return VApp{} 1898 } 1899 return *vapp 1900 } 1901 1902 // Test_NewRequestWitNotEncodedParamsWithApiVersion verifies that api version override works 1903 func (vcd *TestVCD) Test_NewRequestWitNotEncodedParamsWithApiVersion(check *C) { 1904 fmt.Printf("Running: %s\n", check.TestName()) 1905 queryUlr := vcd.client.Client.VCDHREF 1906 queryUlr.Path += "/query" 1907 1908 apiVersion, err := vcd.client.Client.MaxSupportedVersion() 1909 check.Assert(err, IsNil) 1910 1911 req := vcd.client.Client.NewRequestWitNotEncodedParamsWithApiVersion(nil, map[string]string{"type": "media", 1912 "filter": "name==any"}, http.MethodGet, queryUlr, nil, apiVersion) 1913 1914 check.Assert(req.Header.Get("User-Agent"), Equals, vcd.client.Client.UserAgent) 1915 1916 resp, err := checkResp(vcd.client.Client.Http.Do(req)) 1917 check.Assert(err, IsNil) 1918 1919 check.Assert(resp.Header.Get("Content-Type"), Equals, types.MimeQueryRecords+";version="+apiVersion) 1920 1921 // Repeats the call without API version change 1922 req = vcd.client.Client.NewRequestWitNotEncodedParams(nil, map[string]string{"type": "media", 1923 "filter": "name==any"}, http.MethodGet, queryUlr, nil) 1924 1925 resp, err = checkResp(vcd.client.Client.Http.Do(req)) 1926 check.Assert(err, IsNil) 1927 1928 // Checks that the regularAPI version was not affected by the previous call 1929 check.Assert(resp.Header.Get("Content-Type"), Equals, types.MimeQueryRecords+";version="+vcd.client.Client.APIVersion) 1930 1931 fmt.Printf("Test: %s run with api Version: %s\n", check.TestName(), apiVersion) 1932 } 1933 1934 // setBoolFlag binds a flag to a boolean variable (passed as pointer) 1935 // it also uses an optional environment variable that, if set, will 1936 // update the variable before binding it to the flag. 1937 func setBoolFlag(varPointer *bool, name, envVar, help string) { 1938 if envVar != "" && os.Getenv(envVar) != "" { 1939 *varPointer = true 1940 } 1941 flag.BoolVar(varPointer, name, *varPointer, help) 1942 } 1943 1944 // setTestEnv enables environment variables that are also used in non-test code 1945 func setTestEnv() { 1946 if enableDebug { 1947 _ = os.Setenv("GOVCD_DEBUG", "1") 1948 } 1949 if debugShowRequestEnabled { 1950 _ = os.Setenv("GOVCD_SHOW_REQ", "1") 1951 } 1952 if debugShowResponseEnabled { 1953 _ = os.Setenv("GOVCD_SHOW_RESP", "1") 1954 } 1955 } 1956 1957 func skipWhenMediaPathMissing(vcd *TestVCD, check *C) { 1958 if vcd.config.Media.MediaPath == "" { 1959 check.Skip("Skipping test because no iso path given") 1960 } 1961 } 1962 1963 func skipNoNsxtConfiguration(vcd *TestVCD, check *C) { 1964 generalMessage := "Missing NSX-T config: " 1965 if vcd.config.VCD.NsxtProviderVdc.Name == "" { 1966 check.Skip(generalMessage + "No provider vdc specified") 1967 } 1968 if vcd.config.VCD.NsxtProviderVdc.NetworkPool == "" { 1969 check.Skip(generalMessage + "No network pool specified") 1970 } 1971 1972 if vcd.config.VCD.Nsxt.Vdc == "" { 1973 check.Skip(generalMessage + "No NSX-T VDC specified") 1974 } 1975 1976 if vcd.config.VCD.Nsxt.NsxtImportSegment == "" { 1977 check.Skip(generalMessage + "No NSX-T Unused segment (for imported Org VDC network) specified") 1978 } 1979 1980 if vcd.config.VCD.NsxtProviderVdc.StorageProfile == "" { 1981 check.Skip(generalMessage + "No storage profile specified") 1982 } 1983 1984 if vcd.config.VCD.Nsxt.Manager == "" { 1985 check.Skip(generalMessage + "No NSX-T manager specified") 1986 } 1987 1988 if vcd.config.VCD.Nsxt.Tier0router == "" { 1989 check.Skip(generalMessage + "No NSX-T Tier-0 router specified") 1990 } 1991 1992 if vcd.config.VCD.Nsxt.Tier0routerVrf == "" { 1993 check.Skip(generalMessage + "No VRF NSX-T Tier-0 router specified") 1994 } 1995 1996 if vcd.config.VCD.Nsxt.EdgeGateway == "" { 1997 check.Skip(generalMessage + "No NSX-T Edge Gateway specified in configuration") 1998 } 1999 2000 if vcd.config.VCD.Nsxt.IpDiscoveryProfile == "" || 2001 vcd.config.VCD.Nsxt.MacDiscoveryProfile == "" || 2002 vcd.config.VCD.Nsxt.SpoofGuardProfile == "" || 2003 vcd.config.VCD.Nsxt.QosProfile == "" || 2004 vcd.config.VCD.Nsxt.SegmentSecurityProfile == "" { 2005 check.Skip(generalMessage + "NSX-T Segment Profiles are not specified in configuration") 2006 } 2007 } 2008 2009 func skipNoNsxtAlbConfiguration(vcd *TestVCD, check *C) { 2010 skipNoNsxtConfiguration(vcd, check) 2011 generalMessage := "Missing NSX-T ALB config: " 2012 2013 if vcd.config.VCD.Nsxt.NsxtAlbControllerUrl == "" { 2014 check.Skip(generalMessage + "No NSX-T ALB Controller URL specified in configuration") 2015 } 2016 2017 if vcd.config.VCD.Nsxt.NsxtAlbControllerUser == "" { 2018 check.Skip(generalMessage + "No NSX-T ALB Controller Name specified in configuration") 2019 } 2020 2021 if vcd.config.VCD.Nsxt.NsxtAlbControllerPassword == "" { 2022 check.Skip(generalMessage + "No NSX-T ALB Controller Password specified in configuration") 2023 } 2024 2025 if vcd.config.VCD.Nsxt.NsxtAlbImportableCloud == "" { 2026 check.Skip(generalMessage + "No NSX-T ALB Controller Importable Cloud Name") 2027 } 2028 if vcd.config.VCD.Nsxt.NsxtAlbServiceEngineGroup == "" { 2029 check.Skip(generalMessage + "No NSX-T ALB Service Engine Group name specified in configuration") 2030 } 2031 } 2032 2033 // skipOpenApiEndpointTest is a helper to skip tests for particular unsupported OpenAPI endpoints 2034 func skipOpenApiEndpointTest(vcd *TestVCD, check *C, endpoint string) { 2035 minimumRequiredApiVersion := endpointMinApiVersions[endpoint] 2036 2037 constraint := ">= " + minimumRequiredApiVersion 2038 if !vcd.client.Client.APIVCDMaxVersionIs(constraint) { 2039 maxSupportedVersion, err := vcd.client.Client.MaxSupportedVersion() 2040 if err != nil { 2041 panic(fmt.Sprintf("Could not get maximum supported version: %s", err)) 2042 } 2043 skipText := fmt.Sprintf("Skipping test because OpenAPI endpoint '%s' must satisfy API version constraint '%s'. Maximum supported version is %s", 2044 endpoint, constraint, maxSupportedVersion) 2045 check.Skip(skipText) 2046 } 2047 } 2048 2049 // newOrgUserConnection creates a new Org User and returns a connection to it. 2050 // Attention: Set the user to use only lowercase letters. If you put upper case letters the function fails on waiting 2051 // because VCD creates the user with lowercase letters. 2052 func newOrgUserConnection(adminOrg *AdminOrg, userName, password, href string, insecure bool) (*VCDClient, *OrgUser, error) { 2053 u, err := url.ParseRequestURI(href) 2054 if err != nil { 2055 return nil, nil, fmt.Errorf("[newOrgUserConnection] unable to pass url: %s", err) 2056 } 2057 2058 _, err = adminOrg.GetUserByName(userName, false) 2059 if err == nil { 2060 // user exists 2061 return nil, nil, fmt.Errorf("user %s already exists", userName) 2062 } 2063 _, err = adminOrg.CreateUserSimple(OrgUserConfiguration{ 2064 Name: userName, 2065 Password: password, 2066 RoleName: OrgUserRoleOrganizationAdministrator, 2067 ProviderType: OrgUserProviderIntegrated, 2068 IsEnabled: true, 2069 DeployedVmQuota: 0, 2070 StoredVmQuota: 0, 2071 FullName: userName, 2072 Description: "Test user created by newOrgUserConnection", 2073 }) 2074 if err != nil { 2075 return nil, nil, err 2076 } 2077 2078 AddToCleanupList(userName, "user", adminOrg.AdminOrg.Name, "newOrgUserConnection") 2079 2080 _ = adminOrg.Refresh() 2081 vcdClient := NewVCDClient(*u, insecure) 2082 err = vcdClient.Authenticate(userName, password, adminOrg.AdminOrg.Name) 2083 if err != nil { 2084 return nil, nil, fmt.Errorf("[newOrgUserConnection] unable to authenticate: %s", err) 2085 } 2086 2087 // return newUser 2088 newUser, err := adminOrg.GetUserByName(userName, false) 2089 if err != nil { 2090 return nil, nil, fmt.Errorf("[newOrgUserConnection] unable to retrieve newly created user: %s", err) 2091 } 2092 2093 return vcdClient, newUser, nil 2094 } 2095 2096 func (vcd *TestVCD) skipIfNotSysAdmin(check *C) { 2097 if !vcd.client.Client.IsSysAdmin { 2098 check.Skip(fmt.Sprintf("Skipping %s: requires system administrator privileges", check.TestName())) 2099 } 2100 } 2101 2102 // retryOnError is a function that will attempt to execute function with signature `func() error` 2103 // multiple times (until maxRetries) and waiting given retryInterval between tries. It will return 2104 // original deletion error for troubleshooting. 2105 func retryOnError(operation func() error, maxRetries int, retryInterval time.Duration) error { 2106 var err error 2107 for attempt := 0; attempt < maxRetries; attempt++ { 2108 err = operation() 2109 if err == nil { 2110 return nil 2111 } 2112 2113 fmt.Printf("# retrying after %v (Attempt %d/%d)\n", retryInterval, attempt+1, maxRetries) 2114 fmt.Printf("# error was: %s", err) 2115 time.Sleep(retryInterval) 2116 } 2117 2118 return fmt.Errorf("exceeded maximum retries, final error: %s", err) 2119 }