diff --git a/pkg/apis/compute/cloudaccount.go b/pkg/apis/compute/cloudaccount.go index 9a38364c8b..21a9955cc4 100644 --- a/pkg/apis/compute/cloudaccount.go +++ b/pkg/apis/compute/cloudaccount.go @@ -195,6 +195,9 @@ type CloudaccountCreateInput struct { // 是否启用SAML认证 // default: false SAMLAuth *bool `json:"saml_auth"` + + // VMware 账号有zone属性 + Zone string `json:"zone"` } type CloudaccountShareModeInput struct { @@ -335,12 +338,43 @@ type CloudaccountPerformPublicInput struct { type CloudaccountPerformPrepareNetsInput struct { CloudaccountCreateInput - // enum: vcenter,datacenter,cluster WireLevelForVmware string `json:"wire_level_for_vmware"` + Dvs bool `json:"dvs"` } type CloudaccountPerformPrepareNetsOutput struct { - CAWireNets []CAWireNet `json:"wire_networks"` + CAWireNets []CAWireNet `json:"wire_networks"` + Hosts []CAGuestNet `json:"hosts"` + Guests []CAGuestNet `json:"guests"` + Wires []CAPWire `json:"wires"` + VSwitchs []VSwitch `json:"vswitchs"` +} + +type CloudaccountSyncVMwareNetworkInput struct { + Zone string `help:"zone Id or Name" json:"zone"` +} + +type CAPWire struct { + Id string `json:"id"` + Name string `json:"name"` + Distributed bool `json:"distributed"` + Hosts []SimpleHost `json:"hosts"` + HostNetworks []CANetConf `json:"host_networks"` + GuestNetworks []CANetConf `json:"guest_networks"` +} + +type VSwitch struct { + Id string `json:"id"` + Name string `json:"name"` + Distributed bool `json:"distributed"` + Hosts []SimpleHost `json:"hosts"` + HostNetworks []CANetConf `json:"host_networks"` + GuestNetworks []CANetConf `json:"guest_networks"` +} + +type SimpleHost struct { + Id string + Name string } type CAWireNet struct { diff --git a/pkg/apis/compute/cloudaccount_const.go b/pkg/apis/compute/cloudaccount_const.go index e6c398a774..33cfcb6d20 100644 --- a/pkg/apis/compute/cloudaccount_const.go +++ b/pkg/apis/compute/cloudaccount_const.go @@ -28,6 +28,7 @@ const ( CLOUD_PROVIDER_DELETING = "deleting" CLOUD_PROVIDER_DELETED = "deleted" CLOUD_PROVIDER_DELETE_FAILED = "delete_failed" + CLOUD_PROVIDER_SYNC_NETWORK = "sync_network" CLOUD_PROVIDER_SYNC_STATUS_QUEUING = "queuing" CLOUD_PROVIDER_SYNC_STATUS_QUEUED = "queued" diff --git a/pkg/cloudcommon/db/opslog_const.go b/pkg/cloudcommon/db/opslog_const.go index d75098b679..deeabbfbde 100644 --- a/pkg/cloudcommon/db/opslog_const.go +++ b/pkg/cloudcommon/db/opslog_const.go @@ -180,10 +180,12 @@ const ( ACT_UNCONVERT_COMPLETE = "unconverted" ACT_UNCONVERT_FAIL = "unconvert_fail" - ACT_SYNC_HOST_START = "sync_host_start" - ACT_SYNCING_HOST = "syncing_host" - ACT_SYNC_HOST_COMPLETE = "sync_host_end" - ACT_SYNC_HOST_FAILED = "sync_host_fail" + ACT_SYNC_HOST_START = "sync_host_start" + ACT_SYNCING_HOST = "syncing_host" + ACT_SYNC_HOST_COMPLETE = "sync_host_end" + ACT_SYNC_HOST_FAILED = "sync_host_fail" + ACT_SYNC_NETWORK = "sync_network" + ACT_SYNC_NETWORK_FAILED = "sync_network_failed" ACT_SYNC_PROJECT_COMPLETE = "sync_project_end" @@ -279,4 +281,7 @@ const ( ACT_UPDATE_RULE = "update_config" ACT_UPDATE_TAGS = "update_tags" + + ACT_MERGE_NETWORK = "merge_network" + ACT_MERGE_NETWORK_FAILED = "merge_network_failed" ) diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index 8bb12307cc..410f20b885 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -19,7 +19,6 @@ import ( "database/sql" "fmt" "net/url" - "sort" "strconv" "strings" "sync" @@ -30,7 +29,6 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/compare" - "yunion.io/x/pkg/util/netutils" "yunion.io/x/pkg/util/timeutils" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -49,7 +47,6 @@ import ( "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" - "yunion.io/x/onecloud/pkg/multicloud/esxi" "yunion.io/x/onecloud/pkg/multicloud/esxi/vcenter" "yunion.io/x/onecloud/pkg/util/choices" "yunion.io/x/onecloud/pkg/util/httputils" @@ -164,7 +161,8 @@ type SCloudaccount struct { // 公有云子账号登录地址 IamLoginUrl string `width:"512" charset:"ascii" nullable:"false" list:"domain" update:"domain"` - SAMLAuth tristate.TriState `nullable:"false" get:"user" update:"domain" create:"optional" list:"user" default:"false"` + SAMLAuth tristate.TriState `nullable:"false" get:"user" update:"domain" create:"optional" list:"user" default:"false"` + vmwareHostWireCache map[string][]SVs2Wire } func (self *SCloudaccount) GetCloudproviders() []SCloudprovider { @@ -353,657 +351,6 @@ func (self *SCloudaccount) PostUpdate(ctx context.Context, userCred mcclient.Tok } } -func (scm *SCloudaccountManager) AllowPerformPrepareNets(_ context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject) bool { - return db.IsAdminAllowPerform(userCred, scm, "prepare-nets") -} - -type sNetworkInfo struct { - esxi.SNetworkInfo - prefix string - fakeVsId string -} - -func (scm *SCloudaccountManager) hostVMIPsPrepareNets(ctx context.Context, client *esxi.SESXiClient, - input api.CloudaccountPerformPrepareNetsInput) ([]sNetworkInfo, error) { - caName := input.Name - wireLevel := input.WireLevelForVmware - ret := make([]sNetworkInfo, 0) - if len(wireLevel) == 0 { - wireLevel = api.CLOUD_ACCOUNT_WIRE_LEVEL_VCENTER - } - switch wireLevel { - case api.CLOUD_ACCOUNT_WIRE_LEVEL_VCENTER: - nInfo, err := client.HostVmIPs(ctx) - if err != nil { - return nil, errors.Wrap(err, "unable to fetch ips of hosts and vms") - } - nInfo.IPPool.FillVsId(caName) - ret = append(ret, sNetworkInfo{ - SNetworkInfo: nInfo, - prefix: caName, - fakeVsId: 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 { - nInfo, 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()) - } - prefix := fmt.Sprintf("%s/%s", caName, dc.GetName()) - nInfo.IPPool.FillVsId(prefix) - ret = append(ret, sNetworkInfo{ - SNetworkInfo: nInfo, - prefix: prefix, - fakeVsId: prefix, - }) - } - 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 { - nInfo, 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()) - } - prefix := fmt.Sprintf("%s/%s/%s", caName, dc.GetName(), cluster.GetName()) - nInfo.IPPool.FillVsId(prefix) - ret = append(ret, sNetworkInfo{ - SNetworkInfo: nInfo, - prefix: prefix, - fakeVsId: prefix, - }) - } - } - default: - return nil, httperrors.NewInputParameterError("invalid wire_level_for_vmware %q, accept %s", wireLevel, api.CLOUD_ACCOUNT_WIRE_LEVELS) - } - 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 ( - err error - output api.CloudaccountPerformPrepareNetsOutput - ) - if input.Provider != api.CLOUD_PROVIDER_VMWARE { - return output, httperrors.NewNotSupportedError("not support for cloudaccount with provider '%s'", input.Provider) - } - // validate first - ownerId, err := scm.FetchOwnerId(ctx, jsonutils.Marshal(input)) - if err != nil { - return output, errors.Wrap(err, "FetchOwnerId in PerformPrepareNets") - } - if ownerId == nil { - ownerId = userCred - } - // validate domain - if len(input.ProjectDomainId) > 0 { - _, input.DomainizedResourceInput, err = db.ValidateDomainizedResourceInput(ctx, input.DomainizedResourceInput) - if err != nil { - return output, err - } - } - domainId := input.ProjectDomainId - if len(input.ProjectId) > 0 { - var tenent *db.STenant - tenent, input.ProjectizedResourceInput, err = db.ValidateProjectizedResourceInput(ctx, input.ProjectizedResourceInput) - if err != nil { - return output, err - } - if len(domainId) == 0 { - domainId = tenent.DomainId - } - } - if len(domainId) == 0 { - domainId = ownerId.GetProjectDomainId() - } - - // Determine the zoneids according to esxiagent. If there is no esxiagent, zone0 is used by default. And the wires are filtered according to the specified domain and zoneids - // make sure zone - zoneids, err := scm.fetchEsxiZoneIds() - if err != nil { - return output, errors.Wrap(err, "unable to fetchEsxiZoneIds") - } - if len(zoneids) == 0 { - id, err := scm.defaultZoneId(userCred) - if err != nil { - return output, errors.Wrap(err, "unable to fetch defaultZoneId") - } - zoneids = append(zoneids, id) - } - // fetch all wire candidate - wires, err := scm.fetchWires(userCred, zoneids, domainId) - if err != nil { - return output, errors.Wrap(err, "unable to fetch wires") - } - - // Find the appropriate wire from above wires according to the Host's IP. The logic for finding is that the networks in the wire can contain the Host as much as possible. If no suitable one is found, a new wire is used. - // fetch all Host - factory, err := cloudprovider.GetProviderFactory(input.Provider) - if err != nil { - return output, errors.Wrap(err, "cloudprovider.GetProviderFactory") - } - input.SCloudaccount, err = factory.ValidateCreateCloudaccountData(ctx, userCred, input.SCloudaccountCredential) - if err != nil { - return output, errors.Wrap(err, "providerDriver.ValidateCreateCloudaccountData") - } - var proxyFunc httputils.TransportProxyFunc - { - if input.ProxySettingId == "" { - input.ProxySettingId = proxyapi.ProxySettingId_DIRECT - } - var proxySetting *proxy.SProxySetting - proxySetting, input.ProxySettingResourceInput, err = proxy.ValidateProxySettingResourceInput(userCred, input.ProxySettingResourceInput) - if err != nil { - return output, errors.Wrap(err, "ValidateProxySettingResourceInput") - } - proxyFunc = proxySetting.HttpTransportProxyFunc() - } - provider, err := factory.GetProvider(cloudprovider.ProviderConfig{ - Vendor: input.Provider, - URL: input.AccessUrl, - Account: input.Account, - Secret: input.Secret, - ProxyFunc: proxyFunc, - }) - if err != nil { - return output, errors.Wrap(err, "factory.GetProvider") - } - iregion, err := provider.GetOnPremiseIRegion() - if err != nil { - return output, errors.Wrap(err, "provider.GetOnPremiseIRegion") - } - // hack - client := iregion.(*esxi.SESXiClient) - nInfos, err := scm.hostVMIPsPrepareNets(ctx, client, input) - if err != nil { - return output, err - } - // fetch networks - networks := make([][]SNetwork, len(wires)) - for i := range networks { - nets, err := wires[i].getNetworks(userCred, rbacutils.ScopeSystem) - if err != nil { - return output, errors.Wrap(err, "wire.getNetwork") - } - networks[i] = nets - } - return scm.parseAndSuggest(sParseAndSuggest{ - NInfos: nInfos, - AccountName: input.Name, - ZoneIds: zoneids, - Wires: wires, - Networks: networks, - }), nil -} - -type sParseAndSuggest struct { - NInfos []sNetworkInfo - AccountName string - ZoneIds []string - Wires []SWire - Networks [][]SNetwork -} - -func (scm *SCloudaccountManager) parseAndSuggest(params sParseAndSuggest) api.CloudaccountPerformPrepareNetsOutput { - var ( - output api.CloudaccountPerformPrepareNetsOutput - nInfos = params.NInfos - wires = params.Wires - networks = params.Networks - ) - // build global existedNetMap and IPPool - netNum := 0 - for i := range networks { - netNum += len(networks[i]) - } - existedNets := newIPPool(netNum) - for _, nets := range networks { - for i := range nets { - startIp, _ := netutils.NewIPV4Addr(nets[i].GuestIpStart) - endIp, _ := netutils.NewIPV4Addr(nets[i].GuestIpEnd) - existedNets.Insert(startIp, sSimpleNet{ - Diff: endIp - startIp, - Vlan: int32(nets[i].VlanId), - Id: nets[i].Id, - WireId: nets[i].WireId, - }) - - } - } - ipPoolLen := 0 - for i := range nInfos { - ipPoolLen += nInfos[i].IPPool.Len() - } - ipPool := esxi.NewIPPool(ipPoolLen) - for i := range nInfos { - ipPool.Merge(&nInfos[i].IPPool) - } - output.CAWireNets = make([]api.CAWireNet, 0, len(nInfos)) - for _, ni := range nInfos { - var ( - wireNet api.CAWireNet - hostIps = ni.HostIps - ) - - // key of ipHosts is host's ip - ipHosts := make(map[netutils.IPV4Addr]string, len(hostIps)) - for name, ip := range hostIps { - ipHosts[ip] = 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 { - wireNet.SuitableWire = suitableWire.GetId() - } else { - wireNet.SuggestedWire = api.CAWireConf{ - ZoneIds: params.ZoneIds, - Name: ni.prefix + "-wire", - Description: fmt.Sprintf("Auto created Wire for VMware account %q", params.AccountName), - } - } - - // 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", ni.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. - for i := range wireNet.HostSuggestedNetworks { - ipStart, _ := netutils.NewIPV4Addr(wireNet.HostSuggestedNetworks[i].GuestIpStart) - ipEnd, _ := netutils.NewIPV4Addr(wireNet.HostSuggestedNetworks[i].GuestIpEnd) - existedNets.Insert(ipStart, sSimpleNet{ - Diff: ipEnd - ipStart, - }) - } - - guests := make([]api.CAGuestNet, len(ni.VMs)) - for i := range guests { - guests[i].Name = ni.VMs[i].Name - for _, ipvlan := range ni.VMs[i].IPVlans { - var suitableNetId string - sn, ok := existedNets.Get(ipvlan.IP) - if ok { - suitableNetId = sn.Id - } - guests[i].IPNets = append(guests[i].IPNets, api.CAIPNet{ - IP: ipvlan.IP.String(), - VlanID: ipvlan.VlanId, - SuitableNetwork: suitableNetId, - }) - } - } - wireNet.Guests = guests - - for vlan, ips := range ni.VlanIps { - for i := 0; i < len(ips); i++ { - ip := ips[i] - if _, ok := existedNets.Get(ip); ok { - continue - } - net := ip.NetAddr(24) - netLimitLow := net + 1 - netLimitUp := net + 254 - // find startip - startIp := ip - 1 - for ; startIp >= netLimitLow; startIp-- { - if _, ok := existedNets.Get(startIp); ok { - break - } - if _, ok := ipPool.Get(startIp); ok { - break - } - } - endIp := ip + 1 - for ; endIp <= netLimitUp; endIp++ { - if _, ok := existedNets.Get(endIp); ok { - break - } - if proc, ok := ipPool.Get(endIp); ok { - if proc.VlanId == vlan && proc.VSId == ni.fakeVsId { - // find one in ips - i++ - continue - } - break - } - } - slen := len(wireNet.GuestSuggestedNetworks) - wireNet.GuestSuggestedNetworks = append(wireNet.GuestSuggestedNetworks, api.CANetConf{ - Name: fmt.Sprintf("%s-guest-network-%d", ni.prefix, slen+1), - Description: "", - CASimpleNetConf: api.CASimpleNetConf{ - VlanID: vlan, - GuestIpStart: (startIp + 1).String(), - GuestIpEnd: (endIp - 1).String(), - GuestIpMask: 24, - GuestGateway: (net + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi)). - String(), - }, - }) - // Avoid assigning already assigned ip subnet - existedNets.Insert(startIp+1, sSimpleNet{ - Diff: endIp - startIp - 2, - }) - } - } - output.CAWireNets = append(output.CAWireNets, wireNet) - } - return output -} - -func (manager *SCloudaccountManager) fetchWires(userCred mcclient.TokenCredential, zoneIds []string, domainId string) ([]SWire, error) { - q := WireManager.Query().Equals("domain_id", domainId).In("zone_id", zoneIds) - q = WireManager.FilterByOwner(q, userCred, rbacutils.ScopeDomain) - wires := make([]SWire, 0, 1) - err := db.FetchModelObjects(WireManager, q, &wires) - return wires, err -} - -func (manager *SCloudaccountManager) defaultZoneId(userCred mcclient.TokenCredential) (string, error) { - zone, err := ZoneManager.FetchByName(userCred, "zone0") - if err != nil { - return "", err - } - return zone.GetId(), nil -} - -func (manager *SCloudaccountManager) fetchEsxiZoneIds() ([]string, error) { - q := BaremetalagentManager.Query().Equals("agent_type", "esxiagent").Asc("created_at") - agents := make([]SBaremetalagent, 0, 1) - err := db.FetchModelObjects(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 -} - -// The suggestHostNetworks give the suggest config of network contains the ip in 'ips'. -// The suggested network mask is 24 and the gateway is x.x.x.1. -// The suggests network is the smallest network segment that meets the above conditions. -func (manager *SCloudaccountManager) suggestHostNetworks(ips []netutils.IPV4Addr) []api.CASimpleNetConf { - if len(ips) == 0 { - return nil - } - sort.Slice(ips, func(i, j int) bool { - return ips[i] < ips[j] - }) - var ( - mask int8 = 24 - lastnetAddr netutils.IPV4Addr - consequent []netutils.IPV4Addr - ret []api.CASimpleNetConf - ) - lastnetAddr = ips[0].NetAddr(mask) - consequent = []netutils.IPV4Addr{ips[0]} - for i := 1; i < len(ips); i++ { - ip := ips[i] - netAddr := ip.NetAddr(mask) - if netAddr == lastnetAddr && consequent[len(consequent)-1]+1 == ip { - consequent = append(consequent, ip) - continue - } - - if netAddr != lastnetAddr { - lastnetAddr = netAddr - } - - gatewayIP := consequent[0].NetAddr(mask) + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi) - ret = append(ret, api.CASimpleNetConf{ - GuestIpStart: consequent[0].String(), - GuestIpEnd: consequent[len(consequent)-1].String(), - GuestIpMask: mask, - GuestGateway: gatewayIP.String(), - }) - consequent = []netutils.IPV4Addr{ip} - } - gatewayIp := consequent[0].NetAddr(mask) + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi) - ret = append(ret, api.CASimpleNetConf{ - GuestIpStart: consequent[0].String(), - GuestIpEnd: consequent[len(consequent)-1].String(), - GuestIpMask: mask, - GuestGateway: gatewayIp.String(), - }) - 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 -} - -// The suggestVMNetworks give the suggest config of network that contain the IP in 'ips' and does not intersect with the network segment described in 'excludes'. -// The suggested network mask is 24 and the gateway is x.x.x.1. -// The suggests network is the largest network segment that meets the above conditions. -func (manager *SCloudaccountManager) suggestVMNetwors(ips []netutils.IPV4Addr, excludes []netutils.IPV4AddrRange) []api.CASimpleNetConf { - var mask int8 = 24 - netAddrMap := make(map[netutils.IPV4Addr][]netutils.IPV4AddrRange) - // - for _, ip := range ips { - netAddr := ip.NetAddr(mask) - if _, ok := netAddrMap[netAddr]; !ok { - netAddrMap[netAddr] = []netutils.IPV4AddrRange{} - } - } - for _, iprange := range excludes { - startNet := iprange.StartIp().NetAddr(mask) - endNet := iprange.EndIp().NetAddr(mask) - irs := make([]netutils.IPV4AddrRange, 0, 1) - for net := startNet; net <= endNet; net += 1 << (uint(32 - mask)) { - startip := net + 1 - endip := net + 255 - if net == startNet { - startip = iprange.StartIp() - } - if net == endNet { - endip = iprange.EndIp() - } - irs = append(irs, netutils.NewIPV4AddrRange(startip, endip)) - } - for _, ir := range irs { - net := ir.StartIp().NetAddr(mask) - if _, ok := netAddrMap[net]; ok { - netAddrMap[net] = append(netAddrMap[net], ir) - } - } - } - // sort - for _, irs := range netAddrMap { - sort.Slice(irs, func(i, j int) bool { - if irs[i].StartIp() == irs[j].StartIp() { - return irs[i].EndIp() < irs[j].EndIp() - } - return irs[i].StartIp() < irs[j].StartIp() - }) - } - // merge - for ip, irs := range netAddrMap { - newIrs := make([]netutils.IPV4AddrRange, 0, 1) - if len(irs) == 0 { - continue - } - tmp := &irs[0] - for i := 1; i < len(irs); i++ { - if newTmp, ok := tmp.Merge(irs[i]); ok { - tmp = newTmp - continue - } - newIrs = append(newIrs, *tmp) - tmp = &irs[i] - } - newIrs = append(newIrs, *tmp) - netAddrMap[ip] = newIrs - } - // reverse - for ip, irs := range netAddrMap { - if len(irs) == 0 { - netAddrMap[ip] = []netutils.IPV4AddrRange{netutils.NewIPV4AddrRange(ip+1, ip+254)} - } - newIrs := make([]netutils.IPV4AddrRange, 0, 1) - startIP := ip + 1 - for _, ir := range irs { - if startIP == ir.StartIp() { - startIP = ir.EndIp() + 1 - continue - } - newIrs = append(newIrs, netutils.NewIPV4AddrRange(startIP, ir.StartIp()-1)) - startIP = ir.EndIp() + 1 - } - if startIP != ip+255 { - newIrs = append(newIrs, netutils.NewIPV4AddrRange(startIP, ip+254)) - } - netAddrMap[ip] = newIrs - } - lastIrs := make(map[netutils.IPV4Addr]netutils.IPV4AddrRange) - for _, ip := range ips { - net := ip.NetAddr(mask) - for _, ir := range netAddrMap[net] { - if ir.Contains(ip) { - lastIrs[ir.StartIp()] = ir - break - } - } - } - ret := make([]api.CASimpleNetConf, 0, len(lastIrs)) - for _, ir := range lastIrs { - gateway := ir.StartIp().NetAddr(mask) + 1 - ret = append(ret, api.CASimpleNetConf{ - GuestIpStart: ir.StartIp().String(), - GuestIpEnd: ir.EndIp().String(), - GuestIpMask: mask, - GuestGateway: gateway.String(), - }) - } - return ret -} - func (manager *SCloudaccountManager) ValidateCreateData( ctx context.Context, userCred mcclient.TokenCredential, @@ -1079,6 +426,14 @@ func (manager *SCloudaccountManager) validateCreateData( input.AutoCreateProject = &createProject } + if len(input.Zone) > 0 { + obj, err := ZoneManager.FetchByIdOrName(userCred, input.Zone) + if err != nil { + return input, errors.Wrapf(err, "unable to fetch Zone %s", input.Zone) + } + input.Zone = obj.GetId() + } + endpoints := cloudprovider.SApsaraEndpoints{} if input.SCloudaccountCredential.SApsaraEndpoints != nil { if input.Options == nil { @@ -1216,10 +571,23 @@ func (self *SCloudaccount) PostCreate(ctx context.Context, userCred mcclient.Tok self.savePassword(self.Secret) if self.Enabled.IsTrue() { - self.StartSyncCloudProviderInfoTask(ctx, userCred, nil, "") + if self.Provider == api.CLOUD_PROVIDER_VMWARE { + zone, _ := data.GetString("zone") + self.StartSyncVmwareNetworkTask(ctx, userCred, "", zone) + } else { + self.StartSyncCloudProviderInfoTask(ctx, userCred, nil, "") + } } } +func (ca *SCloudaccount) AllowPerformSyncVMwareNetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return ca.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, ca, "sync-vmware-network") +} + +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 { @@ -1395,6 +763,19 @@ func (self *SCloudaccount) PerformUpdateCredential(ctx context.Context, userCred return nil, nil } +func (self *SCloudaccount) StartSyncVmwareNetworkTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, zone string) error { + params := jsonutils.NewDict() + if len(zone) != 0 { + params.Set("zone", jsonutils.NewString(zone)) + } + task, err := taskman.TaskManager.NewTask(ctx, "CloudAccountSyncVMwareNetworkTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "unable to create task CloudAccountSyncVMwareNetworkTask") + } + task.ScheduleRun(nil) + return nil +} + func (self *SCloudaccount) StartSyncCloudProviderInfoTask(ctx context.Context, userCred mcclient.TokenCredential, syncRange *SSyncRange, parentTaskId string) error { params := jsonutils.NewDict() if syncRange != nil { @@ -3406,3 +2787,36 @@ func (self *SCloudaccount) SyncAccountResources(ctx context.Context, userCred mc return nil } + +type SVs2Wire struct { + WireId string + VsId string + Distributed bool + Mac string + SyncTimes int +} + +var METADATA_EXT_HOST2WIRE_KEY = "ext:vmware:host2wire" + +func (cd *SCloudaccount) SetHost2Wire(ctx context.Context, userCred mcclient.TokenCredential, hw map[string][]SVs2Wire) error { + err := cd.SetMetadata(ctx, METADATA_EXT_HOST2WIRE_KEY, hw, userCred) + if err != nil { + return err + } + cd.vmwareHostWireCache = hw + return nil +} + +func (cd *SCloudaccount) GetHost2Wire(ctx context.Context, userCred mcclient.TokenCredential) (map[string][]SVs2Wire, error) { + if cd.vmwareHostWireCache != nil { + return cd.vmwareHostWireCache, nil + } + hwJson := cd.GetMetadataJson(METADATA_EXT_HOST2WIRE_KEY, userCred) + ret := make(map[string][]SVs2Wire) + err := hwJson.Unmarshal(&ret) + if err != nil { + return nil, errors.Wrapf(err, "unable unmarshal json %s to map[string][]SVs2Wire", hwJson.String()) + } + cd.vmwareHostWireCache = ret + return cd.vmwareHostWireCache, nil +} diff --git a/pkg/compute/models/cloudaccounts_prepare.go b/pkg/compute/models/cloudaccounts_prepare.go new file mode 100644 index 0000000000..a6454e1bf5 --- /dev/null +++ b/pkg/compute/models/cloudaccounts_prepare.go @@ -0,0 +1,688 @@ +// 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/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/netutils" + + proxyapi "yunion.io/x/onecloud/pkg/apis/cloudcommon/proxy" + 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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/multicloud/esxi" + "yunion.io/x/onecloud/pkg/util/httputils" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +func (scm *SCloudaccountManager) AllowPerformPrepareNets(_ context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, scm, "prepare-nets") +} + +type sNetworkInfo struct { + esxi.SNetworkInfo + prefix string +} + +func (scm *SCloudaccountManager) hostVMIPsPrepareNets(ctx context.Context, client *esxi.SESXiClient, + input api.CloudaccountPerformPrepareNetsInput) ([]sNetworkInfo, error) { + caName := input.Name + wireLevel := input.WireLevelForVmware + ret := make([]sNetworkInfo, 0) + if len(wireLevel) == 0 { + wireLevel = api.CLOUD_ACCOUNT_WIRE_LEVEL_VCENTER + } + switch wireLevel { + case api.CLOUD_ACCOUNT_WIRE_LEVEL_VCENTER: + nInfo, err := client.HostVmIPs(ctx) + if err != nil { + return nil, errors.Wrap(err, "unable to fetch ips of hosts and vms") + } + ret = append(ret, sNetworkInfo{ + SNetworkInfo: nInfo, + 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 { + nInfo, 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, sNetworkInfo{ + SNetworkInfo: nInfo, + 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 { + nInfo, 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, sNetworkInfo{ + SNetworkInfo: nInfo, + 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 ( + err error + output api.CloudaccountPerformPrepareNetsOutput + ) + if input.Provider != api.CLOUD_PROVIDER_VMWARE { + return output, httperrors.NewNotSupportedError("not support for cloudaccount with provider '%s'", input.Provider) + } + // validate first + ownerId, err := scm.FetchOwnerId(ctx, jsonutils.Marshal(input)) + if err != nil { + return output, errors.Wrap(err, "FetchOwnerId in PerformPrepareNets") + } + if ownerId == nil { + ownerId = userCred + } + // validate domain + if len(input.ProjectDomainId) > 0 { + _, input.DomainizedResourceInput, err = db.ValidateDomainizedResourceInput(ctx, input.DomainizedResourceInput) + if err != nil { + return output, err + } + } + + // Determine the zoneids according to esxiagent. If there is no esxiagent, zone0 is used by default. And the wires are filtered according to the specified domain and zoneids + // make sure zone + zoneids, err := scm.FetchEsxiZoneIds() + if err != nil { + return output, errors.Wrap(err, "unable to FetchEsxiZoneIds") + } + if len(zoneids) == 0 { + id, err := scm.defaultZoneId(userCred) + if err != nil { + return output, errors.Wrap(err, "unable to fetch defaultZoneId") + } + zoneids = append(zoneids, id) + } + + // Find the appropriate wire from above wires according to the Host's IP. The logic for finding is that the networks in the wire can contain the Host as much as possible. If no suitable one is found, a new wire is used. + // fetch all Host + factory, err := cloudprovider.GetProviderFactory(input.Provider) + if err != nil { + return output, errors.Wrap(err, "cloudprovider.GetProviderFactory") + } + input.SCloudaccount, err = factory.ValidateCreateCloudaccountData(ctx, userCred, input.SCloudaccountCredential) + if err != nil { + return output, errors.Wrap(err, "providerDriver.ValidateCreateCloudaccountData") + } + var proxyFunc httputils.TransportProxyFunc + { + if input.ProxySettingId == "" { + input.ProxySettingId = proxyapi.ProxySettingId_DIRECT + } + var proxySetting *proxy.SProxySetting + proxySetting, input.ProxySettingResourceInput, err = proxy.ValidateProxySettingResourceInput(userCred, input.ProxySettingResourceInput) + if err != nil { + return output, errors.Wrap(err, "ValidateProxySettingResourceInput") + } + proxyFunc = proxySetting.HttpTransportProxyFunc() + } + provider, err := factory.GetProvider(cloudprovider.ProviderConfig{ + Vendor: input.Provider, + URL: input.AccessUrl, + Account: input.Account, + Secret: input.Secret, + ProxyFunc: proxyFunc, + Name: input.Name, + }) + if err != nil { + return output, errors.Wrap(err, "factory.GetProvider") + } + iregion, err := provider.GetOnPremiseIRegion() + if err != nil { + return output, errors.Wrap(err, "provider.GetOnPremiseIRegion") + } + // hack + client := iregion.(*esxi.SESXiClient) + return scm.prepareNets(ctx, userCred, client, zoneids, input) +} + +func (cam *SCloudaccountManager) prepareNets(ctx context.Context, userCred mcclient.TokenCredential, + client *esxi.SESXiClient, zoneids []string, input api.CloudaccountPerformPrepareNetsInput) (api.CloudaccountPerformPrepareNetsOutput, error) { + var output api.CloudaccountPerformPrepareNetsOutput + obj, err := VpcManager.FetchById(api.DEFAULT_VPC_ID) + if err != nil { + return output, errors.Wrapf(err, "can not fetch vpc %q", api.DEFAULT_VPC_ID) + } + allNetworks, err := obj.(*SVpc).GetNetworks() + if err != nil { + return output, errors.Wrapf(err, "can not get networks of vpc %q", api.DEFAULT_VPC_ID) + } + existedIpPool := newIPPool(len(allNetworks)) + for i := range allNetworks { + startIp, _ := netutils.NewIPV4Addr(allNetworks[i].GuestIpStart) + endIp, _ := netutils.NewIPV4Addr(allNetworks[i].GuestIpEnd) + existedIpPool.Insert(startIp, sSimpleNet{ + Diff: endIp - startIp, + Vlan: int32(allNetworks[i].VlanId), + Id: allNetworks[i].Id, + }) + } + + if !input.Dvs { + // fetch all wire candidate + wires, err := cam.fetchWires(userCred, input.ProjectDomainId, zoneids) + if err != nil { + return output, errors.Wrap(err, "unable to fetch wires") + } + // fetch networks + networks := make([][]SNetwork, len(wires)) + for i := range networks { + nets, err := wires[i].getNetworks(userCred, rbacutils.ScopeSystem) + if err != nil { + return output, errors.Wrap(err, "wire.getNetwork") + } + networks[i] = nets + } + nInfos, err := cam.hostVMIPsPrepareNets(ctx, client, input) + if err != nil { + return output, err + } + + return cam.parseAndSuggestSingleWire(sParseAndSuggest{ + NInfos: nInfos, + AccountName: input.Name, + ZoneIds: zoneids, + Wires: wires, + Networks: networks, + ExistedIpPool: existedIpPool, + }), nil + } + + nInfo, err := client.HostVmIPsPro(ctx) + if err != nil { + return output, err + } + + for name, ips := range nInfo.HostIps { + ipNets := make([]api.CAIPNet, len(ips)) + for i, ip := range ips { + var suitNetwork string + if p, ok := existedIpPool.Get(ip); ok { + suitNetwork = p.Id + } + ipNets[i] = api.CAIPNet{ + SuitableNetwork: suitNetwork, + IP: ip.String(), + } + } + output.Hosts = append(output.Hosts, api.CAGuestNet{ + Name: name, + IPNets: ipNets, + }) + } + output.Guests = cam.parseSimpleVms(nInfo.VMs, existedIpPool) + + vsList := nInfo.VsMap.List() + for i := range vsList { + vs := vsList[i] + hosts := make([]api.SimpleHost, len(vs.Hosts)) + for i := range hosts { + hosts[i] = api.SimpleHost{ + Id: vs.Hosts[i].Id, + Name: vs.Hosts[i].Name, + } + } + wire := api.CAPWire{ + Name: vs.Name, + Id: vs.Id, + Distributed: vs.Distributed, + Hosts: hosts, + } + // preparenets for host + for hName, ips := range vs.HostIps { + if len(ips) == 0 { + output.Hosts = append(output.Hosts, api.CAGuestNet{ + Name: hName, + }) + } + for j := 0; j < len(ips); j++ { + baseNet := ips[j].NetAddr(24) + 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 := ips[0], ips[len(ips)-1] + simNetConfs := cam.expandIPRnage(ips, ipLimitLow, ipLimitUp, func(proc esxi.SIPProc) bool { + return proc.IsHost + }, nInfo.IPPool, existedIpPool) + for k := range simNetConfs { + wire.HostNetworks = append(wire.HostNetworks, api.CANetConf{ + Name: fmt.Sprintf("host-network-%d", len(wire.HostNetworks)+1), + CASimpleNetConf: simNetConfs[k], + }) + } + } + } + for vlan, ips := range vs.Vlans { + simNetConfs := cam.expandIPRnage(ips, 0, 0, func(proc esxi.SIPProc) bool { + return proc.VlanId == vlan + }, nInfo.IPPool, existedIpPool) + for j := range simNetConfs { + simNetConfs[j].VlanID = vlan + wire.GuestNetworks = append(wire.GuestNetworks, api.CANetConf{ + Name: fmt.Sprintf("guest-network-%d", len(wire.GuestNetworks)+1), + CASimpleNetConf: simNetConfs[j], + }) + } + } + output.Wires = append(output.Wires, wire) + } + + return output, nil +} + +func (cam *SCloudaccountManager) parseSimpleVms(vms []esxi.SSimpleVM, existedIpPool *sIPPool) []api.CAGuestNet { + guests := make([]api.CAGuestNet, len(vms)) + for i := range guests { + guests[i].Name = vms[i].Name + for _, ipVlan := range vms[i].IPVlans { + var suitableNetwork string + p, ok := existedIpPool.Get(ipVlan.IP) + if ok { + suitableNetwork = p.Id + } + guests[i].IPNets = append(guests[i].IPNets, api.CAIPNet{ + IP: ipVlan.IP.String(), + VlanID: ipVlan.VlanId, + SuitableNetwork: suitableNetwork, + }) + } + } + return guests +} + +func (cam *SCloudaccountManager) expandIPRnage(ips []netutils.IPV4Addr, limitLow, limitUp netutils.IPV4Addr, + expand func(esxi.SIPProc) bool, ipPool esxi.SIPPool, existedIpPool *sIPPool) []api.CASimpleNetConf { + ret := make([]api.CASimpleNetConf, 0) + for i := 0; i < len(ips); i++ { + ip := ips[i] + if _, ok := existedIpPool.Get(ip); ok { + 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 + } + // find startip + startIp := ip - 1 + for ; startIp >= netLimitLow; startIp-- { + if _, ok := existedIpPool.Get(startIp); ok { + break + } + if _, ok := ipPool.Get(startIp); ok { + break + } + } + endIp := ip + 1 + for ; endIp <= netLimitUp; endIp++ { + if _, ok := existedIpPool.Get(endIp); ok { + break + } + if proc, ok := ipPool.Get(endIp); ok { + if expand(proc) { + i++ + continue + } + break + } + } + ret = append(ret, api.CASimpleNetConf{ + GuestIpStart: (startIp + 1).String(), + GuestIpEnd: (endIp - 1).String(), + GuestIpMask: 24, + GuestGateway: (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 sParseAndSuggest struct { + NInfos []sNetworkInfo + AccountName string + ZoneIds []string + Wires []SWire + Networks [][]SNetwork + ExistedIpPool *sIPPool +} + +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 +} + +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 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 +} + +func (scm *SCloudaccountManager) parseAndSuggestSingleWire(params sParseAndSuggest) api.CloudaccountPerformPrepareNetsOutput { + var ( + output api.CloudaccountPerformPrepareNetsOutput + nInfos = params.NInfos + wires = params.Wires + networks = params.Networks + ) + output.CAWireNets = make([]api.CAWireNet, 0, len(nInfos)) + for _, ni := range nInfos { + var ( + wireNet api.CAWireNet + hostIps = ni.HostIps + ) + + // key of ipHosts is host's ip + ipHosts := make(map[netutils.IPV4Addr]string, len(hostIps)) + for name, ip := range hostIps { + ipHosts[ip] = 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 { + wireNet.SuitableWire = suitableWire.GetId() + } else { + wireNet.SuggestedWire = api.CAWireConf{ + ZoneIds: params.ZoneIds, + Name: ni.prefix + "-wire", + Description: fmt.Sprintf("Auto created Wire for VMware account %q", params.AccountName), + } + } + + // 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", ni.prefix, i+1) + } + wireNet.HostSuggestedNetworks = confs + } + + for i := range wireNet.HostSuggestedNetworks { + ipStart, _ := netutils.NewIPV4Addr(wireNet.HostSuggestedNetworks[i].GuestIpStart) + ipEnd, _ := netutils.NewIPV4Addr(wireNet.HostSuggestedNetworks[i].GuestIpEnd) + params.ExistedIpPool.Insert(ipStart, sSimpleNet{ + Diff: ipEnd - ipStart, + }) + } + + wireNet.Guests = scm.parseSimpleVms(ni.VMs, params.ExistedIpPool) + + for vlan, ips := range ni.VlanIps { + simNetConfs := scm.expandIPRnage(ips, 0, 0, func(proc esxi.SIPProc) bool { + return proc.VlanId == vlan + }, ni.IPPool, params.ExistedIpPool) + for i := range simNetConfs { + simNetConfs[i].VlanID = vlan + wireNet.GuestSuggestedNetworks = append(wireNet.GuestSuggestedNetworks, api.CANetConf{ + Name: fmt.Sprintf("host-network-%d", len(wireNet.GuestSuggestedNetworks)+1), + CASimpleNetConf: simNetConfs[i], + }) + } + } + output.CAWireNets = append(output.CAWireNets, wireNet) + } + return output +} + +func (manager *SCloudaccountManager) fetchWires(userCred mcclient.TokenCredential, domainId string, zoneIds []string) ([]SWire, error) { + q := WireManager.Query().In("zone_id", zoneIds) + if len(domainId) > 0 { + ownerId := &db.SOwnerId{} + ownerId.DomainId = domainId + q = WireManager.FilterByOwner(q, ownerId, rbacutils.ScopeDomain) + } else { + q = WireManager.FilterByOwner(q, userCred, rbacutils.ScopeDomain) + } + wires := make([]SWire, 0, 1) + err := db.FetchModelObjects(WireManager, q, &wires) + return wires, err +} + +func (manager *SCloudaccountManager) defaultZoneId(userCred mcclient.TokenCredential) (string, error) { + zone, err := ZoneManager.FetchByName(userCred, "zone0") + if err != nil { + return "", err + } + return zone.GetId(), nil +} + +func (manager *SCloudaccountManager) FetchEsxiZoneIds() ([]string, error) { + q := BaremetalagentManager.Query().Equals("agent_type", "esxiagent").Asc("created_at") + agents := make([]SBaremetalagent, 0, 1) + err := db.FetchModelObjects(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 +} + +// The suggestHostNetworks give the suggest config of network contains the ip in 'ips'. +// The suggested network mask is 24 and the gateway is x.x.x.1. +// The suggests network is the smallest network segment that meets the above conditions. +func (manager *SCloudaccountManager) suggestHostNetworks(ips []netutils.IPV4Addr) []api.CASimpleNetConf { + if len(ips) == 0 { + return nil + } + sort.Slice(ips, func(i, j int) bool { + return ips[i] < ips[j] + }) + var ( + mask int8 = 24 + lastnetAddr netutils.IPV4Addr + consequent []netutils.IPV4Addr + ret []api.CASimpleNetConf + ) + lastnetAddr = ips[0].NetAddr(mask) + consequent = []netutils.IPV4Addr{ips[0]} + for i := 1; i < len(ips); i++ { + ip := ips[i] + netAddr := ip.NetAddr(mask) + if netAddr == lastnetAddr && consequent[len(consequent)-1]+1 == ip { + consequent = append(consequent, ip) + continue + } + + if netAddr != lastnetAddr { + lastnetAddr = netAddr + } + + gatewayIP := consequent[0].NetAddr(mask) + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi) + ret = append(ret, api.CASimpleNetConf{ + GuestIpStart: consequent[0].String(), + GuestIpEnd: consequent[len(consequent)-1].String(), + GuestIpMask: mask, + GuestGateway: gatewayIP.String(), + }) + consequent = []netutils.IPV4Addr{ip} + } + gatewayIp := consequent[0].NetAddr(mask) + netutils.IPV4Addr(options.Options.DefaultNetworkGatewayAddressEsxi) + ret = append(ret, api.CASimpleNetConf{ + GuestIpStart: consequent[0].String(), + GuestIpEnd: consequent[len(consequent)-1].String(), + GuestIpMask: mask, + GuestGateway: gatewayIp.String(), + }) + return ret +} + +func (self *SCloudaccount) StartSyncVMwareNetworkTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, networkZone string) error { + if self.Provider != api.CLOUD_PROVIDER_VMWARE { + return errors.ErrNotSupported + } + params := jsonutils.NewDict() + params.Set("zone", jsonutils.NewString(networkZone)) + task, err := taskman.TaskManager.NewTask(ctx, "CloudAccountSyncVMwareNetworkTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + self.SetStatus(userCred, api.CLOUD_PROVIDER_SYNC_NETWORK, "StartSyncVMwareNetworkTask") + task.ScheduleRun(nil) + return nil +} diff --git a/pkg/compute/models/cloudaccounts_test.go b/pkg/compute/models/cloudaccounts_test.go index fb391d3a42..4aa735c98d 100644 --- a/pkg/compute/models/cloudaccounts_test.go +++ b/pkg/compute/models/cloudaccounts_test.go @@ -17,7 +17,7 @@ func TestParseAndSuggest(t *testing.T) { if err != nil { t.Fatalf(err.Error()) } - out := CloudaccountManager.parseAndSuggest(param) + out := CloudaccountManager.parseAndSuggestSingleWire(param) for _, net := range out.CAWireNets[0].GuestSuggestedNetworks { ips, _ := netutils.NewIPV4Addr(net.GuestIpStart) ipe, _ := netutils.NewIPV4Addr(net.GuestIpEnd) @@ -80,8 +80,9 @@ func structureTestData() (sParseAndSuggest, error) { ZoneIds: []string{ "zone_test", }, - Wires: []SWire{}, - Networks: [][]SNetwork{}, + Wires: []SWire{}, + Networks: [][]SNetwork{}, + ExistedIpPool: newIPPool(), }, nil } diff --git a/pkg/compute/models/networks.go b/pkg/compute/models/networks.go index 0044cb387c..ea3c20f687 100644 --- a/pkg/compute/models/networks.go +++ b/pkg/compute/models/networks.go @@ -1228,6 +1228,10 @@ func (self *SNetwork) ensureIfnameHint() { log.Infof("network %s(%s): initialized ifname hint: %s", self.Name, self.Id, hint) } +func (manager *SNetworkManager) NewIfnameHint(hint string) (string, error) { + return manager.newIfnameHint(hint) +} + func (manager *SNetworkManager) newIfnameHint(hint string) (string, error) { isa := func(c byte) bool { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') diff --git a/pkg/compute/tasks/cloud_account_sync_vmware_net.go b/pkg/compute/tasks/cloud_account_sync_vmware_net.go new file mode 100644 index 0000000000..392d885afc --- /dev/null +++ b/pkg/compute/tasks/cloud_account_sync_vmware_net.go @@ -0,0 +1,537 @@ +package tasks + +import ( + "context" + "fmt" + "sort" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/netutils" + + 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/multicloud/esxi" + "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +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() + 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 { + 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(rbacutils.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.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(), rbacutils.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(), rbacutils.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"` +} diff --git a/pkg/mcclient/options/cloudaccounts.go b/pkg/mcclient/options/cloudaccounts.go index 2146235c48..2bac14f540 100644 --- a/pkg/mcclient/options/cloudaccounts.go +++ b/pkg/mcclient/options/cloudaccounts.go @@ -755,6 +755,7 @@ type SVMwareCloudAccountPrepareNetsOptions struct { 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"` + Dvs bool `help:"whether to enable dvs corresponding wire"` NAME string `help:"name for this account"` } diff --git a/pkg/util/logclient/consts.go b/pkg/util/logclient/consts.go index 6da9c57b60..ed85759f1f 100644 --- a/pkg/util/logclient/consts.go +++ b/pkg/util/logclient/consts.go @@ -198,4 +198,8 @@ const ( ACT_UPDATE_TAGS = "update_tags" ACT_SET_ALERT = "set_alert" + + ACT_CLOUDACCOUNT_SYNC_NETWORK = "sync_network" + + ACT_MERGE_NETWORK = "merge_network" )