Repository: vmware/govmomi
Pull request #3475 (2024-06-20 22:30:06)

govc: add storage.policy.create zonal option
New '-z' flag enables Zonal topology for multi-zone Supervisor

Uses the sub profile as created by the H5 UI, see for example:
```
govc storage.policy.info -json wcp-zonal | jq .policies[].profile.constraints.subProfiles
```
```json

[
  {
    "name": "Consumption domain",
    "capability": [
      {
        "id": {
          "namespace": "com.vmware.storage.consumptiondomain",
          "id": "StorageTopology"
        },
        "constraint": [
          {
            "propertyInstance": [
              {
                "id": "StorageTopologyType",
                "value": "Zonal"
              }
            ]
          }
        ]
      }
    ]
  }
]
```


Patch:
diff --git a/govc/USAGE.md b/govc/USAGE.md
index b48a0c94..f8ad98cf 100644
--- a/govc/USAGE.md
+++ b/govc/USAGE.md
@@ -5478,11 +5478,13 @@ Create VM Storage Policy.
 
 Examples:
   govc storage.policy.create -category my_cat -tag my_tag MyStoragePolicy # Tag based placement
+  govc storage.policy.create -z MyZonalPolicy # Zonal topology
 
 Options:
   -category=             Category
   -d=                    Description
   -tag=                  Tag
+  -z=false               Enable Zonal topology for multi-zone Supervisor
 ```
 
 ## storage.policy.info
diff --git a/govc/storage/policy/create.go b/govc/storage/policy/create.go
index ed4195bc..eccedd2b 100644
--- a/govc/storage/policy/create.go
+++ b/govc/storage/policy/create.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2020 VMware, Inc. All Rights Reserved.
+Copyright (c) 2020-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,
@@ -33,6 +33,7 @@ type create struct {
 	spec types.PbmCapabilityProfileCreateSpec
 	tag  string
 	cat  string
+	zone bool
 }
 
 func init() {
@@ -46,6 +47,7 @@ func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) {
 	f.StringVar(&cmd.spec.Description, "d", "", "Description")
 	f.StringVar(&cmd.tag, "tag", "", "Tag")
 	f.StringVar(&cmd.cat, "category", "", "Category")
+	f.BoolVar(&cmd.zone, "z", false, "Enable Zonal topology for multi-zone Supervisor")
 }
 
 func (cmd *create) Usage() string {
@@ -56,7 +58,8 @@ func (cmd *create) Description() string {
 	return `Create VM Storage Policy.
 
 Examples:
-  govc storage.policy.create -category my_cat -tag my_tag MyStoragePolicy # Tag based placement`
+  govc storage.policy.create -category my_cat -tag my_tag MyStoragePolicy # Tag based placement
+  govc storage.policy.create -z MyZonalPolicy # Zonal topology`
 }
 
 func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error {
@@ -65,33 +68,63 @@ func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error {
 	}
 
 	cmd.spec.Name = f.Arg(0)
+	cmd.spec.Category = string(types.PbmProfileCategoryEnumREQUIREMENT)
 	cmd.spec.ResourceType.ResourceType = string(types.PbmProfileResourceTypeEnumSTORAGE)
 
-	if cmd.tag == "" {
+	if cmd.tag == "" && !cmd.zone {
 		return flag.ErrHelp
 	}
 
-	id := fmt.Sprintf("com.vmware.storage.tag.%s.property", cmd.cat)
-	instance := types.PbmCapabilityInstance{
-		Id: types.PbmCapabilityMetadataUniqueId{
-			Namespace: "http://www.vmware.com/storage/tag",
-			Id:        cmd.cat,
-		},
-		Constraint: []types.PbmCapabilityConstraintInstance{{
-			PropertyInstance: []types.PbmCapabilityPropertyInstance{{
-				Id: id,
-				Value: types.PbmCapabilityDiscreteSet{
-					Values: []vim.AnyType{cmd.tag},
-				},
+	var profiles []types.PbmCapabilitySubProfile
+
+	if cmd.tag != "" {
+		id := fmt.Sprintf("com.vmware.storage.tag.%s.property", cmd.cat)
+		instance := types.PbmCapabilityInstance{
+			Id: types.PbmCapabilityMetadataUniqueId{
+				Namespace: "http://www.vmware.com/storage/tag",
+				Id:        cmd.cat,
+			},
+			Constraint: []types.PbmCapabilityConstraintInstance{{
+				PropertyInstance: []types.PbmCapabilityPropertyInstance{{
+					Id: id,
+					Value: types.PbmCapabilityDiscreteSet{
+						Values: []vim.AnyType{cmd.tag},
+					},
+				}},
 			}},
-		}},
+		}
+		profiles = append(profiles, types.PbmCapabilitySubProfile{
+			Name:       "Tag based placement",
+			Capability: []types.PbmCapabilityInstance{instance},
+		})
 	}
 
-	cmd.spec.Constraints = &types.PbmCapabilitySubProfileConstraints{
-		SubProfiles: []types.PbmCapabilitySubProfile{{
-			Name:       "Tag based placement",
+	if cmd.zone {
+		instance := types.PbmCapabilityInstance{
+			Id: types.PbmCapabilityMetadataUniqueId{
+				Namespace: "com.vmware.storage.consumptiondomain",
+				Id:        "StorageTopology",
+			},
+			Constraint: []types.PbmCapabilityConstraintInstance{
+				{
+					PropertyInstance: []types.PbmCapabilityPropertyInstance{
+						{
+							Id:       "StorageTopologyType",
+							Operator: "",
+							Value:    "Zonal",
+						},
+					},
+				},
+			},
+		}
+		profiles = append(profiles, types.PbmCapabilitySubProfile{
+			Name:       "Consumption domain",
 			Capability: []types.PbmCapabilityInstance{instance},
-		}},
+		})
+	}
+
+	cmd.spec.Constraints = &types.PbmCapabilitySubProfileConstraints{
+		SubProfiles: profiles,
 	}
 
 	c, err := cmd.PbmClient()


Test patch:
diff --git a/govc/test/storage.bats b/govc/test/storage.bats
old mode 100644
new mode 100755
index 3d914c3d..e31dd910
--- a/govc/test/storage.bats
+++ b/govc/test/storage.bats
@@ -26,3 +26,28 @@ load test_helper
     run govc storage.policy.info "vSAN Default Storage Policy"
     assert_success
 }
+
+@test "storage.policy.create" {
+  vcsim_env
+
+  run govc storage.policy.create MyStoragePolicy
+  assert_failure # at least one of -z or -tag required
+
+  run govc storage.policy.create -category my_cat -tag my_tag MyStoragePolicy
+  assert_success
+
+  run govc storage.policy.info MyStoragePolicy
+  assert_success
+
+  govc storage.policy.create -z MyZonalPolicy
+  assert_success
+
+  run govc storage.policy.info MyZonalPolicy
+  assert_success
+
+  run govc storage.policy.create -category my_cat -tag my_tag -z MyCombinedPolicy
+  assert_success
+
+  run govc storage.policy.info MyCombinedPolicy
+  assert_success
+}