Repository: vmware/govmomi
Pull request #2748 (2022-02-10 07:15:12)

Add PlaceVmsXCluster bindings and simulator
## Description

Adds new PlaceVmsXCluster method, types and vcsim support.

## 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

## Checklist:

- [x] My code follows the CONTRIBUTION [guidelines](./../CONTRIBUTING.md) of
  this project
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] 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
- [ ] Any dependent changes have been merged


Patch:
diff --git a/object/folder.go b/object/folder.go
index 7a69949f..6e0a7649 100644
--- a/object/folder.go
+++ b/object/folder.go
@@ -225,3 +225,17 @@ func (f Folder) MoveInto(ctx context.Context, list []types.ManagedObjectReferenc
 
 	return NewTask(f.c, res.Returnval), nil
 }
+
+func (f Folder) PlaceVmsXCluster(ctx context.Context, spec types.PlaceVmsXClusterSpec) (*types.PlaceVmsXClusterResult, error) {
+	req := types.PlaceVmsXCluster{
+		This:          f.Reference(),
+		PlacementSpec: spec,
+	}
+
+	res, err := methods.PlaceVmsXCluster(ctx, f.c, &req)
+	if err != nil {
+		return nil, err
+	}
+
+	return &res.Returnval, nil
+}
diff --git a/simulator/folder.go b/simulator/folder.go
index ac93249b..b0866813 100644
--- a/simulator/folder.go
+++ b/simulator/folder.go
@@ -721,3 +721,51 @@ func (f *Folder) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFaul
 		},
 	}
 }
+
+func (f *Folder) PlaceVmsXCluster(ctx *Context, req *types.PlaceVmsXCluster) soap.HasFault {
+	body := new(methods.PlaceVmsXClusterBody)
+
+	// Reject the request if it is against any folder other than the root folder.
+	if req.This != ctx.Map.content().RootFolder {
+		body.Fault_ = Fault("", new(types.InvalidRequest))
+	}
+
+	body.Res = new(types.PlaceVmsXClusterResponse)
+
+	for _, spec := range req.PlacementSpec.VmPlacementSpecs {
+		pspec := types.PlacementSpec{
+			PlacementType: string(types.PlacementSpecPlacementTypeCreate),
+			ConfigSpec:    &spec.ConfigSpec,
+		}
+
+		pools := req.PlacementSpec.ResourcePools
+		pool := ctx.Map.Get(pools[rand.Intn(len(pools))]).(*ResourcePool)
+		cluster := ctx.Map.Get(pool.Owner).(*ClusterComputeResource)
+
+		res := cluster.PlaceVm(ctx, &types.PlaceVm{
+			This:          f.Self,
+			PlacementSpec: pspec,
+		})
+
+		if res.Fault() != nil {
+			faults := types.PlaceVmsXClusterResultPlacementFaults{
+				VmName:       spec.ConfigSpec.Name,
+				ResourcePool: pool.Self,
+				Faults: []types.LocalizedMethodFault{
+					{
+						Fault: res.Fault().Detail.Fault.(types.BaseMethodFault),
+					},
+				},
+			}
+			body.Res.Returnval.Faults = append(body.Res.Returnval.Faults, faults)
+		} else {
+			placement := types.PlaceVmsXClusterResultPlacementInfo{
+				VmName:         spec.ConfigSpec.Name,
+				Recommendation: res.(*methods.PlaceVmBody).Res.Returnval.Recommendations[0],
+			}
+			body.Res.Returnval.PlacementInfos = append(body.Res.Returnval.PlacementInfos, placement)
+		}
+	}
+
+	return body
+}
diff --git a/sts/signer.go b/sts/signer.go
index 6ee0ffde..c6bf49d9 100644
--- a/sts/signer.go
+++ b/sts/signer.go
@@ -351,5 +351,5 @@ func isIPv6(s string) bool {
 	if ip == nil {
 		return false
 	}
-	return ip.To4() == nil
+	return len(ip) == net.IPv6len
 }
diff --git a/vim25/methods/unreleased.go b/vim25/methods/unreleased.go
new file mode 100644
index 00000000..a0ffff8c
--- /dev/null
+++ b/vim25/methods/unreleased.go
@@ -0,0 +1,44 @@
+/*
+   Copyright (c) 2022 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/vim25/soap"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+type PlaceVmsXClusterBody struct {
+	Req    *types.PlaceVmsXCluster         `xml:"urn:vim25 PlaceVmsXCluster,omitempty"`
+	Res    *types.PlaceVmsXClusterResponse `xml:"PlaceVmsXClusterResponse,omitempty"`
+	Fault_ *soap.Fault                     `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"`
+}
+
+func (b *PlaceVmsXClusterBody) Fault() *soap.Fault { return b.Fault_ }
+
+func PlaceVmsXCluster(ctx context.Context, r soap.RoundTripper, req *types.PlaceVmsXCluster) (*types.PlaceVmsXClusterResponse, error) {
+	var reqBody, resBody PlaceVmsXClusterBody
+
+	reqBody.Req = req
+
+	if err := r.RoundTrip(ctx, &reqBody, &resBody); err != nil {
+		return nil, err
+	}
+
+	return resBody.Res, nil
+}
diff --git a/vim25/types/unreleased.go b/vim25/types/unreleased.go
new file mode 100644
index 00000000..167184e8
--- /dev/null
+++ b/vim25/types/unreleased.go
@@ -0,0 +1,117 @@
+/*
+   Copyright (c) 2022 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"
+
+type ArrayOfPlaceVmsXClusterResultPlacementFaults struct {
+	PlaceVmsXClusterResultPlacementFaults []PlaceVmsXClusterResultPlacementFaults `xml:"PlaceVmsXClusterResultPlacementFaults,omitempty"`
+}
+
+func init() {
+	t["ArrayOfPlaceVmsXClusterResultPlacementFaults"] = reflect.TypeOf((*ArrayOfPlaceVmsXClusterResultPlacementFaults)(nil)).Elem()
+}
+
+type ArrayOfPlaceVmsXClusterResultPlacementInfo struct {
+	PlaceVmsXClusterResultPlacementInfo []PlaceVmsXClusterResultPlacementInfo `xml:"PlaceVmsXClusterResultPlacementInfo,omitempty"`
+}
+
+func init() {
+	t["ArrayOfPlaceVmsXClusterResultPlacementInfo"] = reflect.TypeOf((*ArrayOfPlaceVmsXClusterResultPlacementInfo)(nil)).Elem()
+}
+
+type ArrayOfPlaceVmsXClusterSpecVmPlacementSpec struct {
+	PlaceVmsXClusterSpecVmPlacementSpec []PlaceVmsXClusterSpecVmPlacementSpec `xml:"PlaceVmsXClusterSpecVmPlacementSpec,omitempty"`
+}
+
+func init() {
+	t["ArrayOfPlaceVmsXClusterSpecVmPlacementSpec"] = reflect.TypeOf((*ArrayOfPlaceVmsXClusterSpecVmPlacementSpec)(nil)).Elem()
+}
+
+type PlaceVmsXCluster PlaceVmsXClusterRequestType
+
+func init() {
+	t["PlaceVmsXCluster"] = reflect.TypeOf((*PlaceVmsXCluster)(nil)).Elem()
+}
+
+type PlaceVmsXClusterRequestType struct {
+	This          ManagedObjectReference `xml:"_this"`
+	PlacementSpec PlaceVmsXClusterSpec   `xml:"placementSpec"`
+}
+
+func init() {
+	t["PlaceVmsXClusterRequestType"] = reflect.TypeOf((*PlaceVmsXClusterRequestType)(nil)).Elem()
+}
+
+type PlaceVmsXClusterResponse struct {
+	Returnval PlaceVmsXClusterResult `xml:"returnval"`
+}
+
+type PlaceVmsXClusterResult struct {
+	DynamicData
+
+	PlacementInfos []PlaceVmsXClusterResultPlacementInfo   `xml:"placementInfos,omitempty"`
+	Faults         []PlaceVmsXClusterResultPlacementFaults `xml:"faults,omitempty"`
+}
+
+func init() {
+	t["PlaceVmsXClusterResult"] = reflect.TypeOf((*PlaceVmsXClusterResult)(nil)).Elem()
+}
+
+type PlaceVmsXClusterResultPlacementFaults struct {
+	DynamicData
+
+	ResourcePool ManagedObjectReference `xml:"resourcePool"`
+	VmName       string                 `xml:"vmName"`
+	Faults       []LocalizedMethodFault `xml:"faults,omitempty"`
+}
+
+func init() {
+	t["PlaceVmsXClusterResultPlacementFaults"] = reflect.TypeOf((*PlaceVmsXClusterResultPlacementFaults)(nil)).Elem()
+}
+
+type PlaceVmsXClusterResultPlacementInfo struct {
+	DynamicData
+
+	VmName         string                `xml:"vmName"`
+	Recommendation ClusterRecommendation `xml:"recommendation"`
+}
+
+func init() {
+	t["PlaceVmsXClusterResultPlacementInfo"] = reflect.TypeOf((*PlaceVmsXClusterResultPlacementInfo)(nil)).Elem()
+}
+
+type PlaceVmsXClusterSpec struct {
+	DynamicData
+
+	ResourcePools    []ManagedObjectReference              `xml:"resourcePools,omitempty"`
+	VmPlacementSpecs []PlaceVmsXClusterSpecVmPlacementSpec `xml:"vmPlacementSpecs,omitempty"`
+}
+
+func init() {
+	t["PlaceVmsXClusterSpec"] = reflect.TypeOf((*PlaceVmsXClusterSpec)(nil)).Elem()
+}
+
+type PlaceVmsXClusterSpecVmPlacementSpec struct {
+	DynamicData
+
+	ConfigSpec VirtualMachineConfigSpec `xml:"configSpec"`
+}
+
+func init() {
+	t["PlaceVmsXClusterSpecVmPlacementSpec"] = reflect.TypeOf((*PlaceVmsXClusterSpecVmPlacementSpec)(nil)).Elem()
+}


Test patch:
diff --git a/simulator/folder_test.go b/simulator/folder_test.go
index 9fb3e0b5..c898fbc9 100644
--- a/simulator/folder_test.go
+++ b/simulator/folder_test.go
@@ -26,6 +26,7 @@ import (
 	"github.com/vmware/govmomi/object"
 	"github.com/vmware/govmomi/simulator/esx"
 	"github.com/vmware/govmomi/simulator/vpx"
+	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/methods"
 	"github.com/vmware/govmomi/vim25/mo"
 	"github.com/vmware/govmomi/vim25/soap"
@@ -608,3 +609,39 @@ func TestFolderCreateDVS(t *testing.T) {
 		t.Error("expected error")
 	}
 }
+
+func TestPlaceVmsXCluster(t *testing.T) {
+	vpx := VPX()
+	vpx.Cluster = 3
+
+	Test(func(ctx context.Context, c *vim25.Client) {
+		finder := find.NewFinder(c, false)
+
+		spec := types.PlaceVmsXClusterSpec{}
+
+		pools, err := finder.ResourcePoolList(ctx, "/DC0/host/DC0_C*/*")
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		for _, pool := range pools {
+			spec.ResourcePools = append(spec.ResourcePools, pool.Reference())
+		}
+
+		spec.VmPlacementSpecs = []types.PlaceVmsXClusterSpecVmPlacementSpec{{
+			ConfigSpec: types.VirtualMachineConfigSpec{
+				Name: "test-vm",
+			},
+		}}
+
+		folder := object.NewRootFolder(c)
+		res, err := folder.PlaceVmsXCluster(ctx, spec)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if len(res.PlacementInfos) != len(spec.VmPlacementSpecs) {
+			t.Errorf("%d PlacementInfos vs %d VmPlacementSpecs", len(res.PlacementInfos), len(spec.VmPlacementSpecs))
+		}
+	}, vpx)
+}
diff --git a/sts/client_test.go b/sts/client_test.go
index 3b49b1ad..8196e50c 100644
--- a/sts/client_test.go
+++ b/sts/client_test.go
@@ -30,7 +30,6 @@ import (
 	"github.com/vmware/govmomi/session"
 	"github.com/vmware/govmomi/ssoadmin"
 	"github.com/vmware/govmomi/ssoadmin/types"
-	"github.com/vmware/govmomi/vapi/rest"
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/methods"
 	"github.com/vmware/govmomi/vim25/soap"
@@ -161,12 +160,6 @@ func TestIssueHOK(t *testing.T) {
 	}
 
 	log.Printf("current time=%s", now)
-
-	rc := rest.NewClient(c)
-	err = rc.LoginByToken(rc.WithSigner(ctx, s))
-	if err != nil {
-		t.Fatal(err)
-	}
 }
 
 func TestIssueTokenByToken(t *testing.T) {
diff --git a/sts/signer_test.go b/sts/signer_test.go
index 6acacc51..f857b612 100644
--- a/sts/signer_test.go
+++ b/sts/signer_test.go
@@ -140,20 +140,3 @@ func TestSigner(t *testing.T) {
 		t.Fatal(err)
 	}
 }
-
-func TestIsIPv6(t *testing.T) {
-	tests := []struct {
-		ip string
-		v6 bool
-	}{
-		{"0:0:0:0:0:0:0:1", true},
-		{"10.0.0.42", false},
-	}
-
-	for _, test := range tests {
-		v6 := isIPv6(test.ip)
-		if v6 != test.v6 {
-			t.Errorf("%s: expected %t, got %t", test.ip, test.v6, v6)
-		}
-	}
-}