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  }