Repository: vmware/govmomi
Pull request #3558 (2024-09-20 17:25:41)

Support querying PBM profile for IOFILTERS


## Description

This patch supports querying a PBM profile for IOFILTERS, which indicates if the profile supports encryption.

Closes: `NA`

## Type of change

Please mark options that are relevant:

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
- [ ] Build related change

## How Has This Been Tested?

```shell
$ go test -v -count 1 -run TestSupportsEncryption ./pbm
=== RUN   TestSupportsEncryption
=== RUN   TestSupportsEncryption/valid_profile_id
=== RUN   TestSupportsEncryption/invalid_profile_id
--- PASS: TestSupportsEncryption (0.74s)
    --- PASS: TestSupportsEncryption/valid_profile_id (0.40s)
    --- PASS: TestSupportsEncryption/invalid_profile_id (0.35s)
PASS
ok  	github.com/vmware/govmomi/pbm	1.143s
```

There is nearly 100% coverage for the new code:

<img width="609" alt="image" src="https://github.com/user-attachments/assets/2830aaf5-6239-454d-a45e-7748fd594fd0">
<img width="794" alt="image" src="https://github.com/user-attachments/assets/7dd306e9-49ef-4f77-a54b-7ae3f32fa2a1">

And the changes do not break the existing PBM code:

```shell
$ go test -v -count 1 ./pbm ./pbm/simulator
=== RUN   TestClient
--- SKIP: TestClient (0.00s)
=== RUN   TestSupportsEncryption
=== RUN   TestSupportsEncryption/valid_profile_id
=== RUN   TestSupportsEncryption/invalid_profile_id
--- PASS: TestSupportsEncryption (0.85s)
    --- PASS: TestSupportsEncryption/valid_profile_id (0.44s)
    --- PASS: TestSupportsEncryption/invalid_profile_id (0.41s)
PASS
ok  	github.com/vmware/govmomi/pbm	1.217s
=== RUN   TestSimulator
    simulator_test.go:64: PBM version=2.0
    simulator_test.go:141: checking 1 datatores for compatibility results
    simulator_test.go:166: CheckRequirements results: 1
    simulator_test.go:211: VSAN Profile: "4b9b0475-6832-4562-aa53-bf379fcb6148" successfully created
    simulator_test.go:228: Profile: "4b9b0475-6832-4562-aa53-bf379fcb6148" exists on vCenter
    simulator_test.go:232: Found 1 compatible-datastores for profile: "4b9b0475-6832-4562-aa53-bf379fcb6148"
    simulator_test.go:236: Found 0 non-compatible datastores for profile: "4b9b0475-6832-4562-aa53-bf379fcb6148"
    simulator_test.go:296: VSAN-SIOC Profile: "ceb78ae4-47c4-42d4-a004-436b53eae888" successfully created
    simulator_test.go:307: VSAN-SIOC profile: "ceb78ae4-47c4-42d4-a004-436b53eae888" and retrieved profileID: "ceb78ae4-47c4-42d4-a004-436b53eae888" successfully matched
    simulator_test.go:314: Profile: [{DynamicData:{} UniqueId:4b9b0475-6832-4562-aa53-bf379fcb6148} {DynamicData:{} UniqueId:ceb78ae4-47c4-42d4-a004-436b53eae888}] successfully deleted
--- PASS: TestSimulator (0.39s)
PASS
ok  	github.com/vmware/govmomi/pbm/simulator	1.032s
```


## Checklist:

- [x] My code follows the `CONTRIBUTION` [guidelines] of this project
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged

[guidelines]: https://github.com/vmware/govmomi/blob/main/CONTRIBUTING.md



Patch:
diff --git a/pbm/client.go b/pbm/client.go
index 3c7965ca..d8d2131c 100644
--- a/pbm/client.go
+++ b/pbm/client.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2017 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-    http://www.apache.org/licenses/LICENSE-2.0
+http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -313,3 +313,35 @@ func (c *Client) QueryAssociatedProfiles(ctx context.Context, entities []types.P
 
 	return res.Returnval, nil
 }
+
+func (c *Client) QueryIOFiltersFromProfileId(
+	ctx context.Context, profileID string) ([]types.PbmProfileToIofilterMap, error) {
+
+	req := types.PbmQueryIOFiltersFromProfileId{
+		This:       c.ServiceContent.ProfileManager,
+		ProfileIds: []types.PbmProfileId{{UniqueId: profileID}},
+	}
+	res, err := methods.PbmQueryIOFiltersFromProfileId(ctx, c, &req)
+	if err != nil {
+		return nil, err
+	}
+	return res.Returnval, nil
+}
+
+func (c *Client) SupportsEncryption(
+	ctx context.Context, profileID string) (bool, error) {
+
+	list, err := c.QueryIOFiltersFromProfileId(ctx, profileID)
+	if err != nil {
+		return false, err
+	}
+	for i := range list {
+		for j := range list[i].Iofilters {
+			f := list[i].Iofilters[j]
+			if f.FilterType == string(types.PbmIofilterInfoFilterTypeENCRYPTION) {
+				return true, nil
+			}
+		}
+	}
+	return false, nil
+}
diff --git a/pbm/methods/internal_methods.go b/pbm/methods/internal_methods.go
new file mode 100644
index 00000000..11b6fc50
--- /dev/null
+++ b/pbm/methods/internal_methods.go
@@ -0,0 +1,44 @@
+/*
+Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package methods
+
+import (
+	"context"
+
+	"github.com/vmware/govmomi/pbm/types"
+	"github.com/vmware/govmomi/vim25/soap"
+)
+
+type PbmQueryIOFiltersFromProfileIdBody struct {
+	Req    *types.PbmQueryIOFiltersFromProfileId         `xml:"urn:pbm PbmQueryIOFiltersFromProfileId,omitempty"`
+	Res    *types.PbmQueryIOFiltersFromProfileIdResponse `xml:"urn:pbm PbmQueryIOFiltersFromProfileIdResponse,omitempty"`
+	Fault_ *soap.Fault                                   `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"`
+}
+
+func (b *PbmQueryIOFiltersFromProfileIdBody) Fault() *soap.Fault { return b.Fault_ }
+
+func PbmQueryIOFiltersFromProfileId(ctx context.Context, r soap.RoundTripper, req *types.PbmQueryIOFiltersFromProfileId) (*types.PbmQueryIOFiltersFromProfileIdResponse, error) {
+	var reqBody, resBody PbmQueryIOFiltersFromProfileIdBody
+
+	reqBody.Req = req
+
+	if err := r.RoundTrip(ctx, &reqBody, &resBody); err != nil {
+		return nil, err
+	}
+
+	return resBody.Res, nil
+}
diff --git a/pbm/simulator/profiles.go b/pbm/simulator/profiles.go
index 636090e4..92f003b9 100644
--- a/pbm/simulator/profiles.go
+++ b/pbm/simulator/profiles.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2018 VMware, Inc. All Rights Reserved.
+Copyright (c) 2018-2024 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-    http://www.apache.org/licenses/LICENSE-2.0
+http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -23,8 +23,61 @@ import (
 	vim "github.com/vmware/govmomi/vim25/types"
 )
 
-// profiles is a captured from vCenter 6.7's default set of PBM profiles.
-var profiles = []types.BasePbmProfile{
+const DefaultEncryptionProfileID = "4d5f673c-536f-11e6-beb8-9e71128cae77"
+
+var defaultEncryptionProfile = &types.PbmCapabilityProfile{
+	PbmProfile: types.PbmProfile{
+		ProfileId: types.PbmProfileId{
+			UniqueId: DefaultEncryptionProfileID,
+		},
+		Name:            "VM Encryption Policy",
+		Description:     "Sample storage policy for VMware's VM and virtual disk encryption",
+		CreationTime:    time.Now(),
+		CreatedBy:       "Temporary user handle",
+		LastUpdatedTime: time.Now(),
+		LastUpdatedBy:   "Temporary user handle",
+	},
+	ProfileCategory: "REQUIREMENT",
+	ResourceType: types.PbmProfileResourceType{
+		ResourceType: "STORAGE",
+	},
+	Constraints: &types.PbmCapabilitySubProfileConstraints{
+		PbmCapabilityConstraints: types.PbmCapabilityConstraints{},
+		SubProfiles: []types.PbmCapabilitySubProfile{
+			{
+				Name: "sp-1",
+				Capability: []types.PbmCapabilityInstance{
+					{
+						Id: types.PbmCapabilityMetadataUniqueId{
+							Namespace: "com.vmware.storageprofile.dataservice",
+							Id:        "ad5a249d-cbc2-43af-9366-694d7664fa52",
+						},
+						Constraint: []types.PbmCapabilityConstraintInstance{
+							{
+								PropertyInstance: []types.PbmCapabilityPropertyInstance{
+									{
+										Id:       "ad5a249d-cbc2-43af-9366-694d7664fa52",
+										Operator: "",
+										Value:    "ad5a249d-cbc2-43af-9366-694d7664fa52",
+									},
+								},
+							},
+						},
+					},
+				},
+				ForceProvision: vim.NewBool(false),
+			},
+		},
+	},
+	GenerationId:             0,
+	IsDefault:                false,
+	SystemCreatedProfileType: "",
+	LineOfService:            "",
+}
+
+// vcenter67DefaultProfiles is a captured from vCenter 6.7's default set of PBM
+// profiles.
+var vcenter67DefaultProfiles = []types.BasePbmProfile{
 	&types.PbmCapabilityProfile{
 		PbmProfile: types.PbmProfile{
 			ProfileId: types.PbmProfileId{
@@ -164,55 +217,7 @@ var profiles = []types.BasePbmProfile{
 		SystemCreatedProfileType: "VVolDefaultProfile",
 		LineOfService:            "",
 	},
-	&types.PbmCapabilityProfile{
-		PbmProfile: types.PbmProfile{
-			ProfileId: types.PbmProfileId{
-				UniqueId: "4d5f673c-536f-11e6-beb8-9e71128cae77",
-			},
-			Name:            "VM Encryption Policy",
-			Description:     "Sample storage policy for VMware's VM and virtual disk encryption",
-			CreationTime:    time.Now(),
-			CreatedBy:       "Temporary user handle",
-			LastUpdatedTime: time.Now(),
-			LastUpdatedBy:   "Temporary user handle",
-		},
-		ProfileCategory: "REQUIREMENT",
-		ResourceType: types.PbmProfileResourceType{
-			ResourceType: "STORAGE",
-		},
-		Constraints: &types.PbmCapabilitySubProfileConstraints{
-			PbmCapabilityConstraints: types.PbmCapabilityConstraints{},
-			SubProfiles: []types.PbmCapabilitySubProfile{
-				{
-					Name: "sp-1",
-					Capability: []types.PbmCapabilityInstance{
-						{
-							Id: types.PbmCapabilityMetadataUniqueId{
-								Namespace: "com.vmware.storageprofile.dataservice",
-								Id:        "ad5a249d-cbc2-43af-9366-694d7664fa52",
-							},
-							Constraint: []types.PbmCapabilityConstraintInstance{
-								{
-									PropertyInstance: []types.PbmCapabilityPropertyInstance{
-										{
-											Id:       "ad5a249d-cbc2-43af-9366-694d7664fa52",
-											Operator: "",
-											Value:    "ad5a249d-cbc2-43af-9366-694d7664fa52",
-										},
-									},
-								},
-							},
-						},
-					},
-					ForceProvision: vim.NewBool(false),
-				},
-			},
-		},
-		GenerationId:             0,
-		IsDefault:                false,
-		SystemCreatedProfileType: "",
-		LineOfService:            "",
-	},
+	defaultEncryptionProfile,
 	&types.PbmCapabilityProfile{
 		PbmProfile: types.PbmProfile{
 			ProfileId: types.PbmProfileId{
diff --git a/pbm/simulator/simulator.go b/pbm/simulator/simulator.go
index 8f4b3fc3..7478b44f 100644
--- a/pbm/simulator/simulator.go
+++ b/pbm/simulator/simulator.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2018 VMware, Inc. All Rights Reserved.
+Copyright (c) 2018-2024 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-    http://www.apache.org/licenses/LICENSE-2.0
+http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -17,6 +17,7 @@ limitations under the License.
 package simulator
 
 import (
+	"slices"
 	"time"
 
 	"github.com/google/uuid"
@@ -61,9 +62,11 @@ func New() *simulator.Registry {
 		Content:                content,
 	})
 
-	r.Put(&ProfileManager{
+	profileManager := &ProfileManager{
 		ManagedObjectReference: content.ProfileManager,
-	})
+	}
+	profileManager.init(r)
+	r.Put(profileManager)
 
 	r.Put(&PlacementSolver{
 		ManagedObjectReference: content.PlacementSolver,
@@ -88,14 +91,34 @@ func (s *ServiceInstance) PbmRetrieveServiceContent(_ *types.PbmRetrieveServiceC
 
 type ProfileManager struct {
 	vim.ManagedObjectReference
+
+	profiles       []types.BasePbmProfile
+	profileDetails map[string]types.PbmProfileDetails
+}
+
+func (m *ProfileManager) init(_ *simulator.Registry) {
+	m.profiles = slices.Clone(vcenter67DefaultProfiles)
+
+	// Ensure the default encryption profile has the encryption IOFILTER as this
+	// is required when detecting whether a policy supports encryption.
+	m.profileDetails = map[string]types.PbmProfileDetails{
+		defaultEncryptionProfile.ProfileId.UniqueId: {
+			Profile: defaultEncryptionProfile,
+			IofInfos: []types.PbmIofilterInfo{
+				{
+					FilterType: string(types.PbmIofilterInfoFilterTypeENCRYPTION),
+				},
+			},
+		},
+	}
 }
 
 func (m *ProfileManager) PbmQueryProfile(req *types.PbmQueryProfile) soap.HasFault {
 	body := new(methods.PbmQueryProfileBody)
 	body.Res = new(types.PbmQueryProfileResponse)
 
-	for i := range profiles {
-		b, ok := profiles[i].(types.BasePbmCapabilityProfile)
+	for i := range m.profiles {
+		b, ok := m.profiles[i].(types.BasePbmCapabilityProfile)
 		if !ok {
 			continue
 		}
@@ -143,7 +166,7 @@ func (m *ProfileManager) PbmRetrieveContent(req *types.PbmRetrieveContent) soap.
 	var res []types.BasePbmProfile
 
 	match := func(id string) bool {
-		for _, p := range profiles {
+		for _, p := range m.profiles {
 			if id == p.GetPbmProfile().ProfileId.UniqueId {
 				res = append(res, p)
 				return true
@@ -191,7 +214,7 @@ func (m *ProfileManager) PbmCreate(ctx *simulator.Context, req *types.PbmCreate)
 		LineOfService:            "",
 	}
 
-	profiles = append(profiles, profile)
+	m.profiles = append(m.profiles, profile)
 	body.Res.Returnval.UniqueId = profile.PbmProfile.ProfileId.UniqueId
 
 	return body
@@ -201,11 +224,11 @@ func (m *ProfileManager) PbmDelete(req *types.PbmDelete) soap.HasFault {
 	body := new(methods.PbmDeleteBody)
 
 	for _, id := range req.ProfileId {
-		for i, p := range profiles {
+		for i, p := range m.profiles {
 			pid := p.GetPbmProfile().ProfileId
 
 			if id == pid {
-				profiles = append(profiles[:i], profiles[i+1:]...)
+				m.profiles = append(m.profiles[:i], m.profiles[i+1:]...)
 				break
 			}
 		}
@@ -216,6 +239,33 @@ func (m *ProfileManager) PbmDelete(req *types.PbmDelete) soap.HasFault {
 	return body
 }
 
+func (m *ProfileManager) PbmQueryIOFiltersFromProfileId(req *types.PbmQueryIOFiltersFromProfileId) soap.HasFault {
+	body := methods.PbmQueryIOFiltersFromProfileIdBody{
+		Res: &types.PbmQueryIOFiltersFromProfileIdResponse{},
+	}
+
+	for i := range req.ProfileIds {
+		profileID := req.ProfileIds[i]
+		if profileDetails, ok := m.profileDetails[profileID.UniqueId]; ok {
+			body.Res.Returnval = append(
+				body.Res.Returnval,
+				types.PbmProfileToIofilterMap{
+					Key:       profileID,
+					Iofilters: profileDetails.IofInfos,
+				})
+		} else {
+			body.Fault_ = simulator.Fault("Invalid profile ID", &vim.RuntimeFault{})
+			break
+		}
+	}
+
+	if body.Fault_ != nil {
+		body.Res = nil
+	}
+
+	return &body
+}
+
 type PlacementSolver struct {
 	vim.ManagedObjectReference
 }
diff --git a/pbm/types/internal_types.go b/pbm/types/internal_types.go
new file mode 100644
index 00000000..d166d9dc
--- /dev/null
+++ b/pbm/types/internal_types.go
@@ -0,0 +1,95 @@
+/*
+Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package types
+
+import (
+	"reflect"
+
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+type ArrayOfPbmIofilterInfo struct {
+	PbmIofilterInfo []PbmIofilterInfo `xml:"PbmIofilterInfo,omitempty" json:"_value"`
+}
+
+func init() {
+	types.Add("pbm:ArrayOfPbmIofilterInfo", reflect.TypeOf((*ArrayOfPbmIofilterInfo)(nil)).Elem())
+}
+
+type ArrayOfPbmProfileToIofilterMap struct {
+	PbmProfileToIofilterMap []PbmProfileToIofilterMap `xml:"PbmProfileToIofilterMap,omitempty" json:"_value"`
+}
+
+func init() {
+	types.Add("pbm:ArrayOfPbmProfileToIofilterMap", reflect.TypeOf((*ArrayOfPbmProfileToIofilterMap)(nil)).Elem())
+}
+
+type PbmQueryIOFiltersFromProfileId PbmQueryIOFiltersFromProfileIdRequestType
+
+func init() {
+	types.Add("pbm:PbmQueryIOFiltersFromProfileId", reflect.TypeOf((*PbmQueryIOFiltersFromProfileId)(nil)).Elem())
+}
+
+type PbmQueryIOFiltersFromProfileIdRequestType struct {
+	This       types.ManagedObjectReference `xml:"_this" json:"_this"`
+	ProfileIds []PbmProfileId               `xml:"profileIds,typeattr" json:"profileIds"`
+}
+
+func init() {
+	types.Add("pbm:PbmQueryIOFiltersFromProfileIdRequestType", reflect.TypeOf((*PbmQueryIOFiltersFromProfileIdRequestType)(nil)).Elem())
+}
+
+type PbmQueryIOFiltersFromProfileIdResponse struct {
+	Returnval []PbmProfileToIofilterMap `xml:"returnval" json:"returnval"`
+}
+
+type PbmIofilterInfo struct {
+	types.DynamicData
+
+	VibId      string `xml:"vibId" json:"vibId"`
+	FilterType string `xml:"filterType,omitempty" json:"filterType,omitempty"`
+}
+
+func init() {
+	types.Add("pbm:PbmIofilterInfo", reflect.TypeOf((*PbmIofilterInfo)(nil)).Elem())
+}
+
+type PbmProfileToIofilterMap struct {
+	types.DynamicData
+
+	Key       PbmProfileId                `xml:"key,typeattr" json:"key"`
+	Iofilters []PbmIofilterInfo           `xml:"iofilters,omitempty" json:"iofilters,omitempty"`
+	Fault     *types.LocalizedMethodFault `xml:"fault,omitempty" json:"fault,omitempty"`
+}
+
+func init() {
+	types.Add("pbm:PbmProfileToIofilterMap", reflect.TypeOf((*PbmProfileToIofilterMap)(nil)).Elem())
+}
+
+type PbmProfileDetails struct {
+	types.DynamicData
+
+	Profile             BasePbmCapabilityProfile    `xml:"profile,typeattr" json:"profile"`
+	IofInfos            []PbmIofilterInfo           `xml:"iofInfos,omitempty" json:"iofInfos,omitempty"`
+	IofMethodFault      *types.LocalizedMethodFault `xml:"iofMethodFault,omitempty" json:"iofMethodFault,omitempty"`
+	IsReplicationPolicy bool                        `xml:"isReplicationPolicy" json:"isReplicationPolicy"`
+	RepMethodFault      *types.LocalizedMethodFault `xml:"repMethodFault,omitempty" json:"repMethodFault,omitempty"`
+}
+
+func init() {
+	types.Add("pbm:PbmProfileDetails", reflect.TypeOf((*PbmProfileDetails)(nil)).Elem())
+}


Test patch:
diff --git a/pbm/client_test.go b/pbm/client_test.go
index a3666e94..0500e050 100644
--- a/pbm/client_test.go
+++ b/pbm/client_test.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2017 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-    http://www.apache.org/licenses/LICENSE-2.0
+http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package pbm
+package pbm_test
 
 import (
 	"context"
@@ -23,10 +23,17 @@ import (
 	"sort"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
+
 	"github.com/vmware/govmomi"
+	"github.com/vmware/govmomi/fault"
+	"github.com/vmware/govmomi/pbm"
+	pbmsim "github.com/vmware/govmomi/pbm/simulator"
 	"github.com/vmware/govmomi/pbm/types"
 	"github.com/vmware/govmomi/property"
+	"github.com/vmware/govmomi/simulator"
 	"github.com/vmware/govmomi/view"
+	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/mo"
 	"github.com/vmware/govmomi/vim25/soap"
 	vim "github.com/vmware/govmomi/vim25/types"
@@ -52,7 +59,7 @@ func TestClient(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	pc, err := NewClient(ctx, c.Client)
+	pc, err := pbm.NewClient(ctx, c.Client)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -164,27 +171,27 @@ func TestClient(t *testing.T) {
 
 	// user spec for the profile.
 	// VSAN profile with 2 capability instances - hostFailuresToTolerate = 2, stripeWidth = 1
-	pbmCreateSpecForVSAN := CapabilityProfileCreateSpec{
+	pbmCreateSpecForVSAN := pbm.CapabilityProfileCreateSpec{
 		Name:        "Kubernetes-VSAN-TestPolicy",
 		Description: "VSAN Test policy create",
 		Category:    string(types.PbmProfileCategoryEnumREQUIREMENT),
-		CapabilityList: []Capability{
-			Capability{
+		CapabilityList: []pbm.Capability{
+			{
 				ID:        "hostFailuresToTolerate",
 				Namespace: "VSAN",
-				PropertyList: []Property{
-					Property{
+				PropertyList: []pbm.Property{
+					{
 						ID:       "hostFailuresToTolerate",
 						Value:    "2",
 						DataType: "int",
 					},
 				},
 			},
-			Capability{
+			{
 				ID:        "stripeWidth",
 				Namespace: "VSAN",
-				PropertyList: []Property{
-					Property{
+				PropertyList: []pbm.Property{
+					{
 						ID:       "stripeWidth",
 						Value:    "1",
 						DataType: "int",
@@ -195,7 +202,7 @@ func TestClient(t *testing.T) {
 	}
 
 	// Create PBM capability spec for the above defined user spec.
-	createSpecVSAN, err := CreateCapabilityProfileSpec(pbmCreateSpecForVSAN)
+	createSpecVSAN, err := pbm.CreateCapabilityProfileSpec(pbmCreateSpecForVSAN)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -229,37 +236,37 @@ func TestClient(t *testing.T) {
 
 	// user spec for the profile.
 	// VSAN profile with 2 capability instances - stripeWidth = 1 and an SIOC profile.
-	pbmCreateSpecVSANandSIOC := CapabilityProfileCreateSpec{
+	pbmCreateSpecVSANandSIOC := pbm.CapabilityProfileCreateSpec{
 		Name:        "Kubernetes-VSAN-SIOC-TestPolicy",
 		Description: "VSAN-SIOC-Test policy create",
 		Category:    string(types.PbmProfileCategoryEnumREQUIREMENT),
-		CapabilityList: []Capability{
-			Capability{
+		CapabilityList: []pbm.Capability{
+			{
 				ID:        "stripeWidth",
 				Namespace: "VSAN",
-				PropertyList: []Property{
-					Property{
+				PropertyList: []pbm.Property{
+					{
 						ID:       "stripeWidth",
 						Value:    "1",
 						DataType: "int",
 					},
 				},
 			},
-			Capability{
+			{
 				ID:        "spm@DATASTOREIOCONTROL",
 				Namespace: "spm",
-				PropertyList: []Property{
-					Property{
+				PropertyList: []pbm.Property{
+					{
 						ID:       "limit",
 						Value:    "200",
 						DataType: "int",
 					},
-					Property{
+					{
 						ID:       "reservation",
 						Value:    "1000",
 						DataType: "int",
 					},
-					Property{
+					{
 						ID:       "shares",
 						Value:    "2000",
 						DataType: "int",
@@ -270,7 +277,7 @@ func TestClient(t *testing.T) {
 	}
 
 	// Create PBM capability spec for the above defined user spec.
-	createSpecVSANandSIOC, err := CreateCapabilityProfileSpec(pbmCreateSpecVSANandSIOC)
+	createSpecVSANandSIOC, err := pbm.CreateCapabilityProfileSpec(pbmCreateSpecVSANandSIOC)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -313,3 +320,29 @@ func TestClient(t *testing.T) {
 	}
 	t.Logf("Profile: %+v successfully deleted", []types.PbmProfileId{*vsanProfileID, *vsansiocProfileID})
 }
+
+func TestSupportsEncryption(t *testing.T) {
+	t.Run("valid profile id", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			pbmc, err := pbm.NewClient(ctx, c)
+			assert.NoError(t, err)
+			assert.NotNil(t, pbmc)
+
+			ok, err := pbmc.SupportsEncryption(ctx, pbmsim.DefaultEncryptionProfileID)
+			assert.NoError(t, err)
+			assert.True(t, ok)
+		})
+	})
+	t.Run("invalid profile id", func(t *testing.T) {
+		simulator.Test(func(ctx context.Context, c *vim25.Client) {
+			pbmc, err := pbm.NewClient(ctx, c)
+			assert.NoError(t, err)
+			assert.NotNil(t, pbmc)
+
+			ok, err := pbmc.SupportsEncryption(ctx, "invalid")
+			assert.EqualError(t, err, "ServerFaultCode: Invalid profile ID")
+			assert.True(t, fault.Is(err, &vim.RuntimeFault{}))
+			assert.False(t, ok)
+		})
+	})
+}
diff --git a/pbm/simulator/simulator_test.go b/pbm/simulator/simulator_test.go
index 1ff82f71..a8198c41 100644
--- a/pbm/simulator/simulator_test.go
+++ b/pbm/simulator/simulator_test.go
@@ -172,22 +172,22 @@ func TestSimulator(t *testing.T) {
 		Description: "VSAN Test policy create",
 		Category:    string(types.PbmProfileCategoryEnumREQUIREMENT),
 		CapabilityList: []pbm.Capability{
-			pbm.Capability{
+			{
 				ID:        "hostFailuresToTolerate",
 				Namespace: "VSAN",
 				PropertyList: []pbm.Property{
-					pbm.Property{
+					{
 						ID:       "hostFailuresToTolerate",
 						Value:    "2",
 						DataType: "int",
 					},
 				},
 			},
-			pbm.Capability{
+			{
 				ID:        "stripeWidth",
 				Namespace: "VSAN",
 				PropertyList: []pbm.Property{
-					pbm.Property{
+					{
 						ID:       "stripeWidth",
 						Value:    "1",
 						DataType: "int",
@@ -247,32 +247,32 @@ func TestSimulator(t *testing.T) {
 		Description: "VSAN-SIOC-Test policy create",
 		Category:    string(types.PbmProfileCategoryEnumREQUIREMENT),
 		CapabilityList: []pbm.Capability{
-			pbm.Capability{
+			{
 				ID:        "stripeWidth",
 				Namespace: "VSAN",
 				PropertyList: []pbm.Property{
-					pbm.Property{
+					{
 						ID:       "stripeWidth",
 						Value:    "1",
 						DataType: "int",
 					},
 				},
 			},
-			pbm.Capability{
+			{
 				ID:        "spm@DATASTOREIOCONTROL",
 				Namespace: "spm",
 				PropertyList: []pbm.Property{
-					pbm.Property{
+					{
 						ID:       "limit",
 						Value:    "200",
 						DataType: "int",
 					},
-					pbm.Property{
+					{
 						ID:       "reservation",
 						Value:    "1000",
 						DataType: "int",
 					},
-					pbm.Property{
+					{
 						ID:       "shares",
 						Value:    "2000",
 						DataType: "int",