Skip to content

Commit

Permalink
Add service locator to CNS RelocateVolume spec
Browse files Browse the repository at this point in the history
  • Loading branch information
gohilankit committed Feb 14, 2024
1 parent c832515 commit 4190715
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 14 deletions.
109 changes: 98 additions & 11 deletions cns/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
)

const VSphere70u3VersionInt = 703
const VSphere80u3VersionInt = 803

func TestClient(t *testing.T) {
// set CNS_DEBUG to true if you need to emit soap traces from these tests
Expand All @@ -62,9 +63,13 @@ func TestClient(t *testing.T) {
// example: export BACKING_DISK_URL_PATH='https://vc-ip/folder/vmdkfilePath.vmdk?dcPath=DataCenterPath&dsName=DataStoreName'
backingDiskURLPath := os.Getenv("BACKING_DISK_URL_PATH")

// set REMOTE_VC_URL, REMOTE_DATACENTER only if you want to test cross-VC CNS operations.
// For instance, testing cross-VC volume migration.
remoteVcUrl := os.Getenv("REMOTE_VC_URL")
remoteDatacenter := os.Getenv("REMOTE_DATACENTER")

// if datastoreForMigration is not set, test for CNS Relocate API of a volume to another datastore is skipped.
// input format is same as CNS_DATASTORE. Format eg. "vSANDirect_10.92.217.162_mpx.vmhba0:C0:T2:L0"/ "vsandatastore"
// make sure that migration datastore is accessible from host on which CNS_DATASTORE is mounted.
datastoreForMigration := os.Getenv("CNS_MIGRATION_DATASTORE")

// if spbmPolicyId4Reconfig is not set, test for CnsReconfigVolumePolicy API will be skipped
Expand All @@ -74,7 +79,7 @@ func TestClient(t *testing.T) {
if url == "" || datacenter == "" || datastore == "" {
t.Skip("CNS_VC_URL or CNS_DATACENTER or CNS_DATASTORE is not set")
}
resporcePoolPath := os.Getenv("CNS_RESOURCE_POOL_PATH") // example "/datacenter-name/host/host-ip/Resources" or /datacenter-name/host/cluster-name/Resources
resourcePoolPath := os.Getenv("CNS_RESOURCE_POOL_PATH") // example "/datacenter-name/host/host-ip/Resources" or /datacenter-name/host/cluster-name/Resources
u, err := soap.ParseURL(url)
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -537,14 +542,72 @@ func TestClient(t *testing.T) {
// Test Relocate API
// Relocate API is not supported on ReleaseVSAN67u3 and ReleaseVSAN70
// This API is available on vSphere 7.0u1 onward
if cnsClient.Version != ReleaseVSAN67u3 && cnsClient.Version != ReleaseVSAN70 && datastoreForMigration != "" {
migrationDS, err := finder.Datastore(ctx, datastoreForMigration)
if err != nil {
t.Fatal(err)
if cnsClient.Version != ReleaseVSAN67u3 && cnsClient.Version != ReleaseVSAN70 &&
datastoreForMigration != "" {

var migrationDS *object.Datastore
var serviceLocatorInstance *vim25types.ServiceLocator = nil

// Cross-VC migration.
// This is only supported on 8.0u3 onwards.
if remoteVcUrl != "" && isvSphereVersion80U3orAbove(ctx, c.ServiceContent.About) {
remoteUrl, err := soap.ParseURL(remoteVcUrl)
if err != nil {
t.Fatal(err)
}
remoteVcClient, err := govmomi.NewClient(ctx, remoteUrl, true)
if err != nil {
t.Fatal(err)
}
// UseServiceVersion sets soap.Client.Version to the current version of the service endpoint via /sdk/vsanServiceVersions.xml
remoteVcClient.UseServiceVersion("vsan")
remoteCnsClient, err := NewClient(ctx, remoteVcClient.Client)
if err != nil {
t.Fatal(err)
}
remoteFinder := find.NewFinder(remoteCnsClient.vim25Client, false)
remoteDc, err := remoteFinder.Datacenter(ctx, remoteDatacenter)
if err != nil {
t.Fatal(err)
}
remoteFinder.SetDatacenter(remoteDc)

migrationDS, err = remoteFinder.Datastore(ctx, datastoreForMigration)
if err != nil {
t.Fatal(err)
}

// Get ServiceLocator instance for remote VC.
userName := remoteUrl.User.Username()
password, _ := remoteUrl.User.Password()
serviceLocatorInstance, err = GetServiceLocatorInstance(ctx, userName, password, remoteVcClient)
if err != nil {
t.Fatal(err)
}

} else {
// Same VC migration
migrationDS, err = finder.Datastore(ctx, datastoreForMigration)
if err != nil {
t.Fatal(err)
}
}

blockVolRelocateSpec := cnstypes.CnsBlockVolumeRelocateSpec{
CnsVolumeRelocateSpec: cnstypes.CnsVolumeRelocateSpec{
VolumeId: cnstypes.CnsVolumeId{
Id: volumeId,
},
Datastore: migrationDS.Reference(),
},
}
if serviceLocatorInstance != nil {
blockVolRelocateSpec.ServiceLocator = serviceLocatorInstance
}
t.Logf("Relocating volume %v to datastore %+v", pretty.Sprint(volumeId), migrationDS.Reference())
relocateSpec := cnstypes.NewCnsBlockVolumeRelocateSpec(volumeId, migrationDS.Reference())
relocateTask, err := cnsClient.RelocateVolume(ctx, relocateSpec)

t.Logf("Relocating volume using the spec: %+v", pretty.Sprint(blockVolRelocateSpec))

relocateTask, err := cnsClient.RelocateVolume(ctx, blockVolRelocateSpec)
if err != nil {
t.Errorf("Failed to migrate volume with Relocate API. Error: %+v \n", err)
t.Fatal(err)
Expand Down Expand Up @@ -778,10 +841,10 @@ func TestClient(t *testing.T) {
t.Fatal(err)
}
var resourcePool *object.ResourcePool
if resporcePoolPath == "" {
if resourcePoolPath == "" {
resourcePool, err = finder.DefaultResourcePool(ctx)
} else {
resourcePool, err = finder.ResourcePool(ctx, resporcePoolPath)
resourcePool, err = finder.ResourcePool(ctx, resourcePoolPath)
}
if err != nil {
t.Errorf("Error occurred while getting DefaultResourcePool. err: %+v", err)
Expand Down Expand Up @@ -930,6 +993,7 @@ func TestClient(t *testing.T) {
t.Logf("Successfully queried Volume using queryAsync API. queryVolumeAsyncTaskResult: %+v", pretty.Sprint(queryVolumeAsyncTaskResult))
}
}

// Test DeleteVolume API
t.Logf("Deleting volume: %+v", volumeIDList)
deleteTask, err := cnsClient.DeleteVolume(ctx, volumeIDList, true)
Expand Down Expand Up @@ -1414,3 +1478,26 @@ func isvSphereVersion70U3orAbove(ctx context.Context, aboutInfo vim25types.About
// For all other versions
return false
}

// isvSphereVersion80U3orAbove checks if specified version is 8.0 Update 3 or higher
// The method takes aboutInfo{} as input which contains details about
// VC version, build number and so on.
// If the version is 8.0 Update 3 or higher, the method returns true, else returns false
// along with appropriate errors during failure cases
func isvSphereVersion80U3orAbove(ctx context.Context, aboutInfo vim25types.AboutInfo) bool {
items := strings.Split(aboutInfo.Version, ".")
version := strings.Join(items[:], "")
// Convert version string to string, Ex: "8.0.3" becomes 803, "8.0.3.1" becomes 703
if len(version) >= 3 {
vSphereVersionInt, err := strconv.Atoi(version[0:3])
if err != nil {
return false
}
// Check if the current vSphere version is 8.0.3 or higher
if vSphereVersionInt >= VSphere80u3VersionInt {
return true
}
}
// For all other versions
return false
}
62 changes: 62 additions & 0 deletions cns/cns_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,22 @@ limitations under the License.
package cns

import (
"bytes"
"context"
"crypto/sha1"
"crypto/tls"
"errors"
"fmt"

"github.com/vmware/govmomi"
cnstypes "github.com/vmware/govmomi/cns/types"
"github.com/vmware/govmomi/object"
vim25types "github.com/vmware/govmomi/vim25/types"
)

// DefaultVCenterPort is the default port used to access vCenter.
const DefaultVCenterPort string = "443"

// GetTaskInfo gets the task info given a task
func GetTaskInfo(ctx context.Context, task *object.Task) (*vim25types.TaskInfo, error) {
taskInfo, err := task.WaitForResult(ctx, nil)
Expand Down Expand Up @@ -178,3 +186,57 @@ func dropUnknownVolumeMetadataUpdateSpecElements(c *Client, updateSpecList []cns
}
return updateSpecList
}

// GetServiceLocatorInstance takes as input VC userName, VC password, VC client
// and returns a service locator instance for the VC.
func GetServiceLocatorInstance(ctx context.Context, userName string, password string, vcClient *govmomi.Client) (*vim25types.ServiceLocator, error) {
hostPortStr := fmt.Sprintf("%s:%s", vcClient.URL().Hostname(), DefaultVCenterPort)
url := fmt.Sprintf("https://%s/sdk", hostPortStr)

thumbprint, err := getSslThumbprint(ctx, hostPortStr, vcClient)
if err != nil {
return nil, fmt.Errorf("failed to get ssl thumbprint. Error: %+v", err)
}

serviceLocatorInstance := &vim25types.ServiceLocator{
InstanceUuid: vcClient.ServiceContent.About.InstanceUuid,
Url: url,
Credential: &vim25types.ServiceLocatorNamePassword{
Username: userName,
Password: password,
},
SslThumbprint: thumbprint,
}

return serviceLocatorInstance, nil
}

// getSslThumbprint connects to the given network address, initiates a TLS handshake
// and retrieves the SSL thumprint from the resulting TLS connection.
func getSslThumbprint(ctx context.Context, addr string, vcClient *govmomi.Client) (string, error) {
conn, err := vcClient.Client.DefaultTransport().DialTLSContext(ctx, "tcp", addr)
if err != nil {
return "", err
}
defer conn.Close()

tlsConn, ok := conn.(*tls.Conn)
if !ok {
return "", fmt.Errorf("cannot convert net connection to tls connection")
}

cert := tlsConn.ConnectionState().PeerCertificates[0]
thumbPrint := sha1.Sum(cert.Raw)

// Get hex representation for each byte of the thumbprint separated by colon.
// e.g. B9:12:79:B9:36:1B:B5:C1:2F:20:4A:DD:BD:0C:3D:31:82:99:CB:5C
var buf bytes.Buffer
for i, f := range thumbPrint {
if i > 0 {
fmt.Fprintf(&buf, ":")
}
fmt.Fprintf(&buf, "%02X", f)
}

return buf.String(), nil
}
7 changes: 4 additions & 3 deletions cns/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,9 +535,10 @@ type CnsRelocateVolumeResponse struct {
type CnsVolumeRelocateSpec struct {
types.DynamicData

VolumeId CnsVolumeId `xml:"volumeId"`
Datastore types.ManagedObjectReference `xml:"datastore"`
Profile []types.BaseVirtualMachineProfileSpec `xml:"profile,omitempty,typeattr"`
VolumeId CnsVolumeId `xml:"volumeId"`
Datastore types.ManagedObjectReference `xml:"datastore"`
Profile []types.BaseVirtualMachineProfileSpec `xml:"profile,omitempty,typeattr"`
ServiceLocator *types.ServiceLocator `xml:"serviceLocator,omitempty"`
}

func init() {
Expand Down

0 comments on commit 4190715

Please sign in to comment.