feat(region): add wire level for preparing network

There are three wire level for vmware: vcenter, datacenter and cluster.
According to different wire levels, the recommended network has a different structure.
This commit is contained in:
rainzm
2020-12-04 14:27:46 +08:00
parent 553747b7c5
commit a7eadb54e6
5 changed files with 410 additions and 256 deletions
+7
View File
@@ -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"`
+4
View File
@@ -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 (
+333 -255
View File
@@ -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
}
+3 -1
View File
@@ -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) {
+63
View File
@@ -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 {