k8s.io/kubernetes@v1.29.3/test/e2e/storage/vsphere/nodemapper.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     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 vsphere
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"strings"
    23  	"sync"
    24  
    25  	"github.com/onsi/ginkgo/v2"
    26  	"github.com/vmware/govmomi/object"
    27  	"github.com/vmware/govmomi/vapi/rest"
    28  	"github.com/vmware/govmomi/vapi/tags"
    29  	"github.com/vmware/govmomi/vim25/mo"
    30  	"github.com/vmware/govmomi/vim25/types"
    31  	v1 "k8s.io/api/core/v1"
    32  	"k8s.io/kubernetes/test/e2e/framework"
    33  
    34  	neturl "net/url"
    35  )
    36  
    37  // NodeMapper contains information to generate nameToNodeInfo and vcToZoneDatastore maps
    38  type NodeMapper struct {
    39  	nodeInfoRWLock        *sync.RWMutex
    40  	nameToNodeInfo        map[string]*NodeInfo
    41  	vcToZoneDatastoresMap map[string](map[string][]string)
    42  }
    43  
    44  // NodeInfo contains information about vcenter nodes
    45  type NodeInfo struct {
    46  	Name              string
    47  	DataCenterRef     types.ManagedObjectReference
    48  	VirtualMachineRef types.ManagedObjectReference
    49  	HostSystemRef     types.ManagedObjectReference
    50  	VSphere           *VSphere
    51  	Zones             []string
    52  }
    53  
    54  const (
    55  	datacenterType             = "Datacenter"
    56  	clusterComputeResourceType = "ClusterComputeResource"
    57  	hostSystemType             = "HostSystem"
    58  )
    59  
    60  // NewNodeMapper returns a new NodeMapper
    61  func NewNodeMapper() *NodeMapper {
    62  	return &NodeMapper{
    63  		nodeInfoRWLock:        &sync.RWMutex{},
    64  		nameToNodeInfo:        make(map[string]*NodeInfo),
    65  		vcToZoneDatastoresMap: make(map[string](map[string][]string)),
    66  	}
    67  }
    68  
    69  // GenerateNodeMap populates node name to node info map
    70  func (nm *NodeMapper) GenerateNodeMap(vSphereInstances map[string]*VSphere, nodeList v1.NodeList) error {
    71  	type VMSearch struct {
    72  		vs         *VSphere
    73  		datacenter *object.Datacenter
    74  	}
    75  
    76  	var wg sync.WaitGroup
    77  	var queueChannel []*VMSearch
    78  
    79  	var datacenters []*object.Datacenter
    80  	var err error
    81  	for _, vs := range vSphereInstances {
    82  
    83  		// Create context
    84  		ctx, cancel := context.WithCancel(context.Background())
    85  		defer cancel()
    86  
    87  		if vs.Config.Datacenters == "" {
    88  			datacenters, err = vs.GetAllDatacenter(ctx)
    89  			if err != nil {
    90  				framework.Logf("NodeMapper error: %v", err)
    91  				continue
    92  			}
    93  		} else {
    94  			dcName := strings.Split(vs.Config.Datacenters, ",")
    95  			for _, dc := range dcName {
    96  				dc = strings.TrimSpace(dc)
    97  				if dc == "" {
    98  					continue
    99  				}
   100  				datacenter, err := vs.GetDatacenter(ctx, dc)
   101  				if err != nil {
   102  					framework.Logf("NodeMapper error dc: %s \n err: %v", dc, err)
   103  
   104  					continue
   105  				}
   106  				datacenters = append(datacenters, datacenter)
   107  			}
   108  		}
   109  
   110  		for _, dc := range datacenters {
   111  			framework.Logf("Search candidates vc=%s and datacenter=%s", vs.Config.Hostname, dc.Name())
   112  			queueChannel = append(queueChannel, &VMSearch{vs: vs, datacenter: dc})
   113  		}
   114  	}
   115  
   116  	for _, node := range nodeList.Items {
   117  		n := node
   118  		wg.Add(1)
   119  		go func() {
   120  			nodeUUID := getUUIDFromProviderID(n.Spec.ProviderID)
   121  			framework.Logf("Searching for node with UUID: %s", nodeUUID)
   122  			for _, res := range queueChannel {
   123  				ctx, cancel := context.WithCancel(context.Background())
   124  				defer cancel()
   125  				vm, err := res.vs.GetVMByUUID(ctx, nodeUUID, res.datacenter)
   126  				if err != nil {
   127  					framework.Logf("Error %v while looking for node=%s in vc=%s and datacenter=%s",
   128  						err, n.Name, res.vs.Config.Hostname, res.datacenter.Name())
   129  					continue
   130  				}
   131  				if vm != nil {
   132  					hostSystemRef := res.vs.GetHostFromVMReference(ctx, vm.Reference())
   133  					zones := retrieveZoneInformationForNode(n.Name, res.vs, hostSystemRef)
   134  					framework.Logf("Found node %s as vm=%+v placed on host=%+v under zones %s in vc=%s and datacenter=%s",
   135  						n.Name, vm, hostSystemRef, zones, res.vs.Config.Hostname, res.datacenter.Name())
   136  					nodeInfo := &NodeInfo{Name: n.Name, DataCenterRef: res.datacenter.Reference(), VirtualMachineRef: vm.Reference(), HostSystemRef: hostSystemRef, VSphere: res.vs, Zones: zones}
   137  					nm.SetNodeInfo(n.Name, nodeInfo)
   138  					break
   139  				}
   140  			}
   141  			wg.Done()
   142  		}()
   143  	}
   144  	wg.Wait()
   145  
   146  	if len(nm.nameToNodeInfo) != len(nodeList.Items) {
   147  		return errors.New("all nodes not mapped to respective vSphere")
   148  	}
   149  	return nil
   150  }
   151  
   152  // Establish rest connection to retrieve tag manager stub
   153  func withTagsClient(ctx context.Context, connection *VSphere, f func(c *rest.Client) error) error {
   154  	c := rest.NewClient(connection.Client.Client)
   155  	user := neturl.UserPassword(connection.Config.Username, connection.Config.Password)
   156  	if err := c.Login(ctx, user); err != nil {
   157  		return err
   158  	}
   159  	ginkgo.DeferCleanup(c.Logout)
   160  	return f(c)
   161  }
   162  
   163  // Iterates over each node and retrieves the zones in which they are placed
   164  func retrieveZoneInformationForNode(nodeName string, connection *VSphere, hostSystemRef types.ManagedObjectReference) []string {
   165  	ctx, cancel := context.WithCancel(context.Background())
   166  	defer cancel()
   167  	var zones []string
   168  	pc := connection.Client.ServiceContent.PropertyCollector
   169  	withTagsClient(ctx, connection, func(c *rest.Client) error {
   170  		client := tags.NewManager(c)
   171  		// Example result: ["Host", "Cluster", "Datacenter"]
   172  		ancestors, err := mo.Ancestors(ctx, connection.Client, pc, hostSystemRef)
   173  		if err != nil {
   174  			return err
   175  		}
   176  
   177  		var validAncestors []mo.ManagedEntity
   178  		// Filter out only Datacenter, ClusterComputeResource and HostSystem type objects. These objects will be
   179  		// in the following order ["Datacenter" < "ClusterComputeResource" < "HostSystem"] so that the highest
   180  		// zone precedence will be received by the HostSystem type.
   181  		for _, ancestor := range ancestors {
   182  			moType := ancestor.ExtensibleManagedObject.Self.Type
   183  			if moType == datacenterType || moType == clusterComputeResourceType || moType == hostSystemType {
   184  				validAncestors = append(validAncestors, ancestor)
   185  			}
   186  		}
   187  
   188  		for _, ancestor := range validAncestors {
   189  			var zonesAttachedToObject []string
   190  			tags, err := client.ListAttachedTags(ctx, ancestor)
   191  			if err != nil {
   192  				return err
   193  			}
   194  			for _, value := range tags {
   195  				tag, err := client.GetTag(ctx, value)
   196  				if err != nil {
   197  					return err
   198  				}
   199  				category, err := client.GetCategory(ctx, tag.CategoryID)
   200  				if err != nil {
   201  					return err
   202  				}
   203  				switch {
   204  				case category.Name == "k8s-zone":
   205  					framework.Logf("Found %s associated with %s for %s", tag.Name, ancestor.Name, nodeName)
   206  					zonesAttachedToObject = append(zonesAttachedToObject, tag.Name)
   207  				case category.Name == "k8s-region":
   208  					framework.Logf("Found %s associated with %s for %s", tag.Name, ancestor.Name, nodeName)
   209  				}
   210  			}
   211  			// Overwrite zone information if it exists for this object
   212  			if len(zonesAttachedToObject) != 0 {
   213  				zones = zonesAttachedToObject
   214  			}
   215  		}
   216  		return nil
   217  	})
   218  	return zones
   219  }
   220  
   221  // GenerateZoneToDatastoreMap generates a mapping of zone to datastore for easily verifying volume placement
   222  func (nm *NodeMapper) GenerateZoneToDatastoreMap() error {
   223  	// 1. Create zone to hosts map for each VC
   224  	var vcToZoneHostsMap = make(map[string](map[string][]string))
   225  	// 2. Create host to datastores map for each VC
   226  	var vcToHostDatastoresMap = make(map[string](map[string][]string))
   227  	ctx, cancel := context.WithCancel(context.Background())
   228  	defer cancel()
   229  	// 3. Populate vcToZoneHostsMap and vcToHostDatastoresMap
   230  	for _, nodeInfo := range nm.nameToNodeInfo {
   231  		vc := nodeInfo.VSphere.Config.Hostname
   232  		host := nodeInfo.HostSystemRef.Value
   233  		for _, zone := range nodeInfo.Zones {
   234  			if vcToZoneHostsMap[vc] == nil {
   235  				vcToZoneHostsMap[vc] = make(map[string][]string)
   236  			}
   237  			// Populating vcToZoneHostsMap using the HostSystemRef and Zone fields from each NodeInfo
   238  			hosts := vcToZoneHostsMap[vc][zone]
   239  			hosts = append(hosts, host)
   240  			vcToZoneHostsMap[vc][zone] = hosts
   241  		}
   242  		if vcToHostDatastoresMap[vc] == nil {
   243  			vcToHostDatastoresMap[vc] = make(map[string][]string)
   244  		}
   245  		datastores := vcToHostDatastoresMap[vc][host]
   246  		// Populating vcToHostDatastoresMap by finding out the datastores mounted on node's host
   247  		datastoreRefs := nodeInfo.VSphere.GetDatastoresMountedOnHost(ctx, nodeInfo.HostSystemRef)
   248  		for _, datastore := range datastoreRefs {
   249  			datastores = append(datastores, datastore.Value)
   250  		}
   251  		vcToHostDatastoresMap[vc][host] = datastores
   252  	}
   253  	// 4, Populate vcToZoneDatastoresMap from vcToZoneHostsMap and vcToHostDatastoresMap
   254  	for vc, zoneToHostsMap := range vcToZoneHostsMap {
   255  		for zone, hosts := range zoneToHostsMap {
   256  			commonDatastores := retrieveCommonDatastoresAmongHosts(hosts, vcToHostDatastoresMap[vc])
   257  			if nm.vcToZoneDatastoresMap[vc] == nil {
   258  				nm.vcToZoneDatastoresMap[vc] = make(map[string][]string)
   259  			}
   260  			nm.vcToZoneDatastoresMap[vc][zone] = commonDatastores
   261  		}
   262  	}
   263  	framework.Logf("Zone to datastores map : %+v", nm.vcToZoneDatastoresMap)
   264  	return nil
   265  }
   266  
   267  // retrieveCommonDatastoresAmongHosts retrieves the common datastores from the specified hosts
   268  func retrieveCommonDatastoresAmongHosts(hosts []string, hostToDatastoresMap map[string][]string) []string {
   269  	var datastoreCountMap = make(map[string]int)
   270  	for _, host := range hosts {
   271  		for _, datastore := range hostToDatastoresMap[host] {
   272  			datastoreCountMap[datastore] = datastoreCountMap[datastore] + 1
   273  		}
   274  	}
   275  	var commonDatastores []string
   276  	numHosts := len(hosts)
   277  	for datastore, count := range datastoreCountMap {
   278  		if count == numHosts {
   279  			commonDatastores = append(commonDatastores, datastore)
   280  		}
   281  	}
   282  	return commonDatastores
   283  }
   284  
   285  // GetDatastoresInZone returns all the datastores in the specified zone
   286  func (nm *NodeMapper) GetDatastoresInZone(vc string, zone string) []string {
   287  	nm.nodeInfoRWLock.RLock()
   288  	defer nm.nodeInfoRWLock.RUnlock()
   289  	return nm.vcToZoneDatastoresMap[vc][zone]
   290  }
   291  
   292  // GetNodeInfo returns NodeInfo for given nodeName
   293  func (nm *NodeMapper) GetNodeInfo(nodeName string) *NodeInfo {
   294  	nm.nodeInfoRWLock.RLock()
   295  	defer nm.nodeInfoRWLock.RUnlock()
   296  	return nm.nameToNodeInfo[nodeName]
   297  }
   298  
   299  // SetNodeInfo sets NodeInfo for given nodeName. This function is not thread safe. Users need to handle concurrency.
   300  func (nm *NodeMapper) SetNodeInfo(nodeName string, nodeInfo *NodeInfo) {
   301  	nm.nodeInfoRWLock.Lock()
   302  	defer nm.nodeInfoRWLock.Unlock()
   303  	nm.nameToNodeInfo[nodeName] = nodeInfo
   304  }