Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 22 additions & 21 deletions pkg/nsx/services/vpc/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import (
"context"
"errors"
"fmt"
"reflect"
"strings"
"sync"
"unsafe"

stderrors "github.com/vmware/vsphere-automation-sdk-go/lib/vapi/std/errors"
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
Expand Down Expand Up @@ -54,8 +52,9 @@ var (

type VPCService struct {
common.Service
VpcStore *VPCStore
LbsStore *LBSStore
VpcStore *VPCStore
LbsStore *LBSStore
defaultProjectCache sync.Map // cache for IsDefaultNSXProject results, key: "orgID/projectID", value: bool
}

func (s *VPCService) GetDefaultNetworkConfig() (*v1alpha1.VPCNetworkConfiguration, error) {
Expand Down Expand Up @@ -1168,27 +1167,29 @@ func IsPreCreatedVPC(nc *v1alpha1.VPCNetworkConfiguration) bool {
return nc.Spec.VPC != ""
}

func getProjectDefault(p *model.Project) *bool {
v := reflect.ValueOf(p).Elem()
f := v.FieldByName("_Default")
if !f.IsValid() {
return nil
// IsDefaultNSXProject checks if the given project is a NSX default project.
// It queries the NSX Policy API directly and reads the "default" field from the
// JSON response, bypassing the Go SDK struct deserialization which cannot populate
// the unexported model.Project._Default field (CanSet=false for unexported fields).
// Results are cached since a project's default status does not change at runtime.
func (s *VPCService) IsDefaultNSXProject(orgID, projectID string) (bool, error) {
cacheKey := orgID + "/" + projectID
if val, ok := s.defaultProjectCache.Load(cacheKey); ok {
return val.(bool), nil
}

ptr := unsafe.Pointer(f.UnsafeAddr())
return *(**bool)(ptr) // read *bool safely
}

// IsDefaultNSXProject checks if the given project is a default project
func (s *VPCService) IsDefaultNSXProject(orgID, projectID string) (bool, error) {
proj, err := s.NSXClient.ProjectClient.Get(orgID, projectID, nil)
url := fmt.Sprintf("policy/api/v1/orgs/%s/projects/%s", orgID, projectID)
resp, err := s.NSXClient.Cluster.HttpGet(url)
if err != nil {
log.Error(err, "Failed to get project", "ProjectID", projectID)
log.Error(err, "Failed to get project from NSX", "OrgID", orgID, "ProjectID", projectID)
return false, err
}
defaultVal := getProjectDefault(&proj)
if defaultVal != nil && *defaultVal {
return true, nil
isDefault := false
if defaultVal, ok := resp["default"]; ok {
if boolVal, ok := defaultVal.(bool); ok {
isDefault = boolVal
}
}
return false, nil
s.defaultProjectCache.Store(cacheKey, isDefault)
return isDefault, nil
}
91 changes: 32 additions & 59 deletions pkg/nsx/services/vpc/vpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@ import (
"reflect"
"strings"
"testing"
"unsafe"

"github.com/agiledragon/gomonkey/v2"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vmware/vsphere-automation-sdk-go/runtime/data"
nsx_client "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client"
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs"
v1 "k8s.io/api/core/v1"
k8sapierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -2921,83 +2918,59 @@ func Test_isNamespaceReady(t *testing.T) {
assert.False(t, isNamespaceReady(nsUnready))
}

func SetProjectDefault(p *model.Project, b bool) {
v := reflect.ValueOf(p).Elem()
f := v.FieldByName("_Default")
if !f.IsValid() {
return
}

val := b // bool value
ptr := unsafe.Pointer(f.UnsafeAddr())
realPtr := (*unsafe.Pointer)(ptr)
*realPtr = unsafe.Pointer(&val)
}

func TestIsDefaultNSXProject(t *testing.T) {
connector := nsx_client.NewConnector("localhost")
projectClient := orgs.NewProjectsClient(connector)
tests := []struct {
name string
orgID string
projectID string
prepareFunc func(*VPCService) *gomonkey.Patches
httpGetResp map[string]interface{}
httpGetErr error
expectedResult bool
expectedErrStr string
expectedErr bool
}{
{
name: "Project is default",
orgID: "default",
projectID: "project-1",
prepareFunc: func(service *VPCService) *gomonkey.Patches {
patches := gomonkey.ApplyMethod(reflect.TypeOf(projectClient), "Get", func(_ orgs.ProjectsClient, _ string, _ string, _ *bool) (model.Project, error) {
pro := model.Project{}
SetProjectDefault(&pro, true)
return pro, nil
})
return patches
},
name: "project is default",
orgID: "default",
projectID: "default",
httpGetResp: map[string]interface{}{"default": true, "id": "default"},
expectedResult: true,
},
{
name: "Project is not default",
orgID: "default",
projectID: "project-2",
prepareFunc: func(service *VPCService) *gomonkey.Patches {
patches := gomonkey.ApplyMethod(reflect.TypeOf(projectClient), "Get", func(_ orgs.ProjectsClient, _ string, _ string, _ *bool) (model.Project, error) {
pro := model.Project{}
return pro, nil
})
return patches
},
name: "project is not default",
orgID: "default",
projectID: "customer-project",
httpGetResp: map[string]interface{}{"default": false, "id": "customer-project"},
expectedResult: false,
},
{
name: "Get project error",
orgID: "default",
projectID: "project-3",
prepareFunc: func(service *VPCService) *gomonkey.Patches {
patches := gomonkey.ApplyMethod(reflect.TypeOf(projectClient), "Get", func(_ orgs.ProjectsClient, _ string, _ string, _ *bool) (model.Project, error) {
return model.Project{}, fmt.Errorf("failed to get project")
})
return patches
},
expectedErrStr: "failed to get project",
name: "response missing default field",
orgID: "default",
projectID: "some-project",
httpGetResp: map[string]interface{}{"id": "some-project"},
expectedResult: false,
},
{
name: "HTTP error",
orgID: "default",
projectID: "default",
httpGetErr: fmt.Errorf("connection refused"),
expectedErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
service, _, _, _, _ := createService(t)
if tt.prepareFunc != nil {
patches := tt.prepareFunc(service)
defer patches.Reset()
}
service.NSXClient.ProjectClient = projectClient
result, err := service.IsDefaultNSXProject(tt.orgID, tt.projectID)
service.NSXClient.Cluster = &nsx.Cluster{}
patches := gomonkey.ApplyMethod(reflect.TypeOf(service.NSXClient.Cluster), "HttpGet",
func(_ *nsx.Cluster, _ string) (map[string]interface{}, error) {
return tt.httpGetResp, tt.httpGetErr
})
defer patches.Reset()

if tt.expectedErrStr != "" {
assert.ErrorContains(t, err, tt.expectedErrStr)
result, err := service.IsDefaultNSXProject(tt.orgID, tt.projectID)
if tt.expectedErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, result)
Expand Down
Loading