Skip to content

Commit 5bb73f0

Browse files
authored
Merge branch 'master' into BMSChange
2 parents 4349af0 + fd733ca commit 5bb73f0

112 files changed

Lines changed: 585 additions & 896 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ Learn more:
2828

2929
- VMWare NSX 9.1.0
3030
- VMWare NSX 9.0.0
31-
- VMware NSX 4.2.x
3231

3332
The plugin supports versions in accordance with the [Broadcom Product Lifecycle][product-lifecycle].
3433

docs/data-sources/transport_node.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
subcategory: "Fabric"
2+
subcategory: "Deprecated"
33
page_title: "NSXT: transport_node"
44
description: An Transport Node data source.
55
---

docs/data-sources/transport_node_realization.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
subcategory: "Realization"
2+
subcategory: "Deprecated"
33
page_title: "NSXT: transport_node_realization"
44
description: Transport node resource realization information.
55
---

docs/resources/edge_transport_node.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
---
2-
subcategory: "Fabric"
2+
subcategory: "Deprecated"
33
page_title: "NSXT: nsxt_edge_transport_node"
44
description: A resource to configure an Edge Transport Node.
55
---
66

77
# nsxt_edge_transport_node
88

9+
~> **NOTE:** This resource has been deprecated and replaced with nsxt_policy_edge_transport_node.
10+
911
This resource provides a method for the management of an Edge Transport Node.
1012
This resource is supported with NSX 4.1.0 onwards.
1113

nsxt/bms_validation.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ package nsxt
77
import (
88
"fmt"
99
"regexp"
10-
11-
"github.com/vmware/terraform-provider-nsxt/nsxt/util"
1210
)
1311

1412
// validateBMSExternalID validates the format of BMS external IDs
@@ -34,11 +32,3 @@ func isValidBMSExternalID(id string) bool {
3432
_, errors := validateBMSExternalID(id, "external_id")
3533
return len(errors) == 0
3634
}
37-
38-
// validateBMSVersionRequirement checks NSX version compatibility
39-
func validateBMSVersionRequirement() error {
40-
if !util.NsxVersionHigherOrEqual("9.0.0") {
41-
return fmt.Errorf("Bare Metal Server features require NSX-T version 9.0.0 or higher. Current version does not support BMS management. Please upgrade NSX-T to version 9.0.0 or later")
42-
}
43-
return nil
44-
}

nsxt/cache.go

Lines changed: 178 additions & 70 deletions
Large diffs are not rendered by default.

nsxt/cache_test.go

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88

99
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
utl "github.com/vmware/terraform-provider-nsxt/api/utl"
1011
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
1112
)
1213

@@ -390,6 +391,99 @@ func TestAttachRulesByParentPathGatewayPolicy(t *testing.T) {
390391
})
391392
}
392393

394+
func TestGetQueryStringVPCScopedToProjectNotVPC(t *testing.T) {
395+
// VPCID must be omitted from the cache bucket key: NSX policy paths/IDs are unique
396+
// within a project across all VPCs, and narrowing the key (and the underlying search)
397+
// to a single VPC caused a fresh cache bucket per VPC, regressing cache mode below
398+
// no-cache performance for VPC-scoped resource types touching many VPCs.
399+
for _, clientType := range []utl.ClientType{utl.VPC, utl.Multitenancy} {
400+
context := utl.SessionContext{ClientType: clientType, ProjectID: "proj-1", VPCID: "vpc-1"}
401+
got := getQueryString(resourceTypeVpcAttachment, context)
402+
if strings.Contains(got, "vpc-1") {
403+
t.Fatalf("clientType=%v: query %q must not be scoped to a specific VPCID", clientType, got)
404+
}
405+
if !strings.Contains(got, "proj-1") {
406+
t.Fatalf("clientType=%v: query %q must still be scoped to the project", got, got)
407+
}
408+
409+
otherVPC := context
410+
otherVPC.VPCID = "vpc-2"
411+
if got2 := getQueryString(resourceTypeVpcAttachment, otherVPC); got2 != got {
412+
t.Fatalf("clientType=%v: query must be identical across VPCs in the same project so the cache bucket is shared; got %q vs %q", clientType, got, got2)
413+
}
414+
}
415+
}
416+
417+
func TestProjectScopedSearchContextStripsVPCID(t *testing.T) {
418+
for _, clientType := range []utl.ClientType{utl.VPC, utl.Multitenancy} {
419+
in := utl.SessionContext{ClientType: clientType, ProjectID: "proj-1", VPCID: "vpc-1"}
420+
out := projectScopedSearchContext(in)
421+
if out.VPCID != "" {
422+
t.Fatalf("clientType=%v: expected VPCID stripped, got %q", clientType, out.VPCID)
423+
}
424+
if out.ProjectID != "proj-1" {
425+
t.Fatalf("clientType=%v: ProjectID must be preserved, got %q", clientType, out.ProjectID)
426+
}
427+
}
428+
429+
// Non-VPC-scoped contexts must be returned unchanged.
430+
local := utl.SessionContext{ClientType: utl.Local, ProjectID: "", VPCID: ""}
431+
if got := projectScopedSearchContext(local); got != local {
432+
t.Fatalf("Local context should be unchanged, got %+v", got)
433+
}
434+
}
435+
436+
func TestShouldIndexByPathForVPCScopedTypes(t *testing.T) {
437+
// VPC-scoped types must key by path, not short id: NSX ids for these types (often
438+
// user-chosen via nsx_id) are only guaranteed unique within their own VPC, but the cache
439+
// populate search/bucket for these types is now shared across all VPCs in a project.
440+
vpcScopedTypes := []string{
441+
resourceTypeVpc, resourceTypeVpcAttachment, resourceTypeVpcConnectivityProfile,
442+
resourceTypeVpcIpAddressAllocation, resourceTypeVpcServiceProfile, resourceTypeVpcSubnet,
443+
resourceTypeTransitGateway, resourceTypeTransitGatewayAttachment,
444+
resourceTypeProjectIpAddressAllocation, resourceTypePolicyVpcNatRule,
445+
// Also reachable via CacheAwareResourceRead under a VPC-scoped SessionContext
446+
// (resource_nsxt_vpc_group.go, resource_nsxt_vpc_gateway_policy.go,
447+
// resource_nsxt_vpc_static_routes.go, resource_nsxt_vpc_dhcp_v4_static_binding_config.go),
448+
// so they need the same path-indexing safety even though some are shared with
449+
// non-VPC-scoped sibling resources (GatewayPolicy, StaticRoutes, DhcpV4StaticBindingConfig).
450+
resourceTypeVPCGroup, resourceTypeGatewayPolicy, resourceTypeStaticRoutes, resourceTypeDhcpV4StaticBindingConfig,
451+
}
452+
for _, rt := range vpcScopedTypes {
453+
if !shouldIndexByPath(rt) {
454+
t.Errorf("shouldIndexByPath(%q) = false, want true", rt)
455+
}
456+
}
457+
}
458+
459+
func TestConverListToMapByTypeVpcScopedResourcesIndexedByPath(t *testing.T) {
460+
// Two different VPCs in the same project can legitimately have a VpcSubnet with the same
461+
// user-chosen short id (getOrGenerateID2 only checks uniqueness within the current VPC).
462+
// Since the cache bucket for VPC-scoped types is now shared project-wide, both objects
463+
// land in the same map; this test confirms each remains independently retrievable via its
464+
// distinct (project/system-unique) path, even though they share a colliding short id.
465+
pathA := "/orgs/o/projects/p/vpcs/vpcA/subnets/subnet1"
466+
pathB := "/orgs/o/projects/p/vpcs/vpcB/subnets/subnet1"
467+
subnetA := model.VpcSubnet{Id: strPtr("subnet1"), DisplayName: strPtr("subnet1-a"), Path: strPtr(pathA)}
468+
subnetB := model.VpcSubnet{Id: strPtr("subnet1"), DisplayName: strPtr("subnet1-b"), Path: strPtr(pathB)}
469+
470+
svs, err := modelsToStructValues([]model.VpcSubnet{subnetA, subnetB}, model.VpcSubnetBindingType())
471+
if err != nil {
472+
t.Fatalf("modelsToStructValues: %v", err)
473+
}
474+
475+
got := converListToMapByType(svs, resourceTypeVpcSubnet)
476+
if got == nil {
477+
t.Fatal("converListToMapByType returned nil")
478+
}
479+
if got[pathA] == nil {
480+
t.Errorf("VPC A's subnet not retrievable by its path %q", pathA)
481+
}
482+
if got[pathB] == nil {
483+
t.Errorf("VPC B's subnet not retrievable by its path %q", pathB)
484+
}
485+
}
486+
393487
func TestErrCacheUseBackendDirect(t *testing.T) {
394488
if !errors.Is(errCacheUseBackendDirect, errCacheUseBackendDirect) {
395489
t.Fatal("errors.Is should match sentinel to itself")
@@ -437,3 +531,149 @@ func TestReflectStringField(t *testing.T) {
437531
}
438532
})
439533
}
534+
535+
func TestCacheAwareDataSourceReadByIDBypassesCacheForShortIDOnPathIndexedTypes(t *testing.T) {
536+
dsSchema := map[string]*schema.Schema{
537+
"id": getDataSourceIDSchema(),
538+
"display_name": getDataSourceExtendedDisplayNameSchema(),
539+
"description": getDataSourceDescriptionSchema(),
540+
"path": getPathSchema(),
541+
}
542+
m := nsxtClients{CommonConfig: commonProviderConfig{CacheMode: "config_scope"}}
543+
544+
t.Run("short-id-on-path-indexed-type-bypasses-cache", func(t *testing.T) {
545+
d := schema.TestResourceDataRaw(t, dsSchema, map[string]interface{}{"id": "subnet1"})
546+
_, ok := cacheAwareDataSourceReadByID[model.VpcSubnet](d, m, nil, "subnet1", resourceTypeVpcSubnet, model.VpcSubnetBindingType())
547+
if ok {
548+
t.Fatal("expected cache bypass (ok=false) for a short id on a path-indexed resource type")
549+
}
550+
if d.Id() != "" {
551+
t.Fatalf("expected d.Id() to be untouched on bypass, got %q", d.Id())
552+
}
553+
})
554+
555+
t.Run("full-path-on-path-indexed-type-not-bypassed-by-this-check", func(t *testing.T) {
556+
// A full path contains "/", so the new short-id bypass must not trigger; this proves
557+
// the check is specific to short (non-path) ids, not to shouldIndexByPath types broadly.
558+
path := "/orgs/o/projects/p/vpcs/vpcA/subnets/subnet1"
559+
d := schema.TestResourceDataRaw(t, dsSchema, map[string]interface{}{"id": path})
560+
if _, ok := postWriteByKey.LoadAndDelete(postWriteKey(resourceTypeVpcSubnet, path)); ok {
561+
t.Fatal("test setup: unexpected post-write marker present")
562+
}
563+
postWriteByKey.Store(postWriteKey(resourceTypeVpcSubnet, path), struct{}{})
564+
_, ok := cacheAwareDataSourceReadByID[model.VpcSubnet](d, m, nil, path, resourceTypeVpcSubnet, model.VpcSubnetBindingType())
565+
if ok {
566+
t.Fatal("expected ok=false (post-write bypass), proving control reached the postWriteByKey check rather than the short-id bypass")
567+
}
568+
})
569+
}
570+
571+
func TestCacheAwareResourceReadBypassesCacheForShortIDOnPathIndexedTypes(t *testing.T) {
572+
// CacheAwareResourceRead's resourceID is a short id (not yet path) during the Create-then-Read
573+
// sequence (path isn't set on d until the Read populates it from the live object) and after
574+
// terraform import (importers call d.SetId(shortID) without setting path). Without this bypass,
575+
// such a call falls through to gcache.readCache keyed by the short id against the shared
576+
// project-wide bucket, which can return a different VPC's same-short-id object.
577+
rSchema := map[string]*schema.Schema{
578+
"path": getPathSchema(),
579+
}
580+
m := nsxtClients{CommonConfig: commonProviderConfig{CacheMode: "config_scope"}}
581+
582+
t.Run("short-id-on-path-indexed-type-bypasses-cache-and-calls-backendRead", func(t *testing.T) {
583+
d := schema.TestResourceDataRaw(t, rSchema, map[string]interface{}{})
584+
d.SetId("subnet1")
585+
backendReadCalled := false
586+
obj, cacheUsed, cacheAttempted, err := CacheAwareResourceRead[model.VpcSubnet](
587+
d, m, nil, "subnet1", resourceTypeVpcSubnet, model.VpcSubnetBindingType(),
588+
func() (*model.VpcSubnet, error) {
589+
backendReadCalled = true
590+
return &model.VpcSubnet{Id: strPtr("subnet1")}, nil
591+
},
592+
func(*model.VpcSubnet) error { return nil },
593+
)
594+
if err != nil {
595+
t.Fatalf("unexpected error: %v", err)
596+
}
597+
if !backendReadCalled {
598+
t.Fatal("expected backendRead to be called when bypassing cache for a short id on a path-indexed type")
599+
}
600+
if cacheUsed {
601+
t.Fatal("expected cacheUsed=false when bypassing cache")
602+
}
603+
if cacheAttempted {
604+
t.Fatal("expected cacheAttempted=false: the short-id bypass should skip the cache attempt entirely, not count as a failed attempt")
605+
}
606+
if obj == nil || obj.Id == nil || *obj.Id != "subnet1" {
607+
t.Fatalf("expected backendRead's object to be returned, got %+v", obj)
608+
}
609+
})
610+
611+
t.Run("full-path-on-path-indexed-type-not-bypassed-by-this-check", func(t *testing.T) {
612+
path := "/orgs/o/projects/p/vpcs/vpcA/subnets/subnet1"
613+
d := schema.TestResourceDataRaw(t, rSchema, map[string]interface{}{"path": path})
614+
d.SetId(path)
615+
if _, ok := postWriteByKey.LoadAndDelete(postWriteKey(resourceTypeVpcSubnet, path)); ok {
616+
t.Fatal("test setup: unexpected post-write marker present")
617+
}
618+
postWriteByKey.Store(postWriteKey(resourceTypeVpcSubnet, path), struct{}{})
619+
_, cacheUsed, cacheAttempted, err := CacheAwareResourceRead[model.VpcSubnet](
620+
d, m, nil, path, resourceTypeVpcSubnet, model.VpcSubnetBindingType(),
621+
func() (*model.VpcSubnet, error) { return &model.VpcSubnet{Id: strPtr("subnet1")}, nil },
622+
func(*model.VpcSubnet) error { return nil },
623+
)
624+
if err != nil {
625+
t.Fatalf("unexpected error: %v", err)
626+
}
627+
if cacheUsed {
628+
t.Fatal("expected cacheUsed=false (post-write bypass path, not a cache hit)")
629+
}
630+
if !cacheAttempted {
631+
t.Fatal("expected cacheAttempted=true, proving control reached the postWriteByKey check rather than the short-id bypass")
632+
}
633+
})
634+
}
635+
636+
func TestTryCacheReadBypassesCacheForShortIDOnPathIndexedTypes(t *testing.T) {
637+
rSchema := map[string]*schema.Schema{
638+
"path": getPathSchema(),
639+
}
640+
m := nsxtClients{CommonConfig: commonProviderConfig{CacheMode: "config_scope"}}
641+
642+
t.Run("short-id-on-path-indexed-type-bypasses-cache", func(t *testing.T) {
643+
d := schema.TestResourceDataRaw(t, rSchema, map[string]interface{}{})
644+
d.SetId("subnet1")
645+
obj, cacheUsed, cacheAttempted, err := TryCacheRead[model.VpcSubnet](d, m, nil, "subnet1", resourceTypeVpcSubnet, model.VpcSubnetBindingType())
646+
if err != nil {
647+
t.Fatalf("unexpected error: %v", err)
648+
}
649+
if obj != nil {
650+
t.Fatalf("expected nil object on bypass, got %+v", obj)
651+
}
652+
if cacheUsed {
653+
t.Fatal("expected cacheUsed=false when bypassing cache")
654+
}
655+
if cacheAttempted {
656+
t.Fatal("expected cacheAttempted=false: the short-id bypass should skip the cache attempt entirely")
657+
}
658+
})
659+
660+
t.Run("full-path-on-path-indexed-type-not-bypassed-by-this-check", func(t *testing.T) {
661+
path := "/orgs/o/projects/p/vpcs/vpcA/subnets/subnet1"
662+
d := schema.TestResourceDataRaw(t, rSchema, map[string]interface{}{"path": path})
663+
d.SetId(path)
664+
if _, ok := postWriteByKey.LoadAndDelete(postWriteKey(resourceTypeVpcSubnet, path)); ok {
665+
t.Fatal("test setup: unexpected post-write marker present")
666+
}
667+
postWriteByKey.Store(postWriteKey(resourceTypeVpcSubnet, path), struct{}{})
668+
_, cacheUsed, cacheAttempted, err := TryCacheRead[model.VpcSubnet](d, m, nil, path, resourceTypeVpcSubnet, model.VpcSubnetBindingType())
669+
if err != nil {
670+
t.Fatalf("unexpected error: %v", err)
671+
}
672+
if cacheUsed {
673+
t.Fatal("expected cacheUsed=false (post-write bypass path, not a cache hit)")
674+
}
675+
if !cacheAttempted {
676+
t.Fatal("expected cacheAttempted=true, proving control reached the postWriteByKey check rather than the short-id bypass")
677+
}
678+
})
679+
}

nsxt/data_source_nsxt_policy_baremetal_server.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,6 @@ func dataSourceNsxtPolicyBareMetalServer() *schema.Resource {
8484
}
8585

8686
func dataSourceNsxtPolicyBareMetalServerRead(d *schema.ResourceData, m interface{}) error {
87-
if err := validateBMSVersionRequirement(); err != nil {
88-
return err
89-
}
9087

9188
connector := getPolicyConnector(m)
9289
ctx := utl.SessionContext{ClientType: utl.Local}

nsxt/data_source_nsxt_policy_baremetal_server_group_associations.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ func dataSourceNsxtPolicyBareMetalServerGroupAssociations() *schema.Resource {
5959
}
6060

6161
func dataSourceNsxtPolicyBareMetalServerGroupAssociationsRead(d *schema.ResourceData, m interface{}) error {
62-
if err := validateBMSVersionRequirement(); err != nil {
63-
return err
64-
}
6562

6663
connector := getPolicyConnector(m)
6764

nsxt/data_source_nsxt_policy_baremetal_server_interface.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,6 @@ func dataSourceNsxtPolicyBareMetalServerInterface() *schema.Resource {
9292
}
9393

9494
func dataSourceNsxtPolicyBareMetalServerInterfaceRead(d *schema.ResourceData, m interface{}) error {
95-
if err := validateBMSVersionRequirement(); err != nil {
96-
return err
97-
}
9895

9996
connector := getPolicyConnector(m)
10097
ctx := utl.SessionContext{ClientType: utl.Local}

0 commit comments

Comments
 (0)