github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/metadata_openapi_test.go (about) 1 //go:build metadata || openapi || rde || functional || ALL 2 3 /* 4 * Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 5 */ 6 7 package govcd 8 9 import ( 10 "fmt" 11 "github.com/vmware/go-vcloud-director/v2/types/v56" 12 . "gopkg.in/check.v1" 13 "regexp" 14 "strings" 15 ) 16 17 func (vcd *TestVCD) TestRdeMetadata(check *C) { 18 fmt.Printf("Running: %s\n", check.TestName()) 19 20 // This RDE type comes out of the box in VCD 21 rdeType, err := vcd.client.GetRdeType("vmware", "tkgcluster", "1.0.0") 22 check.Assert(err, IsNil) 23 check.Assert(rdeType, NotNil) 24 25 rde, err := rdeType.CreateRde(types.DefinedEntity{ 26 Name: check.TestName(), 27 Entity: map[string]interface{}{"foo": "bar"}, // We don't care about schema correctness here 28 }, nil) 29 check.Assert(err, IsNil) 30 check.Assert(rde, NotNil) 31 32 err = rde.Resolve() // State will be RESOLUTION_ERROR, but we don't care. We resolve to be able to delete it later. 33 check.Assert(err, IsNil) 34 35 // The RDE can't be deleted until rde.Resolve() is called 36 AddToCleanupListOpenApi(rde.DefinedEntity.ID, check.TestName(), types.OpenApiPathVersion1_0_0+types.OpenApiEndpointRdeEntities+rde.DefinedEntity.ID) 37 38 testOpenApiMetadataCRUDActions(rde, check) 39 vcd.testOpenApiMetadataIgnore(rde, "entity", rde.DefinedEntity.Name, check) 40 41 err = rde.Delete() 42 check.Assert(err, IsNil) 43 } 44 45 // openApiMetadataCompatible allows centralizing and generalizing the tests for OpenAPI metadata compatible resources. 46 type openApiMetadataCompatible interface { 47 GetMetadata() ([]*OpenApiMetadataEntry, error) 48 GetMetadataByKey(domain, namespace, key string) (*OpenApiMetadataEntry, error) 49 GetMetadataById(id string) (*OpenApiMetadataEntry, error) 50 AddMetadata(metadataEntry types.OpenApiMetadataEntry) (*OpenApiMetadataEntry, error) 51 } 52 53 type openApiMetadataTest struct { 54 Key string 55 Value interface{} // The type depends on the Type attribute 56 UpdateValue interface{} 57 Namespace string 58 Type string 59 IsReadOnly bool 60 IsPersistent bool 61 Domain string 62 ExpectErrorOnFirstAddMatchesRegex string 63 } 64 65 // testOpenApiMetadataCRUDActions performs a complete test of all use cases that metadata in OpenAPI can have, 66 // for an OpenAPI metadata compatible resource. 67 func testOpenApiMetadataCRUDActions(resource openApiMetadataCompatible, check *C) { 68 // Check how much metadata exists 69 metadata, err := resource.GetMetadata() 70 check.Assert(err, IsNil) 71 check.Assert(metadata, NotNil) 72 existingMetaDataCount := len(metadata) 73 74 var testCases = []openApiMetadataTest{ 75 { 76 Key: "stringKey", 77 Value: "stringValue", 78 UpdateValue: "stringValueUpdated", 79 Type: types.OpenApiMetadataStringEntry, 80 IsReadOnly: false, 81 Domain: "TENANT", 82 Namespace: "foo", 83 }, 84 { 85 Key: "numberKey", 86 Value: "notANumber", 87 Type: types.OpenApiMetadataNumberEntry, 88 IsReadOnly: false, 89 Domain: "TENANT", 90 Namespace: "foo", 91 ExpectErrorOnFirstAddMatchesRegex: "notANumber", 92 }, 93 { 94 Key: "numberKey", 95 Value: float64(1), 96 UpdateValue: float64(42), 97 Type: types.OpenApiMetadataNumberEntry, 98 IsReadOnly: false, 99 Domain: "TENANT", 100 Namespace: "foo", 101 }, 102 { 103 Key: "negativeNumberKey", 104 Value: float64(-1), 105 UpdateValue: float64(-42), 106 Type: types.OpenApiMetadataNumberEntry, 107 IsReadOnly: false, 108 Domain: "TENANT", 109 Namespace: "foo", 110 }, 111 { 112 Key: "boolKey", 113 Value: "notABool", 114 Type: types.OpenApiMetadataBooleanEntry, 115 IsReadOnly: false, 116 Domain: "TENANT", 117 Namespace: "foo", 118 ExpectErrorOnFirstAddMatchesRegex: "notABool", 119 }, 120 { 121 Key: "boolKey", 122 Value: true, 123 UpdateValue: false, 124 Type: types.OpenApiMetadataBooleanEntry, 125 IsReadOnly: false, 126 Domain: "TENANT", 127 Namespace: "foo", 128 }, 129 { 130 Key: "providerKey", 131 Value: "providerValue", 132 UpdateValue: "providerValueUpdated", 133 Type: types.OpenApiMetadataStringEntry, 134 IsReadOnly: false, 135 Domain: "PROVIDER", 136 Namespace: "foo", 137 }, 138 { 139 Key: "readOnlyProviderKey", 140 Value: "readOnlyProviderValue", 141 Type: types.OpenApiMetadataStringEntry, 142 IsReadOnly: true, 143 Domain: "PROVIDER", 144 Namespace: "foo", 145 ExpectErrorOnFirstAddMatchesRegex: "VCD_META_CRUD_INVALID_FLAG", 146 }, 147 { 148 Key: "readOnlyTenantKey", 149 Value: "readOnlyTenantValue", 150 Type: types.OpenApiMetadataStringEntry, 151 IsReadOnly: true, 152 Domain: "TENANT", 153 Namespace: "foo", 154 }, 155 { 156 Key: "persistentKey", 157 Value: "persistentValue", 158 Type: types.OpenApiMetadataStringEntry, 159 IsReadOnly: false, 160 IsPersistent: true, 161 Domain: "TENANT", 162 Namespace: "foo", 163 }, 164 } 165 166 for _, testCase := range testCases { 167 168 var createdEntry *OpenApiMetadataEntry 169 createdEntry, err = resource.AddMetadata(types.OpenApiMetadataEntry{ 170 KeyValue: types.OpenApiMetadataKeyValue{ 171 Domain: testCase.Domain, 172 Key: testCase.Key, 173 Namespace: testCase.Namespace, 174 Value: types.OpenApiMetadataTypedValue{ 175 Type: testCase.Type, 176 Value: testCase.Value, 177 }, 178 }, 179 IsPersistent: testCase.IsPersistent, 180 IsReadOnly: testCase.IsReadOnly, 181 }) 182 if testCase.ExpectErrorOnFirstAddMatchesRegex != "" { 183 p := regexp.MustCompile("(?s)" + testCase.ExpectErrorOnFirstAddMatchesRegex) 184 check.Assert(p.MatchString(err.Error()), Equals, true) 185 continue 186 } 187 check.Assert(err, IsNil) 188 check.Assert(createdEntry, NotNil) 189 check.Assert(createdEntry.href, Not(Equals), "") 190 check.Assert(createdEntry.Etag, Not(Equals), "") 191 check.Assert(createdEntry.parentEndpoint, Not(Equals), "") 192 check.Assert(createdEntry.MetadataEntry, NotNil) 193 check.Assert(createdEntry.MetadataEntry.ID, Not(Equals), "") 194 195 // Check if metadata was added correctly 196 metadata, err = resource.GetMetadata() 197 check.Assert(err, IsNil) 198 check.Assert(len(metadata), Equals, existingMetaDataCount+1) 199 found := false 200 for _, entry := range metadata { 201 if entry.MetadataEntry.ID == createdEntry.MetadataEntry.ID { 202 check.Assert(*entry.MetadataEntry, DeepEquals, *createdEntry.MetadataEntry) 203 found = true 204 break 205 } 206 } 207 check.Assert(found, Equals, true) 208 209 metadataByKey, err := resource.GetMetadataByKey(createdEntry.MetadataEntry.KeyValue.Domain, createdEntry.MetadataEntry.KeyValue.Namespace, createdEntry.MetadataEntry.KeyValue.Key) 210 check.Assert(err, IsNil) 211 check.Assert(metadataByKey, NotNil) 212 check.Assert(metadataByKey.MetadataEntry, NotNil) 213 check.Assert(*metadataByKey.MetadataEntry, DeepEquals, *createdEntry.MetadataEntry) 214 check.Assert(metadataByKey.Etag, Equals, createdEntry.Etag) 215 check.Assert(metadataByKey.parentEndpoint, Equals, createdEntry.parentEndpoint) 216 check.Assert(metadataByKey.href, Equals, createdEntry.href) 217 218 metadataById, err := resource.GetMetadataById(metadataByKey.MetadataEntry.ID) 219 check.Assert(err, IsNil) 220 check.Assert(metadataById, NotNil) 221 check.Assert(metadataById.MetadataEntry, NotNil) 222 check.Assert(*metadataById.MetadataEntry, DeepEquals, *metadataById.MetadataEntry) 223 check.Assert(metadataById.Etag, Equals, metadataByKey.Etag) 224 check.Assert(metadataById.parentEndpoint, Equals, metadataByKey.parentEndpoint) 225 check.Assert(metadataById.href, Equals, metadataByKey.href) 226 227 if testCase.UpdateValue != nil { 228 oldEtag := metadataById.Etag 229 err = metadataById.Update(testCase.UpdateValue, !metadataById.MetadataEntry.IsPersistent) 230 check.Assert(err, IsNil) 231 check.Assert(metadataById, NotNil) 232 check.Assert(metadataById.MetadataEntry, NotNil) 233 // Changed fields 234 check.Assert(metadataById.MetadataEntry.IsPersistent, Equals, !metadataByKey.MetadataEntry.IsPersistent) 235 check.Assert(metadataById.MetadataEntry.KeyValue.Value.Value, Equals, testCase.UpdateValue) 236 // Non-changed fields 237 check.Assert(metadataById.MetadataEntry.ID, Equals, metadataByKey.MetadataEntry.ID) 238 check.Assert(metadataById.MetadataEntry.KeyValue.Value.Type, Equals, metadataByKey.MetadataEntry.KeyValue.Value.Type) 239 check.Assert(metadataById.MetadataEntry.KeyValue.Namespace, Equals, metadataByKey.MetadataEntry.KeyValue.Namespace) 240 check.Assert(metadataById.MetadataEntry.IsReadOnly, Equals, metadataByKey.MetadataEntry.IsReadOnly) 241 check.Assert(metadataById.Etag, Not(Equals), oldEtag) // ETag should be refreshed as we did an update 242 check.Assert(metadataById.parentEndpoint, Equals, metadataByKey.parentEndpoint) 243 check.Assert(metadataById.href, Equals, metadataByKey.href) 244 } 245 246 err = metadataById.Delete() 247 check.Assert(err, IsNil) 248 check.Assert(*metadataById.MetadataEntry, DeepEquals, types.OpenApiMetadataEntry{}) 249 check.Assert(metadataById.Etag, Equals, "") 250 check.Assert(metadataById.href, Equals, "") 251 check.Assert(metadataById.parentEndpoint, Equals, "") 252 253 // Check if metadata was deleted correctly 254 deletedMetadata, err := resource.GetMetadataById(metadataByKey.MetadataEntry.ID) 255 check.Assert(err, NotNil) 256 check.Assert(deletedMetadata, IsNil) 257 check.Assert(true, Equals, ContainsNotFound(err)) 258 } 259 } 260 261 func (vcd *TestVCD) testOpenApiMetadataIgnore(resource openApiMetadataCompatible, objectType, objectName string, check *C) { 262 existingMetadata, err := resource.GetMetadata() 263 check.Assert(err, IsNil) 264 265 _, err = resource.AddMetadata(types.OpenApiMetadataEntry{ 266 IsPersistent: false, 267 IsReadOnly: false, 268 KeyValue: types.OpenApiMetadataKeyValue{ 269 Domain: "TENANT", 270 Key: "foo", 271 Value: types.OpenApiMetadataTypedValue{ 272 Value: "bar", 273 Type: types.OpenApiMetadataStringEntry, 274 }, 275 Namespace: "", 276 }, 277 }) 278 check.Assert(err, IsNil) 279 _, err = resource.AddMetadata(types.OpenApiMetadataEntry{ 280 IsPersistent: false, 281 IsReadOnly: false, 282 KeyValue: types.OpenApiMetadataKeyValue{ 283 Domain: "TENANT", 284 Key: "not_ignored", 285 Value: types.OpenApiMetadataTypedValue{ 286 Value: "bar2", 287 Type: types.OpenApiMetadataStringEntry, 288 }, 289 Namespace: "", 290 }, 291 }) 292 check.Assert(err, IsNil) 293 294 cleanup := func() { 295 vcd.client.Client.IgnoredMetadata = nil 296 metadata, err := resource.GetMetadata() 297 check.Assert(err, IsNil) 298 for _, entry := range metadata { 299 itWasAlreadyPresent := false 300 for _, existingEntry := range existingMetadata { 301 if existingEntry.MetadataEntry.KeyValue.Namespace == entry.MetadataEntry.KeyValue.Namespace && 302 existingEntry.MetadataEntry.KeyValue.Key == entry.MetadataEntry.KeyValue.Key && 303 existingEntry.MetadataEntry.KeyValue.Value.Value == entry.MetadataEntry.KeyValue.Value.Value && 304 existingEntry.MetadataEntry.KeyValue.Value.Type == entry.MetadataEntry.KeyValue.Value.Type { 305 itWasAlreadyPresent = true 306 } 307 } 308 if !itWasAlreadyPresent { 309 toDelete, err := resource.GetMetadataById(entry.MetadataEntry.ID) 310 check.Assert(err, IsNil) 311 err = toDelete.Delete() 312 check.Assert(err, IsNil) 313 } 314 } 315 metadata, err = resource.GetMetadata() 316 check.Assert(err, IsNil) 317 check.Assert(len(metadata), Equals, len(existingMetadata)) 318 } 319 defer cleanup() 320 321 tests := []struct { 322 ignoredMetadata []IgnoredMetadata 323 metadataIsIgnored bool 324 }{ 325 { 326 ignoredMetadata: []IgnoredMetadata{{ObjectType: &objectType, KeyRegex: regexp.MustCompile(`^foo$`)}}, 327 metadataIsIgnored: true, 328 }, 329 { 330 ignoredMetadata: []IgnoredMetadata{{ObjectType: &objectType, ValueRegex: regexp.MustCompile(`^bar$`)}}, 331 metadataIsIgnored: true, 332 }, 333 { 334 ignoredMetadata: []IgnoredMetadata{{ObjectType: &objectType, KeyRegex: regexp.MustCompile(`^fizz$`)}}, 335 metadataIsIgnored: false, 336 }, 337 { 338 ignoredMetadata: []IgnoredMetadata{{ObjectType: &objectType, ValueRegex: regexp.MustCompile(`^buzz$`)}}, 339 metadataIsIgnored: false, 340 }, 341 { 342 ignoredMetadata: []IgnoredMetadata{{ObjectName: &objectName, KeyRegex: regexp.MustCompile(`^foo$`)}}, 343 metadataIsIgnored: true, 344 }, 345 { 346 ignoredMetadata: []IgnoredMetadata{{ObjectName: &objectName, ValueRegex: regexp.MustCompile(`^bar$`)}}, 347 metadataIsIgnored: true, 348 }, 349 { 350 ignoredMetadata: []IgnoredMetadata{{ObjectName: &objectName, KeyRegex: regexp.MustCompile(`^fizz$`)}}, 351 metadataIsIgnored: false, 352 }, 353 { 354 ignoredMetadata: []IgnoredMetadata{{ObjectName: &objectName, ValueRegex: regexp.MustCompile(`^buzz$`)}}, 355 metadataIsIgnored: false, 356 }, 357 { 358 ignoredMetadata: []IgnoredMetadata{{ObjectType: &objectType, ObjectName: &objectName, KeyRegex: regexp.MustCompile(`foo`), ValueRegex: regexp.MustCompile(`bar`)}}, 359 metadataIsIgnored: true, 360 }, 361 } 362 363 for _, tt := range tests { 364 vcd.client.Client.IgnoredMetadata = tt.ignoredMetadata 365 366 // Tests getting a simple metadata entry by its key 367 singleMetadata, err := resource.GetMetadataByKey("", "", "foo") 368 if tt.metadataIsIgnored { 369 check.Assert(err, NotNil) 370 check.Assert(true, Equals, strings.Contains(err.Error(), "could not find the metadata associated to object")) 371 } else { 372 check.Assert(err, IsNil) 373 check.Assert(singleMetadata, NotNil) 374 check.Assert(singleMetadata.MetadataEntry.KeyValue.Value.Value, Equals, "bar") 375 } 376 377 // Retrieve all metadata 378 allMetadata, err := resource.GetMetadata() 379 check.Assert(err, IsNil) 380 check.Assert(allMetadata, NotNil) 381 if tt.metadataIsIgnored { 382 // If metadata is ignored, there should be an offset of 1 entry (with key "test") 383 check.Assert(len(allMetadata), Equals, len(existingMetadata)+1) 384 for _, entry := range allMetadata { 385 if tt.metadataIsIgnored { 386 check.Assert(entry.MetadataEntry.KeyValue.Key, Not(Equals), "foo") 387 check.Assert(entry.MetadataEntry.KeyValue.Value.Value, Not(Equals), "bar") 388 } 389 } 390 } else { 391 // If metadata is NOT ignored, there should be an offset of 2 entries (with key "foo" and "test") 392 check.Assert(len(allMetadata), Equals, len(existingMetadata)+2) 393 } 394 } 395 }