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 }