fix: prepare esxi host network revisit (#15832)

Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
This commit is contained in:
Jian Qiu
2023-01-29 01:34:27 +08:00
committed by GitHub
co-authored by Qiu Jian
parent 71e93cd202
commit 53a78655e2
10 changed files with 408 additions and 612 deletions
+3
View File
@@ -208,6 +208,9 @@ type CloudaccountCreateInput struct {
ReadOnly bool `json:"read_only"`
SProjectMappingResourceInput
// 是否立即开始同步资源
StartSync *bool `json:"start_sync"`
}
type SProjectMappingResourceInput struct {
+2
View File
@@ -32,6 +32,8 @@ const (
CLOUD_PROVIDER_DELETE_FAILED = "delete_failed"
CLOUD_PROVIDER_SYNC_NETWORK = "sync_network"
CLOUD_PROVIDER_SYNC_NETWORK_FAILED = "sync_net_failed"
CLOUD_PROVIDER_SYNC_STATUS_QUEUING = "queuing"
CLOUD_PROVIDER_SYNC_STATUS_QUEUED = "queued"
CLOUD_PROVIDER_SYNC_STATUS_SYNCING = "syncing"
+329
View File
@@ -0,0 +1,329 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package models
import (
"context"
"fmt"
"sort"
"yunion.io/x/cloudmux/pkg/multicloud/esxi"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/util/netutils"
"yunion.io/x/pkg/util/rbacscope"
"yunion.io/x/pkg/utils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/compute/options"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
)
type CASimpleNetConf struct {
IpStart string `json:"guest_ip_start"`
IpEnd string `json:"guest_ip_end"`
IpMask int8 `json:"guest_ip_mask"`
Gateway string `json:"guest_gateway"`
VlanID int32 `json:"vlan_id"`
}
type CANetConf struct {
CASimpleNetConf
Name string `json:"name"`
Description string `json:"description"`
}
type CAPWire struct {
VsId string
WireId string
Name string
Distributed bool
Description string
Hosts []esxi.SSimpleHostDev
HostNetworks []CANetConf
// GuestNetworks []CANetConf
}
var ipMaskLen int8 = 24
func (account *SCloudaccount) PrepareEsxiHostNetwork(ctx context.Context, userCred mcclient.TokenCredential) error {
cProvider, err := account.GetProvider(ctx)
if err != nil {
return errors.Wrap(err, "account.GetProvider")
}
// fetch esxiclient
iregion, err := cProvider.GetOnPremiseIRegion()
if err != nil {
return errors.Wrap(err, "cProvider.GetOnPremiseIRegion")
}
esxiClient, ok := iregion.(*esxi.SESXiClient)
if !ok {
return errors.Wrap(httperrors.ErrNotSupported, "not a esxi provider")
}
// check network
nInfo, err := esxiClient.HostVmIPsPro(ctx)
if err != nil {
return errors.Wrap(err, "esxiClient.HostVmIPsPro")
}
log.Infof("HostVmIPsPro: %s", jsonutils.Marshal(nInfo).String())
capWires := make([]CAPWire, 0)
vsList := nInfo.VsMap.List()
log.Infof("vsList: %s", jsonutils.Marshal(vsList))
onPremiseNets, err := NetworkManager.fetchAllOnpremiseNetworks("", tristate.None)
if err != nil {
return errors.Wrap(err, "NetworkManager.fetchAllOnpremiseNetworks")
}
zoneId, err := guessEsxiZoneId(vsList, onPremiseNets)
if err != nil {
return errors.Wrap(err, "fail to find zone of esxi")
}
desc := fmt.Sprintf("Auto create for cloudaccount %q", account.Name)
for i := range vsList {
vs := vsList[i]
wireName := fmt.Sprintf("%s/%s", account.Name, vs.Name)
capWire, err := guessEsxiNetworks(vs, wireName, onPremiseNets)
if err != nil {
return errors.Wrap(err, "guessEsxiNetworks")
}
if len(capWire.WireId) == 0 {
capWire.Description = desc
}
capWires = append(capWires, *capWire)
}
log.Infof("capWires: %v", capWires)
host2Wire, err := account.createNetworks(ctx, zoneId, capWires)
if err != nil {
return errors.Wrap(err, "account.createNetworks")
}
err = account.SetHost2Wire(ctx, userCred, host2Wire)
if err != nil {
return errors.Wrap(err, "account.SetHost2Wire")
}
return nil
}
func guessEsxiZoneId(vsList []esxi.SVirtualSwitchSpec, onPremiseNets []SNetwork) (string, error) {
zoneIds, err := ZoneManager.getOnpremiseZoneIds()
if err != nil {
return "", errors.Wrap(err, "getOnpremiseZoneIds")
}
if len(zoneIds) == 1 {
return zoneIds[0], nil
} else if len(zoneIds) == 0 {
return "", errors.Wrap(httperrors.ErrNotFound, "no valid on-premise zone")
}
// there are multiple zones
zoneIds = make([]string, 0)
for i := range vsList {
vs := vsList[i]
for _, ips := range vs.HostIps {
for _, ip := range ips {
for _, net := range onPremiseNets {
if net.IsAddressInRange(ip) || net.IsAddressInNet(ip) {
zone, _ := net.GetZone()
if zone != nil && !utils.IsInStringArray(zone.Id, zoneIds) {
zoneIds = append(zoneIds, zone.Id)
}
}
}
}
}
}
if len(zoneIds) == 0 {
// no any clue
return "", errors.Wrap(httperrors.ErrNotFound, "no valid on-premise networks")
}
if len(zoneIds) > 1 {
// network span multiple zones
return "", errors.Wrap(httperrors.ErrConflict, "spans multiple zones?")
}
return zoneIds[0], nil
}
type sIpv4List []netutils.IPV4Addr
func (a sIpv4List) Len() int { return len(a) }
func (a sIpv4List) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a sIpv4List) Less(i, j int) bool { return uint32(a[i]) < uint32(a[j]) }
func guessEsxiNetworks(vs esxi.SVirtualSwitchSpec, wireName string, onPremiseNets []SNetwork) (*CAPWire, error) {
capWire := CAPWire{
Name: wireName,
VsId: vs.Id,
Distributed: vs.Distributed,
Hosts: vs.Hosts,
}
ipList := make([]netutils.IPV4Addr, 0)
for _, ips := range vs.HostIps {
for j := 0; j < len(ips); j++ {
var net *SNetwork
for _, n := range onPremiseNets {
if n.IsAddressInRange(ips[j]) {
// already covered by existing network
net = &n
break
}
}
if net != nil {
// found a network contains this IP, no need to create
if len(capWire.WireId) > 0 && capWire.WireId != net.WireId {
return nil, errors.Wrapf(httperrors.ErrConflict, "%s seems attaching conflict wires %s and %s", wireName, capWire.WireId, net.WireId)
}
capWire.WireId = net.WireId
continue
}
ipList = append(ipList, ips[j])
}
}
if len(ipList) == 0 {
return &capWire, nil
}
sort.Sort(sIpv4List(ipList))
for i := 0; i < len(ipList); {
simNetConfs := CASimpleNetConf{}
var net *SNetwork
for _, n := range onPremiseNets {
if n.IsAddressInNet(ipList[i]) {
// already covered by existing network
net = &n
break
}
}
if net != nil {
if len(capWire.WireId) > 0 && capWire.WireId != net.WireId {
return nil, errors.Wrapf(httperrors.ErrConflict, "%s seems attaching conflict wires %s and %s", wireName, capWire.WireId, net.WireId)
}
capWire.WireId = net.WireId
simNetConfs.Gateway = net.GuestGateway
simNetConfs.IpMask = net.GuestIpMask
} else {
simNetConfs.Gateway = (ipList[i].NetAddr(ipMaskLen) + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi)).String()
simNetConfs.IpMask = ipMaskLen
}
netRange := netutils.NewIPV4AddrRange(ipList[i].NetAddr(simNetConfs.IpMask), ipList[i].BroadcastAddr(simNetConfs.IpMask))
j := i
for j < len(ipList)-1 {
if ipList[j]+1 == ipList[j+1] && netRange.Contains(ipList[j+1]) {
j++
} else {
break
}
}
simNetConfs.IpStart = ipList[i].String()
simNetConfs.IpEnd = ipList[j].String()
i = j + 1
capWire.HostNetworks = append(capWire.HostNetworks, CANetConf{
Name: fmt.Sprintf("%s-host-network-%d", wireName, len(capWire.HostNetworks)+1),
Description: fmt.Sprintf("Auto create for cloudaccount %q", wireName),
CASimpleNetConf: simNetConfs,
})
}
return &capWire, nil
}
func (account *SCloudaccount) createNetworks(ctx context.Context, zoneId string, capWires []CAPWire) (map[string][]SVs2Wire, error) {
var err error
ret := make(map[string][]SVs2Wire)
for i := range capWires {
// if len(capWires[i].GuestNetworks)+len(capWires[i].HostNetworks) == 0 {
if len(capWires[i].HostNetworks) == 0 {
continue
}
var wireId = capWires[i].WireId
if len(wireId) == 0 {
wireId, err = account.createWire(ctx, api.DEFAULT_VPC_ID, zoneId, capWires[i].Name, capWires[i].Description)
if err != nil {
return nil, errors.Wrapf(err, "can't create wire %s", capWires[i].Name)
}
capWires[i].WireId = wireId
}
for _, net := range capWires[i].HostNetworks {
err := account.createNetwork(ctx, wireId, api.NETWORK_TYPE_BAREMETAL, net)
if err != nil {
return nil, errors.Wrapf(err, "can't create network %v", net)
}
}
for _, host := range capWires[i].Hosts {
ret[host.Id] = append(ret[host.Id], SVs2Wire{
VsId: capWires[i].VsId,
WireId: capWires[i].WireId,
Distributed: capWires[i].Distributed,
Mac: host.Mac,
})
}
}
return ret, nil
}
// NETWORK_TYPE_GUEST = "guest"
// NETWORK_TYPE_BAREMETAL = "baremetal"
func (account *SCloudaccount) createNetwork(ctx context.Context, wireId, networkType string, net CANetConf) error {
network := &SNetwork{}
network.Name = net.Name
if hint, err := NetworkManager.NewIfnameHint(net.Name); err != nil {
log.Errorf("can't NewIfnameHint form hint %s", net.Name)
} else {
network.IfnameHint = hint
}
network.GuestIpStart = net.IpStart
network.GuestIpEnd = net.IpEnd
network.GuestIpMask = net.IpMask
network.GuestGateway = net.Gateway
network.VlanId = int(net.VlanID)
network.WireId = wireId
network.ServerType = networkType
network.IsPublic = true
network.Status = api.NETWORK_STATUS_AVAILABLE
network.PublicScope = string(rbacscope.ScopeDomain)
network.ProjectId = account.ProjectId
network.DomainId = account.DomainId
network.Description = net.Description
network.SetModelManager(NetworkManager, network)
// TODO: Prevent IP conflict
log.Infof("create network %s succussfully", network.Id)
err := NetworkManager.TableSpec().Insert(ctx, network)
return err
}
func (account *SCloudaccount) createWire(ctx context.Context, vpcId, zoneId, wireName, desc string) (string, error) {
wire := &SWire{
Bandwidth: 10000,
Mtu: 1500,
}
wire.VpcId = vpcId
wire.ZoneId = zoneId
wire.IsEmulated = false
wire.Name = wireName
wire.DomainId = account.GetOwnerId().GetDomainId()
wire.Description = desc
wire.Status = api.WIRE_STATUS_AVAILABLE
wire.SetModelManager(WireManager, wire)
err := WireManager.TableSpec().Insert(ctx, wire)
if err != nil {
return "", err
}
log.Infof("create wire %s succussfully", wire.GetId())
return wire.GetId(), nil
}
+12 -19
View File
@@ -585,20 +585,13 @@ func (self *SCloudaccount) PostCreate(ctx context.Context, userCred mcclient.Tok
self.SEnabledStatusInfrasResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
self.savePassword(self.Secret)
if self.Enabled.IsTrue() {
if self.Provider == api.CLOUD_PROVIDER_VMWARE {
zone, _ := data.GetString("zone")
self.StartSyncVMwareNetworkTask(ctx, userCred, "", zone)
} else {
self.StartSyncCloudProviderInfoTask(ctx, userCred, nil, "")
}
if self.Enabled.IsTrue() && jsonutils.QueryBoolean(data, "start_sync", true) {
self.StartSyncCloudAccountInfoTask(ctx, userCred, nil, "")
} else {
self.SubmitSyncAccountTask(ctx, userCred, nil)
}
}
func (ca *SCloudaccount) PerformSyncVMwareNetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudaccountSyncVMwareNetworkInput) (jsonutils.JSONObject, error) {
return nil, ca.StartSyncVMwareNetworkTask(ctx, userCred, "", input.Zone)
}
func (self *SCloudaccount) savePassword(secret string) error {
sec, err := utils.EncryptAESBase64(self.Id, secret)
if err != nil {
@@ -630,7 +623,7 @@ func (self *SCloudaccount) PerformSync(ctx context.Context, userCred mcclient.To
syncRange.DeepSync = true
}
if self.CanSync() || syncRange.Force {
return nil, self.StartSyncCloudProviderInfoTask(ctx, userCred, &syncRange, "")
return nil, self.StartSyncCloudAccountInfoTask(ctx, userCred, &syncRange, "")
}
return nil, httperrors.NewInvalidStatusError("Unable to synchronize frequently")
}
@@ -793,13 +786,13 @@ func (self *SCloudaccount) PerformUpdateCredential(ctx context.Context, userCred
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_UPDATE_CREDENTIAL, account.Account, userCred, true)
self.SetStatus(userCred, api.CLOUD_PROVIDER_INIT, "Change credential")
self.StartSyncCloudProviderInfoTask(ctx, userCred, nil, "")
self.StartSyncCloudAccountInfoTask(ctx, userCred, nil, "")
}
return nil, nil
}
func (self *SCloudaccount) StartSyncCloudProviderInfoTask(ctx context.Context, userCred mcclient.TokenCredential, syncRange *SSyncRange, parentTaskId string) error {
func (self *SCloudaccount) StartSyncCloudAccountInfoTask(ctx context.Context, userCred mcclient.TokenCredential, syncRange *SSyncRange, parentTaskId string) error {
params := jsonutils.NewDict()
if syncRange != nil {
params.Add(jsonutils.Marshal(syncRange), "sync_range")
@@ -863,10 +856,10 @@ func (self *SCloudaccount) MarkEndSyncWithLock(ctx context.Context, userCred mcc
return errors.Error("some cloud providers not idle")
}
return self.markEndSync(userCred)
return self.MarkEndSync(userCred)
}
func (self *SCloudaccount) markEndSync(userCred mcclient.TokenCredential) error {
func (self *SCloudaccount) MarkEndSync(userCred mcclient.TokenCredential) error {
_, err := db.Update(self, func() error {
self.SyncStatus = api.CLOUD_PROVIDER_SYNC_STATUS_IDLE
self.LastSyncEndAt = timeutils.UtcNow()
@@ -2228,7 +2221,7 @@ func (account *SCloudaccount) PerformPublic(ctx context.Context, userCred mcclie
FullSync: true,
},
}
account.StartSyncCloudProviderInfoTask(ctx, userCred, syncRange, "")
account.StartSyncCloudAccountInfoTask(ctx, userCred, syncRange, "")
return nil, nil
}
@@ -2262,7 +2255,7 @@ func (account *SCloudaccount) PerformPrivate(ctx context.Context, userCred mccli
FullSync: true,
},
}
account.StartSyncCloudProviderInfoTask(ctx, userCred, syncRange, "")
account.StartSyncCloudAccountInfoTask(ctx, userCred, syncRange, "")
return nil, nil
}
@@ -2711,7 +2704,7 @@ func (self *SCloudaccount) PerformCreateSubscription(ctx context.Context, userCr
}
syncRange := SSyncRange{}
return nil, self.StartSyncCloudProviderInfoTask(ctx, userCred, &syncRange, "")
return nil, self.StartSyncCloudAccountInfoTask(ctx, userCred, &syncRange, "")
}
func (self *SCloudaccount) GetDnsZoneCaches() ([]SDnsZoneCache, error) {
@@ -31,7 +31,6 @@ import (
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/proxy"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/compute/options"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
@@ -672,20 +671,3 @@ func (manager *SCloudaccountManager) suggestHostNetworks(ips []netutils.IPV4Addr
})
return ret
}
func (ca *SCloudaccount) StartSyncVMwareNetworkTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, networkZone string) error {
if ca.Provider != api.CLOUD_PROVIDER_VMWARE {
return errors.ErrNotSupported
}
params := jsonutils.NewDict()
if len(networkZone) != 0 {
params.Set("zone", jsonutils.NewString(networkZone))
}
task, err := taskman.TaskManager.NewTask(ctx, "CloudAccountSyncVMwareNetworkTask", ca, userCred, params, parentTaskId, "", nil)
if err != nil {
return err
}
ca.SetStatus(userCred, api.CLOUD_PROVIDER_SYNC_NETWORK, "StartSyncVMwareNetworkTask")
task.ScheduleRun(nil)
return nil
}
+33 -19
View File
@@ -184,7 +184,7 @@ func (self *SNetwork) ValidateDeleteCondition(ctx context.Context, data *api.Net
data.SNetworkNics = cnt
}
}
if data.Total > 0 {
if data.Total-data.ReserveVnics-data.NetworkinterfaceVnics > 0 {
return httperrors.NewNotEmptyError("not an empty network %s", jsonutils.Marshal(data.SNetworkNics).String())
}
@@ -273,12 +273,17 @@ func (self *SNetwork) GetIPRange() netutils.IPV4AddrRange {
return self.getIPRange()
}
func (self *SNetwork) getIPRange() netutils.IPV4AddrRange {
start, _ := netutils.NewIPV4Addr(self.GuestIpStart)
end, _ := netutils.NewIPV4Addr(self.GuestIpEnd)
func (net *SNetwork) getIPRange() netutils.IPV4AddrRange {
start, _ := netutils.NewIPV4Addr(net.GuestIpStart)
end, _ := netutils.NewIPV4Addr(net.GuestIpEnd)
return netutils.NewIPV4AddrRange(start, end)
}
func (net *SNetwork) getNetRange() netutils.IPV4AddrRange {
start, _ := netutils.NewIPV4Addr(net.GuestIpStart)
return netutils.NewIPV4AddrRange(start.NetAddr(net.GuestIpMask), start.BroadcastAddr(net.GuestIpMask))
}
func isIpUsed(ipstr string, addrTable map[string]bool, recentUsedAddrTable map[string]bool) bool {
_, ok := addrTable[ipstr]
if !ok {
@@ -752,8 +757,12 @@ func (manager *SNetworkManager) newFromCloudNetwork(ctx context.Context, userCre
return &net, nil
}
func (self *SNetwork) IsAddressInRange(address netutils.IPV4Addr) bool {
return self.getIPRange().Contains(address)
func (net *SNetwork) IsAddressInRange(address netutils.IPV4Addr) bool {
return net.getIPRange().Contains(address)
}
func (net *SNetwork) IsAddressInNet(address netutils.IPV4Addr) bool {
return net.getNetRange().Contains(address)
}
func (self *SNetwork) isAddressUsed(address string) (bool, error) {
@@ -770,17 +779,10 @@ func (self *SNetwork) isAddressUsed(address string) (bool, error) {
}
}
func (manager *SNetworkManager) GetOnPremiseNetworkOfIP(ipAddr string, serverType string, isPublic tristate.TriState) (*SNetwork, error) {
address, err := netutils.NewIPV4Addr(ipAddr)
if err != nil {
return nil, err
}
func (manager *SNetworkManager) fetchAllOnpremiseNetworks(serverType string, isPublic tristate.TriState) ([]SNetwork, error) {
q := manager.Query()
wires := WireManager.Query().SubQuery()
// vpcs := VpcManager.Query().SubQuery()
q = q.Join(wires, sqlchemy.Equals(q.Field("wire_id"), wires.Field("id")))
// q = q.Join(vpcs, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id")))
// q = q.Filter(sqlchemy.IsNullOrEmpty(vpcs.Field("manager_id")))
q = q.Filter(sqlchemy.Equals(wires.Field("vpc_id"), api.DEFAULT_VPC_ID))
if len(serverType) > 0 {
q = q.Filter(sqlchemy.Equals(q.Field("server_type"), serverType))
@@ -790,11 +792,22 @@ func (manager *SNetworkManager) GetOnPremiseNetworkOfIP(ipAddr string, serverTyp
} else if isPublic.IsFalse() {
q = q.Filter(sqlchemy.IsFalse(q.Field("is_public")))
}
nets := make([]SNetwork, 0)
err = db.FetchModelObjects(manager, q, &nets)
err := db.FetchModelObjects(manager, q, &nets)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "FetchModelObjects")
}
return nets, nil
}
func (manager *SNetworkManager) GetOnPremiseNetworkOfIP(ipAddr string, serverType string, isPublic tristate.TriState) (*SNetwork, error) {
address, err := netutils.NewIPV4Addr(ipAddr)
if err != nil {
return nil, errors.Wrap(err, "NewIPV4Addr")
}
nets, err := manager.fetchAllOnpremiseNetworks(serverType, isPublic)
if err != nil {
return nil, errors.Wrap(err, "fetchAllOnpremiseNetworks")
}
for _, n := range nets {
if n.IsAddressInRange(address) {
@@ -2475,9 +2488,10 @@ func (self *SNetwork) CheckInvalidToMerge(ctx context.Context, net *SNetwork, al
if self.VlanId != net.VlanId {
failReason = append(failReason, "vlan_id")
}
if self.ServerType != net.ServerType {
// Qiujian: allow merge networks of different server_type
/*if self.ServerType != net.ServerType {
failReason = append(failReason, "server_type")
}
}*/
if len(failReason) > 0 {
err := httperrors.NewInputParameterError("Invalid Target Network %s: inconsist %s", net.GetId(), strings.Join(failReason, ","))
+4
View File
@@ -939,3 +939,7 @@ func (self *SZone) ClearSchedDescCache() error {
}
return nil
}
func (manager *SZoneManager) getOnpremiseZoneIds() ([]string, error) {
return zoneRegionFilter([]string{api.DEFAULT_REGION_ID})
}
@@ -21,6 +21,7 @@ import (
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/compute/models"
@@ -39,6 +40,24 @@ func init() {
func (self *CloudAccountSyncInfoTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
cloudaccount := obj.(*models.SCloudaccount)
if cloudaccount.Provider == api.CLOUD_PROVIDER_VMWARE {
cloudaccount.SetStatus(self.UserCred, api.CLOUD_PROVIDER_SYNC_NETWORK, "StartSyncVMwareNetworkTask")
err := cloudaccount.PrepareEsxiHostNetwork(ctx, self.UserCred)
if err != nil {
d := jsonutils.NewDict()
d.Set("error", jsonutils.NewString(err.Error()))
db.OpsLog.LogEvent(cloudaccount, db.ACT_SYNC_NETWORK_FAILED, d, self.UserCred)
cloudaccount.SetStatus(self.UserCred, api.CLOUD_PROVIDER_SYNC_NETWORK_FAILED, "sync network failed")
logclient.AddActionLogWithStartable(self, cloudaccount, logclient.ACT_CLOUDACCOUNT_SYNC_NETWORK, d, self.UserCred, false)
cloudaccount.MarkEndSync(self.UserCred)
self.SetStageFailed(ctx, d)
return
} else {
cloudaccount.SetStatus(self.UserCred, api.CLOUD_PROVIDER_INIT, "sync network sucess")
logclient.AddActionLogWithStartable(self, cloudaccount, logclient.ACT_CLOUDACCOUNT_SYNC_NETWORK, cloudaccount.GetShortDesc(ctx), self.UserCred, true)
}
}
db.OpsLog.LogEvent(cloudaccount, db.ACT_SYNCING_HOST, "", self.UserCred)
self.SetStage("OnCloudaccountSyncReady", nil)
@@ -1,556 +0,0 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tasks
import (
"context"
"fmt"
"sort"
"yunion.io/x/cloudmux/pkg/multicloud/esxi"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/netutils"
"yunion.io/x/pkg/util/rbacscope"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/compute/options"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type CloudAccountSyncVMwareNetworkTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(CloudAccountSyncVMwareNetworkTask{})
}
func (self *CloudAccountSyncVMwareNetworkTask) taskFailed(ctx context.Context, cloudaccount *models.SCloudaccount, desc string, err error) {
log.Errorf("err: %v", err)
d := jsonutils.NewDict()
d.Set("description", jsonutils.NewString(desc))
if err != nil {
d.Set("error", jsonutils.NewString(err.Error()))
}
db.OpsLog.LogEvent(cloudaccount, db.ACT_SYNC_NETWORK_FAILED, d, self.UserCred)
logclient.AddActionLogWithStartable(self, cloudaccount, logclient.ACT_CLOUDACCOUNT_SYNC_NETWORK, d, self.UserCred, false)
self.SetStageFailed(ctx, nil)
}
func (self *CloudAccountSyncVMwareNetworkTask) taskSuccess(ctx context.Context, cloudaccount *models.SCloudaccount, desc string) {
d := jsonutils.NewString(desc)
db.OpsLog.LogEvent(cloudaccount, db.ACT_SYNC_NETWORK, d, self.UserCred)
logclient.AddActionLogWithStartable(self, cloudaccount, logclient.ACT_CLOUDACCOUNT_SYNC_NETWORK, d, self.UserCred, true)
self.SetStageComplete(ctx, nil)
}
func (self *CloudAccountSyncVMwareNetworkTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
cloudaccount := obj.(*models.SCloudaccount)
// make sure zone
zoneId, err := self.zoneId()
if err != nil {
self.taskFailed(ctx, cloudaccount, "unable to make sure zoneid", err)
return
}
self.Params.Set("zoneId", jsonutils.NewString(zoneId))
cProvider, err := cloudaccount.GetProvider(ctx)
if err != nil {
self.taskFailed(ctx, cloudaccount, "unable to GetProvider", err)
return
}
// fetch esxiclient
iregion, err := cProvider.GetOnPremiseIRegion()
if err != nil {
self.taskFailed(ctx, cloudaccount, "unable to GetOnPremiseIRegion", err)
return
}
esxiClient := iregion.(*esxi.SESXiClient)
// check network
nInfo, err := esxiClient.HostVmIPsPro(ctx)
if err != nil {
self.taskFailed(ctx, cloudaccount, "unable to fetch HostVmIPs", err)
return
}
log.Infof("nInfo: %s", jsonutils.Marshal(nInfo).String())
// make sure wire and network existed
wires, err := self.fetchWires(cloudaccount, zoneId)
if err != nil {
self.taskFailed(ctx, cloudaccount, "unable to fetch wires", err)
return
}
existedIPPool, err := self.ipPool(cloudaccount, wires)
if err != nil {
self.taskFailed(ctx, cloudaccount, "uanble to structure existedIPPool", err)
return
}
excludedIPPool, err := self.ipPool(nil, nil)
if err != nil {
self.taskFailed(ctx, cloudaccount, "unable to structure excludedIPPool", err)
return
}
vss := nInfo.VsMap.List()
for i := range vss {
for hostName, ips := range vss[i].HostIps {
for _, ip := range ips {
if _, ok := existedIPPool.Get(ip); ok {
continue
}
net, ok := excludedIPPool.Get(ip)
if !ok {
continue
}
desc := fmt.Sprintf("networks %s shouldn't have ip %s belong to host %s, because the wire %s where in this network don't belong to zone %s or can't be used by cloudaccount %s owner", net.Id, ip, hostName, net.WireId, zoneId, cloudaccount.Id)
self.taskFailed(ctx, cloudaccount, desc, nil)
return
}
}
}
capWires := make([]CAPWire, 0)
vsList := nInfo.VsMap.List()
log.Infof("vsList: %s", jsonutils.Marshal(vsList))
desc := fmt.Sprintf("Auto create for cloudaccount %q", cloudaccount.GetName())
for i := range vsList {
vs := vsList[i]
wireName := fmt.Sprintf("%s/%s", cloudaccount.Name, vs.Name)
capWire := CAPWire{
Name: wireName,
VsId: vs.Id,
Distributed: vs.Distributed,
Hosts: vs.Hosts,
}
wireScore := make(map[string]int)
for _, ips := range vs.HostIps {
for j := 0; j < len(ips); j++ {
baseNet := ips[j].NetAddr(ipMaskLen)
ipWithSameNet := []netutils.IPV4Addr{ips[j]}
for k := j + 1; k < len(ips); k++ {
net := ips[k].NetAddr(24)
if net != baseNet {
break
}
ipWithSameNet = append(ipWithSameNet, ips[k])
}
ipLimitLow, ipLimitUp := ipWithSameNet[0], ipWithSameNet[len(ips)-1]
simNetConfs := self.expandIPRnage(ipWithSameNet, ipLimitLow, ipLimitUp,
func(proc esxi.SIPProc) bool {
return proc.IsHost
},
nInfo.IPPool, excludedIPPool,
func(net sSimpleNet) {
wireScore[net.WireId] += 2
},
)
for k := range simNetConfs {
capWire.HostNetworks = append(capWire.HostNetworks, CANetConf{
Name: fmt.Sprintf("%s-host-network-%d", wireName, len(capWire.HostNetworks)+1),
Description: desc,
CASimpleNetConf: simNetConfs[k],
})
}
}
}
/* for vlan, ips := range vs.Vlans {
simNetConfs := self.expandIPRnage(ips, 0, 0, func(proc esxi.SIPProc) bool {
return proc.VlanId == vlan && proc.VSId == vs.Id && !proc.IsHost
}, nInfo.IPPool, excludedIPPool, func(net sSimpleNet) {
wireScore[net.WireId] += 1
})
for j := range simNetConfs {
simNetConfs[j].VlanID = vlan
capWire.GuestNetworks = append(capWire.GuestNetworks, CANetConf{
Name: fmt.Sprintf("%s-guest-network-%d", wireName, len(capWire.GuestNetworks)+1),
Description: desc,
CASimpleNetConf: simNetConfs[j],
})
}
}*/
// make sure wire
suitableWire := ""
maxScore := 1
for wireId, score := range wireScore {
if score > maxScore {
suitableWire = wireId
maxScore = score
}
}
capWire.WireId = suitableWire
if len(capWire.WireId) == 0 {
capWire.Description = desc
}
capWires = append(capWires, capWire)
}
log.Infof("capWires: %v", capWires)
host2Wire, err := self.createNetworks(ctx, cloudaccount, zoneId, capWires)
if err != nil {
self.taskFailed(ctx, cloudaccount, "uable to creat networks", err)
return
}
err = cloudaccount.SetHost2Wire(ctx, self.UserCred, host2Wire)
if err != nil {
self.taskFailed(ctx, cloudaccount, "unable to store host2wire map", err)
return
}
self.SetStage("OnSyncCloudProviderInfoComplete", nil)
err = cloudaccount.StartSyncCloudProviderInfoTask(ctx, self.UserCred, nil, self.GetTaskId())
if err != nil {
self.taskFailed(ctx, cloudaccount, "unable to StartSyncCloudProviderInfoTask", err)
}
}
func (self *CloudAccountSyncVMwareNetworkTask) OnSyncCloudProviderInfoComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
cloudaccount := obj.(*models.SCloudaccount)
self.taskSuccess(ctx, cloudaccount, "")
}
func (self *CloudAccountSyncVMwareNetworkTask) OnSyncCloudProviderInfoCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) {
cloudaccount := obj.(*models.SCloudaccount)
db.OpsLog.LogEvent(cloudaccount, db.ACT_SYNC_NETWORK_FAILED, err, self.UserCred)
logclient.AddActionLogWithStartable(self, cloudaccount, logclient.ACT_CLOUDACCOUNT_SYNC_NETWORK, err, self.UserCred, false)
self.SetStageFailed(ctx, nil)
}
func (self *CloudAccountSyncVMwareNetworkTask) createNetworks(ctx context.Context, cloudaccount *models.SCloudaccount, zoneId string, capWires []CAPWire) (map[string][]models.SVs2Wire, error) {
var err error
ret := make(map[string][]models.SVs2Wire)
for i := range capWires {
// if len(capWires[i].GuestNetworks)+len(capWires[i].HostNetworks) == 0 {
if len(capWires[i].HostNetworks) == 0 {
continue
}
var wireId = capWires[i].WireId
if len(wireId) == 0 {
wireId, err = self.createWire(ctx, cloudaccount, api.DEFAULT_VPC_ID, zoneId, capWires[i].Name, capWires[i].Description)
if err != nil {
return nil, errors.Wrapf(err, "can't create wire %s", capWires[i].Name)
}
capWires[i].WireId = wireId
}
for _, net := range capWires[i].HostNetworks {
err := self.createNetwork(ctx, cloudaccount, wireId, api.NETWORK_TYPE_BAREMETAL, net)
if err != nil {
return nil, errors.Wrapf(err, "can't create network %v", net)
}
}
/* for _, net := range capWires[i].GuestNetworks {
err := self.createNetwork(ctx, cloudaccount, wireId, api.NETWORK_TYPE_GUEST, net)
if err != nil {
return nil, errors.Wrapf(err, "can't create network %v", net)
}
}*/
for _, host := range capWires[i].Hosts {
ret[host.Id] = append(ret[host.Id], models.SVs2Wire{
VsId: capWires[i].VsId,
WireId: capWires[i].WireId,
Distributed: capWires[i].Distributed,
Mac: host.Mac,
})
}
}
return ret, nil
}
// NETWORK_TYPE_GUEST = "guest"
// NETWORK_TYPE_BAREMETAL = "baremetal"
func (self *CloudAccountSyncVMwareNetworkTask) createNetwork(ctx context.Context, cloudaccount *models.SCloudaccount, wireId, networkType string, net CANetConf) error {
network := &models.SNetwork{}
network.Name = net.Name
if hint, err := models.NetworkManager.NewIfnameHint(net.Name); err != nil {
log.Errorf("can't NewIfnameHint form hint %s", net.Name)
} else {
network.IfnameHint = hint
}
network.GuestIpStart = net.IpStart
network.GuestIpEnd = net.IpEnd
network.GuestIpMask = net.IpMask
network.GuestGateway = net.Gateway
network.VlanId = int(net.VlanID)
network.WireId = wireId
network.ServerType = networkType
network.IsPublic = true
network.Status = api.NETWORK_STATUS_AVAILABLE
network.PublicScope = string(rbacscope.ScopeDomain)
network.ProjectId = cloudaccount.ProjectId
network.DomainId = cloudaccount.DomainId
network.Description = net.Description
network.SetModelManager(models.NetworkManager, network)
// TODO: Prevent IP conflict
log.Infof("create network %s succussfully", network.Id)
err := models.NetworkManager.TableSpec().Insert(ctx, network)
return err
}
func (self *CloudAccountSyncVMwareNetworkTask) createWire(ctx context.Context, cloudaccount *models.SCloudaccount, vpcId, zoneId, wireName, desc string) (string, error) {
wire := &models.SWire{
Bandwidth: 10000,
Mtu: 1500,
}
wire.VpcId = vpcId
wire.ZoneId = zoneId
wire.IsEmulated = false
wire.Name = wireName
wire.DomainId = cloudaccount.GetOwnerId().GetDomainId()
wire.Description = desc
wire.Status = api.WIRE_STATUS_AVAILABLE
wire.SetModelManager(models.WireManager, wire)
err := models.WireManager.TableSpec().Insert(ctx, wire)
if err != nil {
return "", err
}
log.Infof("create wire %s succussfully", wire.GetId())
return wire.GetId(), nil
}
var ipMaskLen int8 = 24
func (self *CloudAccountSyncVMwareNetworkTask) ipPool(cloudaccount *models.SCloudaccount, wires map[string]*models.SWire) (*sIPPool, error) {
networks := make([]models.SNetwork, 0, len(wires))
if wires == nil {
obj, err := models.VpcManager.FetchById(api.DEFAULT_VPC_ID)
if err != nil {
return nil, errors.Wrap(err, "unable fetch defaut vpc")
}
networks, err = obj.(*models.SVpc).GetNetworks()
if err != nil {
return nil, errors.Wrap(err, "unable to get networks of vpc default")
}
} else {
for _, wire := range wires {
nets, err := wire.GetNetworks(cloudaccount.GetOwnerId(), rbacscope.ScopeDomain)
if err != nil {
return nil, errors.Wrapf(err, "unable to fetch networks of wire %s", wire.GetId())
}
networks = append(networks, nets...)
}
}
pool := newIPPool(len(networks))
for i := range networks {
startIp, _ := netutils.NewIPV4Addr(networks[i].GuestIpStart)
endIp, _ := netutils.NewIPV4Addr(networks[i].GuestIpEnd)
pool.Insert(startIp, sSimpleNet{
Diff: endIp - startIp,
Vlan: int32(networks[i].VlanId),
Id: networks[i].Id,
WireId: networks[i].WireId,
})
}
return pool, nil
}
func (self *CloudAccountSyncVMwareNetworkTask) zoneId() (string, error) {
if !self.Params.Contains("zone") {
zoneids, err := models.CloudaccountManager.FetchEsxiZoneIds()
if err != nil {
return "", errors.Wrap(err, "unable to fetch esxi zoneids")
}
return zoneids[0], nil
}
zone, _ := self.Params.GetString("zone")
obj, err := models.ZoneManager.FetchByIdOrName(self.UserCred, zone)
if err != nil {
return "", errors.Wrapf(err, "unable to fetch zone %q", zone)
}
return obj.GetId(), nil
}
func (self *CloudAccountSyncVMwareNetworkTask) fetchWires(cloudaccount *models.SCloudaccount, zoneId string) (map[string]*models.SWire, error) {
q := models.WireManager.Query().Equals("zone_id", zoneId)
q = models.WireManager.FilterByOwner(q, cloudaccount.GetOwnerId(), rbacscope.ScopeDomain)
wires := make([]models.SWire, 0, 1)
err := db.FetchModelObjects(models.WireManager, q, &wires)
if err != nil {
return nil, err
}
ret := make(map[string]*models.SWire, len(wires))
for i := range wires {
ret[wires[i].GetId()] = &wires[i]
}
return ret, nil
}
func (self *CloudAccountSyncVMwareNetworkTask) fetchEsxiZoneIds() ([]string, error) {
q := models.BaremetalagentManager.Query().Equals("agent_type", "esxiagent").Asc("created_at")
agents := make([]models.SBaremetalagent, 0, 1)
err := db.FetchModelObjects(models.BaremetalagentManager, q, &agents)
if err != nil {
return nil, err
}
ids := make([]string, 0, len(agents))
for i := range agents {
if agents[i].Status == api.BAREMETAL_AGENT_ENABLED {
ids = append(ids, agents[i].ZoneId)
}
}
for i := range agents {
if agents[i].Status != api.BAREMETAL_AGENT_ENABLED {
ids = append(ids, agents[i].ZoneId)
}
}
return ids, nil
}
func (self *CloudAccountSyncVMwareNetworkTask) expandIPRnage(ips []netutils.IPV4Addr, limitLow, limitUp netutils.IPV4Addr, expand func(esxi.SIPProc) bool, ipPool esxi.SIPPool, existedIpPool *sIPPool, existed func(sSimpleNet)) []CASimpleNetConf {
ret := make([]CASimpleNetConf, 0)
for i := 0; i < len(ips); i++ {
log.Infof("ipPool: %v", ipPool)
log.Infof("existedIpPool: %v", existedIpPool)
ip := ips[i]
if net, ok := existedIpPool.Get(ip); ok {
if existed != nil {
existed(net)
}
continue
}
net := ip.NetAddr(24)
netLimitLow := net + 1
netLimitUp := net + 254
if limitLow != 0 && limitLow > netLimitLow {
netLimitLow = limitLow
}
if limitUp != 0 && limitUp < netLimitUp {
netLimitUp = limitUp
}
log.Infof("ip: %s", ip.String())
// find startip
startIp := ip - 1
for ; startIp >= netLimitLow; startIp-- {
log.Infof("startIp: %s", startIp)
if _, ok := existedIpPool.Get(startIp); ok {
log.Infof("existedIpPool get startIp")
break
}
if _, ok := ipPool.Get(startIp); ok {
log.Infof("ipPool get startIp")
break
}
}
endIp := ip + 1
for ; endIp <= netLimitUp; endIp++ {
log.Infof("endIp: %s", endIp)
if _, ok := existedIpPool.Get(endIp); ok {
log.Infof("existedIpPool get endIp")
break
}
if proc, ok := ipPool.Get(endIp); ok {
log.Infof("existedIpPool get endIp")
if expand(proc) {
i++
continue
}
break
}
}
ret = append(ret, CASimpleNetConf{
IpStart: (startIp + 1).String(),
IpEnd: (endIp - 1).String(),
IpMask: ipMaskLen,
Gateway: (net + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi)).String(),
})
// Avoid assigning already assigned ip subnet
existedIpPool.Insert(startIp+1, sSimpleNet{
Diff: endIp - startIp - 2,
})
}
return ret
}
type sIPPool struct {
netranges []netutils.IPV4Addr
simpleNetMap map[netutils.IPV4Addr]sSimpleNet
}
func newIPPool(length ...int) *sIPPool {
initLen := 0
if len(length) > 0 {
initLen = length[0]
}
return &sIPPool{
netranges: make([]netutils.IPV4Addr, 0, initLen),
simpleNetMap: make(map[netutils.IPV4Addr]sSimpleNet, initLen),
}
}
type sSimpleNet struct {
Diff netutils.IPV4Addr
Id string
Vlan int32
WireId string
}
func (pl *sIPPool) Insert(startIp netutils.IPV4Addr, sNet sSimpleNet) {
// TODO:check
index := pl.getIndex(startIp)
pl.netranges = append(pl.netranges, 0)
pl.netranges = append(pl.netranges[:index+1], pl.netranges[index:len(pl.netranges)-1]...)
pl.netranges[index] = startIp
pl.simpleNetMap[startIp] = sNet
}
func (pl *sIPPool) getIndex(ip netutils.IPV4Addr) int {
index := sort.Search(len(pl.netranges), func(n int) bool {
return pl.netranges[n] >= ip
})
return index
}
func (pl *sIPPool) Get(ip netutils.IPV4Addr) (sSimpleNet, bool) {
index := pl.getIndex(ip)
if index > len(pl.netranges) || index < 0 {
return sSimpleNet{}, false
}
if index < len(pl.netranges) && pl.netranges[index] == ip {
return pl.simpleNetMap[ip], true
}
if index == 0 {
return sSimpleNet{}, false
}
startIp := pl.netranges[index-1]
simpleNet := pl.simpleNetMap[startIp]
if ip-startIp <= simpleNet.Diff {
return simpleNet, true
}
return sSimpleNet{}, false
}
type CAPWire struct {
VsId string
WireId string
Name string
Distributed bool
Description string
Hosts []esxi.SSimpleHostDev
HostNetworks []CANetConf
// GuestNetworks []CANetConf
}
type CASimpleNetConf struct {
IpStart string `json:"guest_ip_start"`
IpEnd string `json:"guest_ip_end"`
IpMask int8 `json:"guest_ip_mask"`
Gateway string `json:"guest_gateway"`
VlanID int32 `json:"vlan_id"`
}
type CANetConf struct {
CASimpleNetConf
Name string `json:"name"`
Description string `json:"description"`
}
+6
View File
@@ -1351,4 +1351,10 @@ func init() {
EN("WebSSH").
CN("WebSSH"),
)
o.Set(ACT_CLOUDACCOUNT_SYNC_NETWORK, i18n.NewTableEntry().
EN("Probe Network").
CN("探测网络配置"),
)
}