diff --git a/pkg/apis/compute/cloudaccount.go b/pkg/apis/compute/cloudaccount.go index ea45f44dfc..9a38364c8b 100644 --- a/pkg/apis/compute/cloudaccount.go +++ b/pkg/apis/compute/cloudaccount.go @@ -334,9 +334,16 @@ type CloudaccountPerformPublicInput struct { type CloudaccountPerformPrepareNetsInput struct { CloudaccountCreateInput + + // enum: vcenter,datacenter,cluster + WireLevelForVmware string `json:"wire_level_for_vmware"` } type CloudaccountPerformPrepareNetsOutput struct { + CAWireNets []CAWireNet `json:"wire_networks"` +} + +type CAWireNet struct { SuggestedWire CAWireConf `json:"suggested_wire"` SuitableWire string `json:"suitable_wire,allowempty"` Hosts []CAHostNet `json:"hosts"` diff --git a/pkg/apis/compute/cloudaccount_const.go b/pkg/apis/compute/cloudaccount_const.go index 90cc2107eb..e6440a691c 100644 --- a/pkg/apis/compute/cloudaccount_const.go +++ b/pkg/apis/compute/cloudaccount_const.go @@ -61,6 +61,10 @@ const ( ZSTACK_BRAND_DSTACK = "DStack" ONECLOUD_BRAND_ONECLOUD = "OneCloud" + + CLOUD_ACCOUNT_WIRE_LEVEL_VCENTER = "vcenter" + CLOUD_ACCOUNT_WIRE_LEVEL_DATACENTER = "datacenter" + CLOUD_ACCOUNT_WIRE_LEVEL_CLUSTER = "cluster" ) const ( diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index 7f68a8a1eb..2c853f4c8e 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -357,6 +357,74 @@ func (scm *SCloudaccountManager) AllowPerformPrepareNets(_ context.Context, user return db.IsAdminAllowPerform(userCred, scm, "prepare-nets") } +type sHostVMIp struct { + hostIps map[string]string + vms []esxi.SSimpleVM + prefix string +} + +func (scm *SCloudaccountManager) hostVMIPsPrepareNets(ctx context.Context, client *esxi.SESXiClient, input api.CloudaccountPerformPrepareNetsInput) ([]sHostVMIp, error) { + caName := input.Name + wireLevel := input.WireLevelForVmware + ret := make([]sHostVMIp, 0) + if len(wireLevel) == 0 { + wireLevel = api.CLOUD_ACCOUNT_WIRE_LEVEL_VCENTER + } + switch wireLevel { + case api.CLOUD_ACCOUNT_WIRE_LEVEL_VCENTER: + hostIps, simpleVms, err := client.HostVmIPs(ctx) + if err != nil { + return ret, errors.Wrap(err, "unable to fetch ips of hosts and vms") + } + ret = append(ret, sHostVMIp{ + hostIps: hostIps, + vms: simpleVms, + prefix: caName, + }) + case api.CLOUD_ACCOUNT_WIRE_LEVEL_DATACENTER: + dcs, err := client.GetDatacenters() + if err != nil { + return ret, errors.Wrap(err, "GetDatacenters") + } + for _, dc := range dcs { + hostIps, simpleVms, err := client.HostVmIPsInDc(ctx, dc) + if err != nil { + return ret, errors.Wrapf(err, "unable to fetch ips of hosts and vms for dc %q", dc.GetName()) + } + ret = append(ret, sHostVMIp{ + hostIps: hostIps, + vms: simpleVms, + prefix: fmt.Sprintf("%s/%s", caName, dc.GetName()), + }) + } + case api.CLOUD_ACCOUNT_WIRE_LEVEL_CLUSTER: + dcs, err := client.GetDatacenters() + if err != nil { + return ret, errors.Wrap(err, "GetDatacenters") + } + for _, dc := range dcs { + clusters, err := dc.ListClusters() + if err != nil { + return nil, errors.Wrapf(err, "unable to ListCluster for dc %q", dc.GetName()) + } + for _, cluster := range clusters { + hostIps, simpleVms, err := client.HostVmIPsInCluster(ctx, cluster) + if err != nil { + return ret, errors.Wrapf(err, "unable to fetch ips of hosts and vms for dc %q cluster %q", dc.GetName(), cluster.GetName()) + } + ret = append(ret, sHostVMIp{ + hostIps: hostIps, + vms: simpleVms, + prefix: fmt.Sprintf("%s/%s/%s", caName, dc.GetName(), cluster.GetName()), + }) + } + } + default: + return nil, httperrors.NewInputParameterError("valid wire_level_for_vmware, accept vcenter, datacenter, cluster") + } + return ret, nil +} + // Performpreparenets searches for suitable network facilities for physical and virtual machines under the cloud account or provides configuration recommendations for network facilities before importing a cloud account. func (scm *SCloudaccountManager) PerformPrepareNets(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudaccountPerformPrepareNetsInput) (api.CloudaccountPerformPrepareNetsOutput, error) { var ( @@ -438,9 +506,9 @@ func (scm *SCloudaccountManager) PerformPrepareNets(ctx context.Context, userCre } // hack client := iregion.(*esxi.SESXiClient) - hostIps, simpleVms, err := client.HostVmIPs(ctx) + hostVMIPs, err := scm.hostVMIPsPrepareNets(ctx, client, input) if err != nil { - return output, errors.Wrap(err, "unable to fetch ips of hosts and vms") + return output, err } // fetch networks networks := make([][]SNetwork, len(wires)) @@ -451,198 +519,181 @@ func (scm *SCloudaccountManager) PerformPrepareNets(ctx context.Context, userCre } networks[i] = nets } + output.CAWireNets = make([]api.CAWireNet, 0, len(hostVMIPs)) + for _, hv := range hostVMIPs { + var ( + wireNet api.CAWireNet + hostIps = hv.hostIps + simpleVms = hv.vms + prefix = hv.prefix + ) - // key of ipHosts is host's ip - ipHosts := make(map[netutils.IPV4Addr]string, len(hostIps)) - for name, ip := range hostIps { - addr, err := netutils.NewIPV4Addr(ip) - if err != nil { - return output, err - } - ipHosts[addr] = name - } - // Find suitable wire and the network containing the Host IP in suitable wire. - var ( - tmpSocre int - maxScore = len(ipHosts) - suitableWire *SWire - suitableNetworks map[netutils.IPV4Addr]*SNetwork - ) - for i, nets := range networks { - score := 0 - tmpSNs := make(map[netutils.IPV4Addr]*SNetwork) - ipRanges := make([]netutils.IPV4AddrRange, len(nets)) - for i2 := range ipRanges { - ipRanges[i2] = nets[i2].GetIPRange() - } - for ip := range ipHosts { - for i := range ipRanges { - if !ipRanges[i].Contains(ip) { - continue - } - tmpSNs[ip] = &nets[i] - score += 1 - break - } - } - if score > tmpSocre { - tmpSocre = score - suitableWire = &wires[i] - suitableNetworks = tmpSNs - } - if tmpSocre == maxScore { - break - } - } - if suitableWire != nil { - output.SuitableWire = suitableWire.GetId() - } else { - output.SuggestedWire = api.CAWireConf{ - ZoneIds: zoneids, - Name: input.Name + "-wire", - Description: fmt.Sprintf("Auto created Wire for VMware account %q", input.Name), - } - } - - // Give the suggested network configuration for the Host IP that does not have a corresponding suitable network. - noNetHostIP := make([]netutils.IPV4Addr, 0, len(ipHosts)) - for ip, name := range ipHosts { - rnet := api.CAHostNet{ - Name: name, - IP: ip.String(), - } - if net, ok := suitableNetworks[ip]; ok { - rnet.SuitableNetwork = net.GetId() - } else { - noNetHostIP = append(noNetHostIP, ip) - } - output.Hosts = append(output.Hosts, rnet) - } - - if len(noNetHostIP) > 0 { - sConfs := scm.suggestHostNetworks(noNetHostIP) - confs := make([]api.CANetConf, len(sConfs)) - for i := range confs { - confs[i].CASimpleNetConf = sConfs[i] - confs[i].Name = fmt.Sprintf("%s-host-network-%d", input.Name, i+1) - } - output.HostSuggestedNetworks = confs - } - - // Find the suitable network containing the VM IP, and if not, give the corresponding suggested network configuration in this project. - var allNets []SNetwork - if suitableWire != nil { - allNets, err = suitableWire.getNetworks(userCred, rbacutils.ScopeSystem) - if err != nil { - return output, err - } - } - - type vm struct { - FakeID int - IP netutils.IPV4Addr - Name string - VlanID int32 - } - vms := make([]vm, 0, len(simpleVms)) - var nip netutils.IPV4Addr - guestMap := make(map[int]*api.CAGuestNet) - for i := range simpleVms { - id := i - if len(simpleVms[i].IPVlans) == 0 { - if _, ok := guestMap[id]; !ok { - guestMap[id] = &api.CAGuestNet{ - Name: simpleVms[i].Name, - IPNets: []api.CAIPNet{}, - } - } - } - for _, ipVlan := range simpleVms[i].IPVlans { - nip, err = netutils.NewIPV4Addr(ipVlan.IP) + // key of ipHosts is host's ip + ipHosts := make(map[netutils.IPV4Addr]string, len(hostIps)) + for name, ip := range hostIps { + addr, err := netutils.NewIPV4Addr(ip) if err != nil { return output, err } - vms = append(vms, vm{ - FakeID: i, - IP: nip, - Name: simpleVms[i].Name, - VlanID: ipVlan.VlanID, - }) + ipHosts[addr] = name } - } - // sort vms via vm's ip - sort.Slice(vms, func(i, j int) bool { - return vms[i].IP < vms[j].IP - }) - - type sExcludeNet struct { - StartIp netutils.IPV4Addr - EndIp netutils.IPV4Addr - Id string - } - excludeNets := make([]sExcludeNet, 0, len(allNets)+len(output.HostSuggestedNetworks)) - for i := range allNets { - startIp, _ := netutils.NewIPV4Addr(allNets[i].GuestIpStart) - endIp, _ := netutils.NewIPV4Addr(allNets[i].GuestIpEnd) - excludeNets = append(excludeNets, sExcludeNet{ - StartIp: startIp, - EndIp: endIp, - Id: allNets[i].GetId(), - }) - } - for i := range output.HostSuggestedNetworks { - startIp, _ := netutils.NewIPV4Addr(output.HostSuggestedNetworks[i].GuestIpStart) - endIp, _ := netutils.NewIPV4Addr(output.HostSuggestedNetworks[i].GuestIpEnd) - excludeNets = append(excludeNets, sExcludeNet{ - StartIp: startIp, - EndIp: endIp, - }) - } - // sort excludeNets via their GuestIpStart - sort.Slice(excludeNets, func(i, j int) bool { - return excludeNets[i].StartIp < excludeNets[j].StartIp - }) - - svNets := make([]api.CASimpleNetConf, 0, 5) - var vmi, neti int - -Loop: - for neti = 0; neti < len(excludeNets); { - if vmi >= len(vms) { - break + // Find suitable wire and the network containing the Host IP in suitable wire. + var ( + tmpSocre int + maxScore = len(ipHosts) + suitableWire *SWire + suitableNetworks map[netutils.IPV4Addr]*SNetwork + ) + for i, nets := range networks { + score := 0 + tmpSNs := make(map[netutils.IPV4Addr]*SNetwork) + ipRanges := make([]netutils.IPV4AddrRange, len(nets)) + for i2 := range ipRanges { + ipRanges[i2] = nets[i2].GetIPRange() + } + for ip := range ipHosts { + for i := range ipRanges { + if !ipRanges[i].Contains(ip) { + continue + } + tmpSNs[ip] = &nets[i] + score += 1 + break + } + } + if score > tmpSocre { + tmpSocre = score + suitableWire = &wires[i] + suitableNetworks = tmpSNs + } + if tmpSocre == maxScore { + break + } } - startIp := excludeNets[neti].StartIp - endIp := excludeNets[neti].EndIp - switch { - case vms[vmi].IP > endIp: - neti++ - case vms[vmi].IP >= startIp: - for vms[vmi].IP <= endIp { - id := vms[vmi].FakeID + if suitableWire != nil { + wireNet.SuitableWire = suitableWire.GetId() + } else { + wireNet.SuggestedWire = api.CAWireConf{ + ZoneIds: zoneids, + Name: prefix + "-wire", + Description: fmt.Sprintf("Auto created Wire for VMware account %q", input.Name), + } + } + + // Give the suggested network configuration for the Host IP that does not have a corresponding suitable network. + noNetHostIP := make([]netutils.IPV4Addr, 0, len(ipHosts)) + for ip, name := range ipHosts { + rnet := api.CAHostNet{ + Name: name, + IP: ip.String(), + } + if net, ok := suitableNetworks[ip]; ok { + rnet.SuitableNetwork = net.GetId() + } else { + noNetHostIP = append(noNetHostIP, ip) + } + wireNet.Hosts = append(wireNet.Hosts, rnet) + } + + if len(noNetHostIP) > 0 { + sConfs := scm.suggestHostNetworks(noNetHostIP) + confs := make([]api.CANetConf, len(sConfs)) + for i := range confs { + confs[i].CASimpleNetConf = sConfs[i] + confs[i].Name = fmt.Sprintf("%s-host-network-%d", prefix, i+1) + } + wireNet.HostSuggestedNetworks = confs + } + + // Find the suitable network containing the VM IP, and if not, give the corresponding suggested network configuration in this project. + var allNets []SNetwork + if suitableWire != nil { + allNets, err = suitableWire.getNetworks(userCred, rbacutils.ScopeSystem) + if err != nil { + return output, err + } + } + + type vm struct { + FakeID int + IP netutils.IPV4Addr + Name string + VlanID int32 + } + vms := make([]vm, 0, len(simpleVms)) + var nip netutils.IPV4Addr + guestMap := make(map[int]*api.CAGuestNet) + for i := range simpleVms { + id := i + if len(simpleVms[i].IPVlans) == 0 { if _, ok := guestMap[id]; !ok { guestMap[id] = &api.CAGuestNet{ - Name: vms[vmi].Name, + Name: simpleVms[i].Name, IPNets: []api.CAIPNet{}, } } - guestMap[id].IPNets = append(guestMap[id].IPNets, api.CAIPNet{ - IP: vms[vmi].IP.String(), - SuitableNetwork: excludeNets[neti].Id, - VlanID: vms[vmi].VlanID, - }) - vmi += 1 - if vmi == len(vms) { - break Loop - } } - neti++ - default: - for vms[vmi].IP < startIp { - suggestStartIp := vms[vmi].IP - suggestEndIp := vms[vmi].IP - endLimitIp := suggestStartIp.NetAddr(24) + 255 - vlanId := vms[vmi].VlanID - for { + for _, ipVlan := range simpleVms[i].IPVlans { + nip, err = netutils.NewIPV4Addr(ipVlan.IP) + if err != nil { + return output, err + } + vms = append(vms, vm{ + FakeID: i, + IP: nip, + Name: simpleVms[i].Name, + VlanID: ipVlan.VlanID, + }) + } + } + // sort vms via vm's ip + sort.Slice(vms, func(i, j int) bool { + return vms[i].IP < vms[j].IP + }) + + type sExcludeNet struct { + StartIp netutils.IPV4Addr + EndIp netutils.IPV4Addr + Id string + } + excludeNets := make([]sExcludeNet, 0, len(allNets)+len(wireNet.HostSuggestedNetworks)) + for i := range allNets { + startIp, _ := netutils.NewIPV4Addr(allNets[i].GuestIpStart) + endIp, _ := netutils.NewIPV4Addr(allNets[i].GuestIpEnd) + excludeNets = append(excludeNets, sExcludeNet{ + StartIp: startIp, + EndIp: endIp, + Id: allNets[i].GetId(), + }) + } + for i := range wireNet.HostSuggestedNetworks { + startIp, _ := netutils.NewIPV4Addr(wireNet.HostSuggestedNetworks[i].GuestIpStart) + endIp, _ := netutils.NewIPV4Addr(wireNet.HostSuggestedNetworks[i].GuestIpEnd) + excludeNets = append(excludeNets, sExcludeNet{ + StartIp: startIp, + EndIp: endIp, + }) + } + // sort excludeNets via their GuestIpStart + sort.Slice(excludeNets, func(i, j int) bool { + return excludeNets[i].StartIp < excludeNets[j].StartIp + }) + + svNets := make([]api.CASimpleNetConf, 0, 5) + var vmi, neti int + + Loop: + for neti = 0; neti < len(excludeNets); { + if vmi >= len(vms) { + break + } + startIp := excludeNets[neti].StartIp + endIp := excludeNets[neti].EndIp + switch { + case vms[vmi].IP > endIp: + neti++ + case vms[vmi].IP >= startIp: + for vms[vmi].IP <= endIp { id := vms[vmi].FakeID if _, ok := guestMap[id]; !ok { guestMap[id] = &api.CAGuestNet{ @@ -651,92 +702,119 @@ Loop: } } guestMap[id].IPNets = append(guestMap[id].IPNets, api.CAIPNet{ - IP: vms[vmi].IP.String(), - VlanID: vms[vmi].VlanID, + IP: vms[vmi].IP.String(), + SuitableNetwork: excludeNets[neti].Id, + VlanID: vms[vmi].VlanID, }) - vmi++ + vmi += 1 if vmi == len(vms) { - break + break Loop } - if suggestEndIp == endLimitIp { - break - } - if vms[vmi].IP > suggestEndIp+1 { - break - } - if vms[vmi].IP >= startIp { - break - } - if vms[vmi].VlanID != vlanId { - break - } - suggestEndIp = vms[vmi].IP } - svNets = append(svNets, api.CASimpleNetConf{ - GuestIpStart: suggestStartIp.String(), - GuestIpEnd: suggestEndIp.String(), - VlanID: vlanId, - GuestIpMask: 24, - GuestGateway: (suggestStartIp.NetAddr(24) + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi)).String(), + neti++ + default: + for vms[vmi].IP < startIp { + suggestStartIp := vms[vmi].IP + suggestEndIp := vms[vmi].IP + endLimitIp := suggestStartIp.NetAddr(24) + 255 + vlanId := vms[vmi].VlanID + for { + id := vms[vmi].FakeID + if _, ok := guestMap[id]; !ok { + guestMap[id] = &api.CAGuestNet{ + Name: vms[vmi].Name, + IPNets: []api.CAIPNet{}, + } + } + guestMap[id].IPNets = append(guestMap[id].IPNets, api.CAIPNet{ + IP: vms[vmi].IP.String(), + VlanID: vms[vmi].VlanID, + }) + vmi++ + if vmi == len(vms) { + break + } + if suggestEndIp == endLimitIp { + break + } + if vms[vmi].IP > suggestEndIp+1 { + break + } + if vms[vmi].IP >= startIp { + break + } + if vms[vmi].VlanID != vlanId { + break + } + suggestEndIp = vms[vmi].IP + } + svNets = append(svNets, api.CASimpleNetConf{ + GuestIpStart: suggestStartIp.String(), + GuestIpEnd: suggestEndIp.String(), + VlanID: vlanId, + GuestIpMask: 24, + GuestGateway: (suggestStartIp.NetAddr(24) + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi)).String(), + }) + if vmi == len(vms) { + break Loop + } + } + } + } + + for vmi < len(vms) { + suggestStartIp := vms[vmi].IP + endLimitIp := suggestStartIp.NetAddr(24) + 255 + suggestEndIp := suggestStartIp + vlanId := vms[vmi].VlanID + for { + id := vms[vmi].FakeID + if _, ok := guestMap[id]; !ok { + guestMap[id] = &api.CAGuestNet{ + Name: vms[vmi].Name, + IPNets: []api.CAIPNet{}, + } + } + guestMap[id].IPNets = append(guestMap[id].IPNets, api.CAIPNet{ + IP: vms[vmi].IP.String(), }) + vmi++ if vmi == len(vms) { - break Loop + break } - } - } - } - - for vmi < len(vms) { - suggestStartIp := vms[vmi].IP - endLimitIp := suggestStartIp.NetAddr(24) + 255 - suggestEndIp := suggestStartIp - vlanId := vms[vmi].VlanID - for { - id := vms[vmi].FakeID - if _, ok := guestMap[id]; !ok { - guestMap[id] = &api.CAGuestNet{ - Name: vms[vmi].Name, - IPNets: []api.CAIPNet{}, + if suggestEndIp == endLimitIp { + break } + if vms[vmi].IP > suggestEndIp+1 { + break + } + if vms[vmi].VlanID != vlanId { + break + } + suggestEndIp = vms[vmi].IP } - guestMap[id].IPNets = append(guestMap[id].IPNets, api.CAIPNet{ - IP: vms[vmi].IP.String(), + svNets = append(svNets, api.CASimpleNetConf{ + GuestIpStart: suggestStartIp.String(), + GuestIpEnd: suggestEndIp.String(), + VlanID: vlanId, + GuestIpMask: 24, + GuestGateway: (suggestStartIp.NetAddr(24) + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi)).String(), }) - vmi++ - if vmi == len(vms) { - break - } - if suggestEndIp == endLimitIp { - break - } - if vms[vmi].IP > suggestEndIp+1 { - break - } - if vms[vmi].VlanID != vlanId { - break - } - suggestEndIp = vms[vmi].IP } - svNets = append(svNets, api.CASimpleNetConf{ - GuestIpStart: suggestStartIp.String(), - GuestIpEnd: suggestEndIp.String(), - VlanID: vlanId, - GuestIpMask: 24, - GuestGateway: (suggestStartIp.NetAddr(24) + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi)).String(), - }) - } - for _, guest := range guestMap { - output.Guests = append(output.Guests, *guest) - } - - if len(svNets) > 0 { - confs := make([]api.CANetConf, len(svNets)) - for i := range confs { - confs[i].CASimpleNetConf = svNets[i] - confs[i].Name = fmt.Sprintf("%s-guest-network-%d", input.Name, i+1) + for _, guest := range guestMap { + wireNet.Guests = append(wireNet.Guests, *guest) } - output.GuestSuggestedNetworks = confs + + if len(svNets) > 0 { + confs := make([]api.CANetConf, len(svNets)) + for i := range confs { + confs[i].CASimpleNetConf = svNets[i] + confs[i].Name = fmt.Sprintf("%s-guest-network-%d", prefix, i+1) + } + wireNet.GuestSuggestedNetworks = confs + } + output.CAWireNets = append(output.CAWireNets, wireNet) } return output, nil } diff --git a/pkg/mcclient/options/cloudaccounts.go b/pkg/mcclient/options/cloudaccounts.go index a5f45e08fd..2146235c48 100644 --- a/pkg/mcclient/options/cloudaccounts.go +++ b/pkg/mcclient/options/cloudaccounts.go @@ -750,10 +750,12 @@ func (opts *SCtyunCloudAccountUpdateOptions) Params() (jsonutils.JSONObject, err } type SVMwareCloudAccountPrepareNetsOptions struct { + SVMwareCredentialWithEnvironment + Project string `help:"project for this account"` ProjectDomain string `help:"domain for this account"` + WireLevel string `help:"wire level for this account" choices:"vcenter|datacenter|cluster" json:"wire_level_for_vmware"` NAME string `help:"name for this account"` - SVMwareCredentialWithEnvironment } func (opts *SVMwareCloudAccountPrepareNetsOptions) Params() (jsonutils.JSONObject, error) { diff --git a/pkg/multicloud/esxi/manager.go b/pkg/multicloud/esxi/manager.go index 182faa30da..fb5151bf82 100644 --- a/pkg/multicloud/esxi/manager.go +++ b/pkg/multicloud/esxi/manager.go @@ -724,6 +724,69 @@ func (cli *SESXiClient) dvpgKeyVlanMap() (*sync.Map, error) { return ret, nil } +func (cli *SESXiClient) HostVmIPsInDc(ctx context.Context, dc *SDatacenter) (map[string]string, []SSimpleVM, error) { + var hosts []mo.HostSystem + err := cli.scanMObjects(dc.object.Entity().Self, HOST_SYSTEM_PROPS, &hosts) + if err != nil { + return nil, nil, errors.Wrap(err, "scanMObjects") + } + return cli.hostVMIPs(ctx, hosts) +} + +func (cli *SESXiClient) HostVmIPsInCluster(ctx context.Context, cluster *SCluster) (map[string]string, []SSimpleVM, + error) { + var hosts []mo.HostSystem + err := cli.scanMObjects(cluster.object.Entity().Self, HOST_SYSTEM_PROPS, &hosts) + if err != nil { + return nil, nil, errors.Wrap(err, "scanMObjects") + } + return cli.hostVMIPs(ctx, hosts) +} + +func (cli *SESXiClient) hostVMIPs(ctx context.Context, hosts []mo.HostSystem) (map[string]string, []SSimpleVM, error) { + dvpgKeyVlanMap, err := cli.dvpgKeyVlanMap() + if err != nil { + return nil, nil, errors.Wrap(err, "unable to get dvpgKeyVlanMap") + } + group, ctx := errgroup.WithContext(ctx) + collection := make([][]SSimpleVM, len(hosts)) + for i := range hosts { + j := i + group.Go(func() error { + vmIps, err := cli.vmIPs(&hosts[j], dvpgKeyVlanMap) + if err != nil { + return err + } + collection[j] = vmIps + return nil + }) + } + + hostIps := make(map[string]string, len(hosts)) + for i := range hosts { + // find ip + host := &SHost{SManagedObject: newManagedObject(cli, &hosts[i], nil)} + ip := host.GetAccessIp() + hostIps[host.GetName()] = ip + } + err = group.Wait() + if err != nil { + return nil, nil, err + } + // length + length := 0 + for i := range collection { + length += len(collection[i]) + } + svms := make([]SSimpleVM, 0, length) + for i := range collection { + for j := range collection[i] { + svms = append(svms, collection[i][j]) + } + } + return hostIps, svms, nil +} + func (cli *SESXiClient) HostVmIPs(ctx context.Context) (map[string]string, []SSimpleVM, error) { dvpgKeyVlanMap, err := cli.dvpgKeyVlanMap() if err != nil {