github.com/vmware/govmomi@v0.37.2/cns/cns_util.go (about)

     1  /*
     2  Copyright (c) 2019 VMware, Inc. All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cns
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"crypto/sha1"
    23  	"crypto/tls"
    24  	"errors"
    25  	"fmt"
    26  
    27  	"github.com/vmware/govmomi"
    28  	cnstypes "github.com/vmware/govmomi/cns/types"
    29  	"github.com/vmware/govmomi/object"
    30  	vim25types "github.com/vmware/govmomi/vim25/types"
    31  )
    32  
    33  // DefaultVCenterPort is the default port used to access vCenter.
    34  const DefaultVCenterPort string = "443"
    35  
    36  // GetTaskInfo gets the task info given a task
    37  func GetTaskInfo(ctx context.Context, task *object.Task) (*vim25types.TaskInfo, error) {
    38  	taskInfo, err := task.WaitForResult(ctx, nil)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	return taskInfo, nil
    43  }
    44  
    45  // GetQuerySnapshotsTaskResult gets the task result of QuerySnapshots given a task info
    46  func GetQuerySnapshotsTaskResult(ctx context.Context, taskInfo *vim25types.TaskInfo) (*cnstypes.CnsSnapshotQueryResult, error) {
    47  	if taskInfo == nil {
    48  		return nil, errors.New("TaskInfo is empty")
    49  	}
    50  	if taskInfo.Result != nil {
    51  		snapshotQueryResult := taskInfo.Result.(cnstypes.CnsSnapshotQueryResult)
    52  		if &snapshotQueryResult == nil {
    53  			return nil, errors.New("Cannot get SnapshotQueryResult")
    54  		}
    55  		return &snapshotQueryResult, nil
    56  	}
    57  	return nil, errors.New("TaskInfo result is empty")
    58  }
    59  
    60  // GetTaskResult gets the task result given a task info
    61  func GetTaskResult(ctx context.Context, taskInfo *vim25types.TaskInfo) (cnstypes.BaseCnsVolumeOperationResult, error) {
    62  	if taskInfo == nil {
    63  		return nil, errors.New("TaskInfo is empty")
    64  	}
    65  	if taskInfo.Result != nil {
    66  		volumeOperationBatchResult := taskInfo.Result.(cnstypes.CnsVolumeOperationBatchResult)
    67  		if &volumeOperationBatchResult == nil ||
    68  			volumeOperationBatchResult.VolumeResults == nil ||
    69  			len(volumeOperationBatchResult.VolumeResults) == 0 {
    70  			return nil, errors.New("Cannot get VolumeOperationResult")
    71  		}
    72  		return volumeOperationBatchResult.VolumeResults[0], nil
    73  	}
    74  	return nil, errors.New("TaskInfo result is empty")
    75  }
    76  
    77  // GetTaskResultArray gets the task result array for a specified task info
    78  func GetTaskResultArray(ctx context.Context, taskInfo *vim25types.TaskInfo) ([]cnstypes.BaseCnsVolumeOperationResult, error) {
    79  	if taskInfo == nil {
    80  		return nil, errors.New("TaskInfo is empty")
    81  	}
    82  	if taskInfo.Result != nil {
    83  		volumeOperationBatchResult := taskInfo.Result.(cnstypes.CnsVolumeOperationBatchResult)
    84  		if &volumeOperationBatchResult == nil ||
    85  			volumeOperationBatchResult.VolumeResults == nil ||
    86  			len(volumeOperationBatchResult.VolumeResults) == 0 {
    87  			return nil, errors.New("Cannot get VolumeOperationResult")
    88  		}
    89  		return volumeOperationBatchResult.VolumeResults, nil
    90  	}
    91  	return nil, errors.New("TaskInfo result is empty")
    92  }
    93  
    94  // dropUnknownCreateSpecElements helps drop newly added elements in the CnsVolumeCreateSpec, which are not known to the prior vSphere releases
    95  func dropUnknownCreateSpecElements(c *Client, createSpecList []cnstypes.CnsVolumeCreateSpec) []cnstypes.CnsVolumeCreateSpec {
    96  	updatedcreateSpecList := make([]cnstypes.CnsVolumeCreateSpec, 0, len(createSpecList))
    97  	switch c.Version {
    98  	case ReleaseVSAN67u3:
    99  		// Dropping optional fields not known to vSAN 6.7U3
   100  		for _, createSpec := range createSpecList {
   101  			createSpec.Metadata.ContainerCluster.ClusterFlavor = ""
   102  			createSpec.Metadata.ContainerCluster.ClusterDistribution = ""
   103  			createSpec.Metadata.ContainerClusterArray = nil
   104  			var updatedEntityMetadata []cnstypes.BaseCnsEntityMetadata
   105  			for _, entityMetadata := range createSpec.Metadata.EntityMetadata {
   106  				k8sEntityMetadata := interface{}(entityMetadata).(*cnstypes.CnsKubernetesEntityMetadata)
   107  				k8sEntityMetadata.ClusterID = ""
   108  				k8sEntityMetadata.ReferredEntity = nil
   109  				updatedEntityMetadata = append(updatedEntityMetadata, cnstypes.BaseCnsEntityMetadata(k8sEntityMetadata))
   110  			}
   111  			createSpec.Metadata.EntityMetadata = updatedEntityMetadata
   112  			_, ok := createSpec.BackingObjectDetails.(*cnstypes.CnsBlockBackingDetails)
   113  			if ok {
   114  				createSpec.BackingObjectDetails.(*cnstypes.CnsBlockBackingDetails).BackingDiskUrlPath = ""
   115  			}
   116  			updatedcreateSpecList = append(updatedcreateSpecList, createSpec)
   117  		}
   118  		createSpecList = updatedcreateSpecList
   119  	case ReleaseVSAN70:
   120  		// Dropping optional fields not known to vSAN 7.0
   121  		for _, createSpec := range createSpecList {
   122  			createSpec.Metadata.ContainerCluster.ClusterDistribution = ""
   123  			var updatedContainerClusterArray []cnstypes.CnsContainerCluster
   124  			for _, containerCluster := range createSpec.Metadata.ContainerClusterArray {
   125  				containerCluster.ClusterDistribution = ""
   126  				updatedContainerClusterArray = append(updatedContainerClusterArray, containerCluster)
   127  			}
   128  			createSpec.Metadata.ContainerClusterArray = updatedContainerClusterArray
   129  			_, ok := createSpec.BackingObjectDetails.(*cnstypes.CnsBlockBackingDetails)
   130  			if ok {
   131  				createSpec.BackingObjectDetails.(*cnstypes.CnsBlockBackingDetails).BackingDiskUrlPath = ""
   132  			}
   133  			updatedcreateSpecList = append(updatedcreateSpecList, createSpec)
   134  		}
   135  		createSpecList = updatedcreateSpecList
   136  	case ReleaseVSAN70u1:
   137  		// Dropping optional fields not known to vSAN 7.0U1
   138  		for _, createSpec := range createSpecList {
   139  			createSpec.Metadata.ContainerCluster.ClusterDistribution = ""
   140  			var updatedContainerClusterArray []cnstypes.CnsContainerCluster
   141  			for _, containerCluster := range createSpec.Metadata.ContainerClusterArray {
   142  				containerCluster.ClusterDistribution = ""
   143  				updatedContainerClusterArray = append(updatedContainerClusterArray, containerCluster)
   144  			}
   145  			createSpec.Metadata.ContainerClusterArray = updatedContainerClusterArray
   146  			updatedcreateSpecList = append(updatedcreateSpecList, createSpec)
   147  		}
   148  		createSpecList = updatedcreateSpecList
   149  	}
   150  	return createSpecList
   151  }
   152  
   153  // dropUnknownVolumeMetadataUpdateSpecElements helps drop newly added elements in the CnsVolumeMetadataUpdateSpec, which are not known to the prior vSphere releases
   154  func dropUnknownVolumeMetadataUpdateSpecElements(c *Client, updateSpecList []cnstypes.CnsVolumeMetadataUpdateSpec) []cnstypes.CnsVolumeMetadataUpdateSpec {
   155  	// Dropping optional fields not known to vSAN 6.7U3
   156  	if c.Version == ReleaseVSAN67u3 {
   157  		updatedUpdateSpecList := make([]cnstypes.CnsVolumeMetadataUpdateSpec, 0, len(updateSpecList))
   158  		for _, updateSpec := range updateSpecList {
   159  			updateSpec.Metadata.ContainerCluster.ClusterFlavor = ""
   160  			updateSpec.Metadata.ContainerCluster.ClusterDistribution = ""
   161  			var updatedEntityMetadata []cnstypes.BaseCnsEntityMetadata
   162  			for _, entityMetadata := range updateSpec.Metadata.EntityMetadata {
   163  				k8sEntityMetadata := interface{}(entityMetadata).(*cnstypes.CnsKubernetesEntityMetadata)
   164  				k8sEntityMetadata.ClusterID = ""
   165  				k8sEntityMetadata.ReferredEntity = nil
   166  				updatedEntityMetadata = append(updatedEntityMetadata, cnstypes.BaseCnsEntityMetadata(k8sEntityMetadata))
   167  			}
   168  			updateSpec.Metadata.ContainerClusterArray = nil
   169  			updateSpec.Metadata.EntityMetadata = updatedEntityMetadata
   170  			updatedUpdateSpecList = append(updatedUpdateSpecList, updateSpec)
   171  		}
   172  		updateSpecList = updatedUpdateSpecList
   173  	} else if c.Version == ReleaseVSAN70 || c.Version == ReleaseVSAN70u1 {
   174  		updatedUpdateSpecList := make([]cnstypes.CnsVolumeMetadataUpdateSpec, 0, len(updateSpecList))
   175  		for _, updateSpec := range updateSpecList {
   176  			updateSpec.Metadata.ContainerCluster.ClusterDistribution = ""
   177  			var updatedContainerClusterArray []cnstypes.CnsContainerCluster
   178  			for _, containerCluster := range updateSpec.Metadata.ContainerClusterArray {
   179  				containerCluster.ClusterDistribution = ""
   180  				updatedContainerClusterArray = append(updatedContainerClusterArray, containerCluster)
   181  			}
   182  			updateSpec.Metadata.ContainerClusterArray = updatedContainerClusterArray
   183  			updatedUpdateSpecList = append(updatedUpdateSpecList, updateSpec)
   184  		}
   185  		updateSpecList = updatedUpdateSpecList
   186  	}
   187  	return updateSpecList
   188  }
   189  
   190  // GetServiceLocatorInstance takes as input VC userName, VC password, VC client
   191  // and returns a service locator instance for the VC.
   192  func GetServiceLocatorInstance(ctx context.Context, userName string, password string, vcClient *govmomi.Client) (*vim25types.ServiceLocator, error) {
   193  	hostPortStr := fmt.Sprintf("%s:%s", vcClient.URL().Hostname(), DefaultVCenterPort)
   194  	url := fmt.Sprintf("https://%s/sdk", hostPortStr)
   195  
   196  	thumbprint, err := getSslThumbprint(ctx, hostPortStr, vcClient)
   197  	if err != nil {
   198  		return nil, fmt.Errorf("failed to get ssl thumbprint. Error: %+v", err)
   199  	}
   200  
   201  	serviceLocatorInstance := &vim25types.ServiceLocator{
   202  		InstanceUuid: vcClient.ServiceContent.About.InstanceUuid,
   203  		Url:          url,
   204  		Credential: &vim25types.ServiceLocatorNamePassword{
   205  			Username: userName,
   206  			Password: password,
   207  		},
   208  		SslThumbprint: thumbprint,
   209  	}
   210  
   211  	return serviceLocatorInstance, nil
   212  }
   213  
   214  // getSslThumbprint connects to the given network address, initiates a TLS handshake
   215  // and retrieves the SSL thumprint from the resulting TLS connection.
   216  func getSslThumbprint(ctx context.Context, addr string, vcClient *govmomi.Client) (string, error) {
   217  	conn, err := vcClient.Client.DefaultTransport().DialTLSContext(ctx, "tcp", addr)
   218  	if err != nil {
   219  		return "", err
   220  	}
   221  	defer conn.Close()
   222  
   223  	tlsConn, ok := conn.(*tls.Conn)
   224  	if !ok {
   225  		return "", fmt.Errorf("cannot convert net connection to tls connection")
   226  	}
   227  
   228  	cert := tlsConn.ConnectionState().PeerCertificates[0]
   229  	thumbPrint := sha1.Sum(cert.Raw)
   230  
   231  	// Get hex representation for each byte of the thumbprint separated by colon.
   232  	// e.g. B9:12:79:B9:36:1B:B5:C1:2F:20:4A:DD:BD:0C:3D:31:82:99:CB:5C
   233  	var buf bytes.Buffer
   234  	for i, f := range thumbPrint {
   235  		if i > 0 {
   236  			fmt.Fprintf(&buf, ":")
   237  		}
   238  		fmt.Fprintf(&buf, "%02X", f)
   239  	}
   240  
   241  	return buf.String(), nil
   242  }