// 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" "database/sql" "fmt" "math" "math/big" "sort" "strings" "time" "yunion.io/x/cloudmux/pkg/cloudprovider" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/gotypes" "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/billing" "yunion.io/x/pkg/util/compare" "yunion.io/x/pkg/util/netutils" "yunion.io/x/pkg/util/rand" "yunion.io/x/pkg/util/rbacscope" "yunion.io/x/pkg/util/regutils" "yunion.io/x/pkg/util/sets" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/apis" billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/consts" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/util/logclient" "yunion.io/x/onecloud/pkg/util/rbacutils" "yunion.io/x/onecloud/pkg/util/stringutils2" ) type SNetworkManager struct { db.SSharableVirtualResourceBaseManager db.SExternalizedResourceBaseManager SWireResourceBaseManager } var NetworkManager *SNetworkManager func GetNetworkManager() *SNetworkManager { if NetworkManager != nil { return NetworkManager } NetworkManager = &SNetworkManager{ SSharableVirtualResourceBaseManager: db.NewSharableVirtualResourceBaseManager( SNetwork{}, "networks_tbl", "network", "networks", ), } NetworkManager.SetVirtualObject(NetworkManager) return NetworkManager } func init() { GetNetworkManager() } type SNetwork struct { db.SSharableVirtualResourceBase db.SExternalizedResourceBase SWireResourceBase IfnameHint string `width:"9" charset:"ascii" nullable:"true" list:"user" create:"optional"` // 起始IP地址 GuestIpStart string `width:"16" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // 结束IP地址 GuestIpEnd string `width:"16" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // 掩码 GuestIpMask int8 `nullable:"true" list:"user" update:"user" create:"optional"` // 网关地址 GuestGateway string `width:"16" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // DNS, allow multiple dns, seperated by "," GuestDns string `width:"64" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // allow multiple dhcp, seperated by "," GuestDhcp string `width:"64" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // allow mutiple ntp, seperated by "," GuestNtp string `width:"64" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // search domain GuestDomain string `width:"128" charset:"ascii" nullable:"true" get:"user" update:"user"` // 起始IPv6地址 GuestIp6Start string `width:"64" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // 结束IPv6地址 GuestIp6End string `width:"64" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // IPv6子网掩码 GuestIp6Mask uint8 `nullable:"true" list:"user" update:"user" create:"optional"` // IPv6网关 GuestGateway6 string `width:"64" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // IPv6域名服务器 // GuestDns6 string `width:"64" charset:"ascii" nullable:"true"` // GuestDomain6 string `width:"128" charset:"ascii" nullable:"true"` VlanId int `nullable:"false" default:"1" list:"user" update:"user" create:"optional"` // 服务器类型 // example: server ServerType api.TNetworkType `width:"16" charset:"ascii" default:"guest" nullable:"true" list:"user" update:"admin" create:"optional"` // 分配策略 AllocPolicy string `width:"16" charset:"ascii" nullable:"true" get:"user" update:"user" create:"optional"` AllocTimoutSeconds int `default:"0" nullable:"true" get:"admin"` // 该网段是否用于自动分配IP地址,如果为false,则用户需要明确选择该网段,才会使用该网段分配IP, // 如果为true,则用户不指定网段时,则自动从该值为true的网络中选择一个分配地址 IsAutoAlloc tristate.TriState `list:"user" get:"user" update:"user" create:"optional"` // 线路类型 BgpType string `width:"64" charset:"utf8" nullable:"false" list:"user" get:"user" update:"user" create:"optional"` } func (manager *SNetworkManager) GetContextManagers() [][]db.IModelManager { return [][]db.IModelManager{ {WireManager}, } } func (snet *SNetwork) getMtu(wire *SWire) int16 { baseMtu := options.Options.DefaultMtu if wire == nil { wire, _ = snet.GetWire() } if wire != nil { if IsOneCloudVpcResource(wire) { return int16(options.Options.OvnUnderlayMtu - api.VPC_OVN_ENCAP_COST) } else if wire.Mtu != 0 { return int16(wire.Mtu) } else { return int16(baseMtu) } } return int16(baseMtu) } func (snet *SNetwork) GetNetworkInterfaces() ([]SNetworkInterface, error) { sq := NetworkinterfacenetworkManager.Query().SubQuery() q := NetworkInterfaceManager.Query() q = q.Join(sq, sqlchemy.Equals(q.Field("id"), sq.Field("networkinterface_id"))). Filter(sqlchemy.Equals(sq.Field("network_id"), snet.Id)) networkinterfaces := []SNetworkInterface{} err := db.FetchModelObjects(NetworkInterfaceManager, q, &networkinterfaces) if err != nil { return nil, err } return networkinterfaces, nil } func (snet *SNetwork) ValidateDeleteCondition(ctx context.Context, data *api.NetworkDetails) error { if data == nil { data = &api.NetworkDetails{} nics, err := NetworkManager.TotalNicCount([]string{snet.Id}) if err != nil { return errors.Wrapf(err, "TotalNicCount") } if cnt, ok := nics[snet.Id]; ok { data.SNetworkNics = cnt } } if data.Total-data.ReserveVnics4-data.NetworkinterfaceVnics > 0 || data.Total6-data.ReserveVnics6 > 0 { return httperrors.NewNotEmptyError("not an empty network %s", jsonutils.Marshal(data.SNetworkNics).String()) } return snet.SSharableVirtualResourceBase.ValidateDeleteCondition(ctx, nil) } func (snet *SNetwork) GetGuestnetworks() ([]SGuestnetwork, error) { q := GuestnetworkManager.Query().Equals("network_id", snet.Id) gns := []SGuestnetwork{} err := db.FetchModelObjects(GuestnetworkManager, q, &gns) if err != nil { return nil, errors.Wrapf(err, "db.FetchModelObjects") } return gns, nil } func (snet *SNetwork) GetDBInstanceNetworks() ([]SDBInstanceNetwork, error) { q := DBInstanceNetworkManager.Query().Equals("network_id", snet.Id) networks := []SDBInstanceNetwork{} err := db.FetchModelObjects(DBInstanceNetworkManager, q, &networks) if err != nil { return nil, errors.Wrapf(err, "db.FetchModelObjects") } return networks, nil } func (manager *SNetworkManager) GetOrCreateClassicNetwork(ctx context.Context, wire *SWire) (*SNetwork, error) { _network, err := db.FetchByExternalIdAndManagerId(manager, wire.Id, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { v, _ := wire.GetVpc() if v != nil { wire := WireManager.Query().SubQuery() vpc := VpcManager.Query().SubQuery() return q.Join(wire, sqlchemy.Equals(wire.Field("id"), q.Field("wire_id"))). Join(vpc, sqlchemy.Equals(vpc.Field("id"), wire.Field("vpc_id"))). Filter(sqlchemy.Equals(vpc.Field("manager_id"), v.ManagerId)) } return q }) if err == nil { return _network.(*SNetwork), nil } if errors.Cause(err) != sql.ErrNoRows { return nil, errors.Wrap(err, "db.FetchByExternalId") } network := SNetwork{ GuestIpStart: "0.0.0.0", GuestIpEnd: "255.255.255.255", GuestIpMask: 0, GuestGateway: "0.0.0.0", ServerType: api.NETWORK_TYPE_GUEST, } network.WireId = wire.Id network.SetModelManager(manager, &network) network.Name = fmt.Sprintf("emulate network for classic network with wire %s", wire.Id) network.ExternalId = wire.Id network.IsEmulated = true network.IsPublic = true network.PublicScope = "system" admin := auth.AdminCredential() network.DomainId = admin.GetProjectDomainId() network.ProjectId = admin.GetProjectId() network.Status = api.NETWORK_STATUS_UNAVAILABLE err = manager.TableSpec().Insert(ctx, &network) if err != nil { return nil, errors.Wrap(err, "Insert classic network") } return &network, nil } func (snet *SNetwork) GetUsedAddresses(ctx context.Context) map[string]bool { used := make(map[string]bool) q := snet.getUsedAddressQuery(ctx, nil, nil, rbacscope.ScopeSystem, true) results, err := q.AllStringMap() if err != nil { log.Errorf("GetUsedAddresses fail %s", err) return used } for _, result := range results { used[result["ip_addr"]] = true } return used } func (snet *SNetwork) GetUsedAddresses6(ctx context.Context) map[string]bool { used := make(map[string]bool) q := snet.getUsedAddressQuery6(ctx, nil, nil, rbacscope.ScopeSystem, true) results, err := q.AllStringMap() if err != nil { log.Errorf("GetUsedAddresses fail %s", err) return used } for _, result := range results { used[result["ip6_addr"]] = true } return used } func (snet *SNetwork) GetIPRange() *netutils.IPV4AddrRange { return snet.getIPRange() } func (net *SNetwork) getIPRange() *netutils.IPV4AddrRange { if len(net.GuestIpStart) == 0 || len(net.GuestIpEnd) == 0 { return nil } start, _ := netutils.NewIPV4Addr(net.GuestIpStart) end, _ := netutils.NewIPV4Addr(net.GuestIpEnd) ipRange := netutils.NewIPV4AddrRange(start, end) return &ipRange } func (net *SNetwork) getIPRange6() *netutils.IPV6AddrRange { if len(net.GuestIp6Start) == 0 || len(net.GuestIp6End) == 0 { return nil } start, _ := netutils.NewIPV6Addr(net.GuestIp6Start) end, _ := netutils.NewIPV6Addr(net.GuestIp6End) ipRange := netutils.NewIPV6AddrRange(start, end) return &ipRange } 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 { recentUsed := false if recentUsedAddrTable != nil { if _, ok := recentUsedAddrTable[ipstr]; ok { recentUsed = true } } return recentUsed } else { return true } } func (snet *SNetwork) getFreeIP6(addrTable map[string]bool, recentUsedAddrTable map[string]bool, candidate string) (string, error) { if !snet.IsSupportIPv6() { return "", errors.Wrapf(cloudprovider.ErrNotSupported, "ipv6") } iprange := snet.getIPRange6() // Try candidate first if len(candidate) > 0 { candIP, err := netutils.NewIPV6Addr(candidate) if err != nil { return "", errors.Wrap(err, "NewIPV6Addr") } if !iprange.Contains(candIP) { return "", httperrors.NewInputParameterError("candidate %s out of range %s", candidate, iprange.String()) } if _, ok := addrTable[candidate]; !ok { return candidate, nil } } // ipv6 support random address allocation only const MAX_TRIES = 5 for i := 0; i < MAX_TRIES; i += 1 { ip := iprange.Random() if !isIpUsed(ip.String(), addrTable, recentUsedAddrTable) { return ip.String(), nil } } // failed, fallback to IPAllocationStepup return "", httperrors.NewInsufficientResourceError("Out of IP address") } func (snet *SNetwork) getFreeIP(addrTable map[string]bool, recentUsedAddrTable map[string]bool, candidate string, allocDir api.IPAllocationDirection) (string, error) { iprange := snet.getIPRange() // Try candidate first if len(candidate) > 0 { candIP, err := netutils.NewIPV4Addr(candidate) if err != nil { return "", err } if !iprange.Contains(candIP) { return "", httperrors.NewInputParameterError("candidate %s out of range %s", candidate, iprange.String()) } if _, ok := addrTable[candidate]; !ok { return candidate, nil } } // If network's alloc_policy is not none, then use network's alloc_policy if len(snet.AllocPolicy) > 0 && api.IPAllocationDirection(snet.AllocPolicy) != api.IPAllocationNone { allocDir = api.IPAllocationDirection(snet.AllocPolicy) } // if alloc_dir is not speicified, and network's alloc_policy is not either, use default if len(allocDir) == 0 { allocDir = api.IPAllocationDirection(options.Options.DefaultIPAllocationDirection) } if allocDir == api.IPAllocationStepdown { ip, _ := netutils.NewIPV4Addr(snet.GuestIpEnd) for iprange.Contains(ip) { if !isIpUsed(ip.String(), addrTable, recentUsedAddrTable) { return ip.String(), nil } ip = ip.StepDown() } } else { if allocDir == api.IPAllocationRandom { const MAX_TRIES = 5 for i := 0; i < MAX_TRIES; i += 1 { ip := iprange.Random() if !isIpUsed(ip.String(), addrTable, recentUsedAddrTable) { return ip.String(), nil } } // failed, fallback to IPAllocationStepup } ip, _ := netutils.NewIPV4Addr(snet.GuestIpStart) for iprange.Contains(ip) { if !isIpUsed(ip.String(), addrTable, recentUsedAddrTable) { return ip.String(), nil } ip = ip.StepUp() } } return "", httperrors.NewInsufficientResourceError("Out of IP address") } func (snet *SNetwork) GetFreeIPWithLock(ctx context.Context, userCred mcclient.TokenCredential, addrTable map[string]bool, recentUsedAddrTable map[string]bool, candidate string, allocDir api.IPAllocationDirection, reserved bool, addrType api.TAddressType) (string, error) { lockman.LockObject(ctx, snet) defer lockman.ReleaseObject(ctx, snet) return snet.GetFreeIP(ctx, userCred, addrTable, recentUsedAddrTable, candidate, allocDir, reserved, addrType) } func (snet *SNetwork) GetFreeIP(ctx context.Context, userCred mcclient.TokenCredential, addrTable map[string]bool, recentUsedAddrTable map[string]bool, candidate string, allocDir api.IPAllocationDirection, reserved bool, addrType api.TAddressType) (string, error) { // if reserved true, first try find IP in reserved IP pool if reserved { rip := ReservedipManager.GetReservedIP(snet, candidate, addrType) if rip != nil { rip.Release(ctx, userCred, snet) return candidate, nil } // return "", httperrors.NewInsufficientResourceError("Reserved address %s not found", candidate) // if not find, warning, then fallback to normal procedure log.Warningf("Reserved address %s not found", candidate) } if addrType == api.AddressTypeIPv6 { if addrTable == nil { addrTable = snet.GetUsedAddresses6(ctx) } if recentUsedAddrTable == nil { recentUsedAddrTable = GuestnetworkManager.getRecentlyReleasedIPAddresses6(snet.Id, snet.getAllocTimoutDuration()) } cand, err := snet.getFreeIP6(addrTable, recentUsedAddrTable, candidate) if err != nil { return "", errors.Wrap(err, "getFreeIP6") } return cand, nil } else { if addrTable == nil { addrTable = snet.GetUsedAddresses(ctx) } if recentUsedAddrTable == nil { recentUsedAddrTable = GuestnetworkManager.getRecentlyReleasedIPAddresses(snet.Id, snet.getAllocTimoutDuration()) } cand, err := snet.getFreeIP(addrTable, recentUsedAddrTable, candidate, allocDir) if err != nil { return "", errors.Wrap(err, "getFreeIP") } return cand, nil } } func (snet *SNetwork) GetNetAddr() netutils.IPV4Addr { startIp, _ := netutils.NewIPV4Addr(snet.GuestIpStart) return startIp.NetAddr(snet.GuestIpMask) } func (snet *SNetwork) GetDNS(zoneName string) string { if len(snet.GuestDns) > 0 { return snet.GuestDns } if len(zoneName) == 0 { wire, _ := snet.GetWire() if wire != nil { zone, _ := wire.GetZone() if zone != nil { zoneName = zone.Name } } } srvs, _ := auth.GetDNSServers(options.Options.Region, zoneName) if len(srvs) > 0 { return strings.Join(srvs, ",") } if len(options.Options.DNSServer) > 0 { return options.Options.DNSServer } return "" } func (snet *SNetwork) GetNTP() string { if len(snet.GuestNtp) > 0 { return snet.GuestNtp } else { zoneName := "" wire, _ := snet.GetWire() if wire != nil { zone, _ := wire.GetZone() if zone != nil { zoneName = zone.Name } } srvs, _ := auth.GetNTPServers(options.Options.Region, zoneName) if len(srvs) > 0 { return strings.Join(srvs, ",") } return "" } } func (snet *SNetwork) GetDomain() string { if len(snet.GuestDomain) > 0 { return snet.GuestDomain } else if !apis.IsIllegalSearchDomain(options.Options.DNSDomain) { return options.Options.DNSDomain } else { return "" } } func (snet *SNetwork) GetRoutes() []types.SRoute { ret := make([]types.SRoute, 0) routes := snet.GetMetadataJson(context.Background(), "static_routes", nil) if routes != nil { routesMap, err := routes.GetMap() if err != nil { return nil } for net, routeJson := range routesMap { route, _ := routeJson.GetString() ret = append(ret, types.SRoute{net, route}) } } return ret } func (snet *SNetwork) updateDnsRecord(nic *SGuestnetwork, isAdd bool) { guest := nic.GetGuest() if !gotypes.IsNil(guest) { snet._updateDnsRecord(guest.Name, nic.IpAddr, isAdd) } } func (snet *SNetwork) _updateDnsRecord(name string, ipAddr string, isAdd bool) { if len(snet.GuestDns) > 0 && len(snet.GuestDomain) > 0 && len(ipAddr) > 0 { keyName := snet.GetMetadata(context.Background(), "dns_update_key_name", nil) keySecret := snet.GetMetadata(context.Background(), "dns_update_key_secret", nil) dnsSrv := snet.GetMetadata(context.Background(), "dns_update_server", nil) if len(dnsSrv) == 0 || !regutils.MatchIPAddr(dnsSrv) { dnsSrv = snet.GuestDns } log.Infof("dns update %s %s isAdd=%t", ipAddr, dnsSrv, isAdd) if len(keyName) > 0 && len(keySecret) > 0 { /* netman.get_manager().dns_update(name, snet.guest_domain, ip_addr, None, dns_srv, snet.guest_dns6, key_name, key_secret, is_add) */ } targets := snet.getDnsUpdateTargets() if targets != nil { for srv, keys := range targets { for _, key := range keys { log.Debugf("Register %s %s", srv, key) /* netman.get_manager().dns_update(name, snet.guest_domain, ip_addr, None, srv, None, key.get('key', None), key.get('secret', None), is_add) */ } } } } } type DNSUpdateKeySecret struct { Key string Secret string } func (snet *SNetwork) getDnsUpdateTargets() map[string][]DNSUpdateKeySecret { targets := make(map[string][]DNSUpdateKeySecret) targetsJson := snet.GetMetadataJson(context.Background(), api.EXTRA_DNS_UPDATE_TARGETS, nil) if targetsJson == nil { return nil } else { err := targetsJson.Unmarshal(&targets) if err != nil { return nil } return targets } } func (snet *SNetwork) GetGuestIpv4StartAddress() netutils.IPV4Addr { addr, _ := netutils.NewIPV4Addr(snet.GuestIpStart) return addr } func (snet *SNetwork) IsExitNetwork() bool { if len(snet.GuestIpStart) == 0 { return false } return len(snet.ExternalId) == 0 && netutils.IsExitAddress(snet.GetGuestIpv4StartAddress()) } func (manager *SNetworkManager) getNetworksByWire(ctx context.Context, wire *SWire) ([]SNetwork, error) { return wire.getNetworks(ctx, nil, nil, rbacscope.ScopeNone) } func (manager *SNetworkManager) SyncNetworks( ctx context.Context, userCred mcclient.TokenCredential, wire *SWire, nets []cloudprovider.ICloudNetwork, provider *SCloudprovider, xor bool, ) ([]SNetwork, []cloudprovider.ICloudNetwork, compare.SyncResult) { syncOwnerId := provider.GetOwnerId() lockman.LockRawObject(ctx, manager.Keyword(), wire.Id) defer lockman.ReleaseRawObject(ctx, manager.Keyword(), wire.Id) localNets := make([]SNetwork, 0) remoteNets := make([]cloudprovider.ICloudNetwork, 0) syncResult := compare.SyncResult{} dbNets, err := manager.getNetworksByWire(ctx, wire) if err != nil { syncResult.Error(err) return nil, nil, syncResult } for i := range dbNets { if taskman.TaskManager.IsInTask(&dbNets[i]) { syncResult.Error(fmt.Errorf("object in task")) return nil, nil, syncResult } } removed := make([]SNetwork, 0) commondb := make([]SNetwork, 0) commonext := make([]cloudprovider.ICloudNetwork, 0) added := make([]cloudprovider.ICloudNetwork, 0) err = compare.CompareSets(dbNets, nets, &removed, &commondb, &commonext, &added) if err != nil { syncResult.Error(err) return nil, nil, syncResult } for i := 0; i < len(removed); i += 1 { err = removed[i].syncRemoveCloudNetwork(ctx, userCred) if err != nil { syncResult.DeleteError(err) } else { syncResult.Delete() } } if !xor { for i := 0; i < len(commondb); i += 1 { err = commondb[i].SyncWithCloudNetwork(ctx, userCred, commonext[i]) if err != nil { syncResult.UpdateError(err) continue } localNets = append(localNets, commondb[i]) remoteNets = append(remoteNets, commonext[i]) syncResult.Update() } } for i := 0; i < len(added); i += 1 { new, err := manager.newFromCloudNetwork(ctx, userCred, added[i], wire, syncOwnerId, provider) if err != nil { syncResult.AddError(err) } else { localNets = append(localNets, *new) remoteNets = append(remoteNets, added[i]) syncResult.Add() } } return localNets, remoteNets, syncResult } func (snet *SNetwork) syncRemoveCloudNetwork(ctx context.Context, userCred mcclient.TokenCredential) error { lockman.LockObject(ctx, snet) defer lockman.ReleaseObject(ctx, snet) if snet.ExternalId == snet.WireId { return nil } err := snet.ValidateDeleteCondition(ctx, nil) if err != nil { // cannot delete err = snet.SetStatus(ctx, userCred, api.NETWORK_STATUS_UNKNOWN, "Sync to remove") } else { err = snet.RealDelete(ctx, userCred) if err == nil { notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{ Obj: snet, Action: notifyclient.ActionSyncDelete, }) } } return err } func (snet *SNetwork) SyncWithCloudNetwork(ctx context.Context, userCred mcclient.TokenCredential, extNet cloudprovider.ICloudNetwork) error { diff, err := db.UpdateWithLock(ctx, snet, func() error { if options.Options.EnableSyncName { newName, _ := db.GenerateAlterName(snet, extNet.GetName()) if len(newName) > 0 { snet.Name = newName } } snet.Status = extNet.GetStatus() snet.GuestIpStart = extNet.GetIpStart() snet.GuestIpEnd = extNet.GetIpEnd() snet.GuestIpMask = extNet.GetIpMask() snet.GuestGateway = extNet.GetGateway() snet.ServerType = api.TNetworkType(extNet.GetServerType()) snet.GuestIp6Start = extNet.GetIp6Start() snet.GuestIp6End = extNet.GetIp6End() snet.GuestIp6Mask = extNet.GetIp6Mask() snet.GuestGateway6 = extNet.GetGateway6() snet.AllocTimoutSeconds = extNet.GetAllocTimeoutSeconds() if createdAt := extNet.GetCreatedAt(); !createdAt.IsZero() { snet.CreatedAt = createdAt } return nil }) if err != nil { log.Errorf("syncWithCloudNetwork error %s", err) return err } db.OpsLog.LogSyncUpdate(snet, diff, userCred) if len(diff) > 0 { notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{ Obj: snet, Action: notifyclient.ActionSyncUpdate, }) } vpc, err := snet.GetVpc() if err != nil { return errors.Wrapf(err, "GetVpc") } provider := vpc.GetCloudprovider() if provider != nil { if account, _ := provider.GetCloudaccount(); account != nil { syncVirtualResourceMetadata(ctx, userCred, snet, extNet, account.ReadOnly) } SyncCloudProject(ctx, userCred, snet, provider.GetOwnerId(), extNet, provider) shareInfo := provider.getAccountShareInfo() if utils.IsInStringArray(provider.Provider, api.PRIVATE_CLOUD_PROVIDERS) && extNet.GetPublicScope() == rbacscope.ScopeNone { shareInfo = apis.SAccountShareInfo{ IsPublic: false, PublicScope: rbacscope.ScopeNone, } } snet.SyncShareState(ctx, userCred, shareInfo) } return nil } func (manager *SNetworkManager) newFromCloudNetwork(ctx context.Context, userCred mcclient.TokenCredential, extNet cloudprovider.ICloudNetwork, wire *SWire, syncOwnerId mcclient.IIdentityProvider, provider *SCloudprovider) (*SNetwork, error) { net := &SNetwork{} net.SetModelManager(manager, net) net.Status = extNet.GetStatus() net.ExternalId = extNet.GetGlobalId() net.WireId = wire.Id net.GuestIpStart = extNet.GetIpStart() net.GuestIpEnd = extNet.GetIpEnd() net.GuestIpMask = extNet.GetIpMask() net.GuestGateway = extNet.GetGateway() net.ServerType = api.TNetworkType(extNet.GetServerType()) net.GuestIp6Start = extNet.GetIp6Start() net.GuestIp6End = extNet.GetIp6End() net.GuestIp6Mask = extNet.GetIp6Mask() net.GuestGateway6 = extNet.GetGateway6() // net.IsPublic = extNet.GetIsPublic() // extScope := extNet.GetPublicScope() // if extScope == rbacutils.ScopeDomain && !consts.GetNonDefaultDomainProjects() { // extScope = rbacutils.ScopeSystem // } // net.PublicScope = string(extScope) net.AllocTimoutSeconds = extNet.GetAllocTimeoutSeconds() if createdAt := extNet.GetCreatedAt(); !createdAt.IsZero() { net.CreatedAt = createdAt } var err = func() error { lockman.LockRawObject(ctx, manager.Keyword(), "name") defer lockman.ReleaseRawObject(ctx, manager.Keyword(), "name") newName, err := db.GenerateName(ctx, manager, syncOwnerId, extNet.GetName()) if err != nil { return err } net.Name = newName return manager.TableSpec().Insert(ctx, net) }() if err != nil { return nil, errors.Wrapf(err, "Insert") } syncVirtualResourceMetadata(ctx, userCred, net, extNet, false) if provider != nil { SyncCloudProject(ctx, userCred, net, syncOwnerId, extNet, provider) shareInfo := provider.getAccountShareInfo() if utils.IsInStringArray(provider.Provider, api.PRIVATE_CLOUD_PROVIDERS) && extNet.GetPublicScope() == rbacscope.ScopeNone { shareInfo = apis.SAccountShareInfo{ IsPublic: false, PublicScope: rbacscope.ScopeNone, } } net.SyncShareState(ctx, userCred, shareInfo) } db.OpsLog.LogEvent(net, db.ACT_CREATE, net.GetShortDesc(ctx), userCred) notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{ Obj: net, Action: notifyclient.ActionSyncCreate, }) return net, nil } func (net *SNetwork) IsAddressInRange(address netutils.IPV4Addr) bool { ipRange := net.getIPRange() if ipRange == nil { return false } return ipRange.Contains(address) } func (net *SNetwork) IsAddress6InRange(address netutils.IPV6Addr) bool { addrRange := net.getIPRange6() if addrRange == nil { return false } return addrRange.Contains(address) } func (net *SNetwork) IsAddressInNet(address netutils.IPV4Addr) bool { return net.getNetRange().Contains(address) } func (snet *SNetwork) isAddressUsed(ctx context.Context, address string) (bool, error) { q := snet.getUsedAddressQuery(ctx, nil, nil, rbacscope.ScopeSystem, true) q = q.Equals("ip_addr", address) count, err := q.CountWithError() if err != nil && errors.Cause(err) != sql.ErrNoRows { return false, errors.Wrap(err, "Query") } if count > 0 { return true, nil } else { return false, nil } } func (snet *SNetwork) isAddress6Used(ctx context.Context, address string) (bool, error) { q := snet.getUsedAddressQuery6(ctx, nil, nil, rbacscope.ScopeSystem, true) q = q.Equals("ip6_addr", address) count, err := q.CountWithError() if err != nil && errors.Cause(err) != sql.ErrNoRows { return false, errors.Wrap(err, "Query") } if count > 0 { return true, nil } else { return false, nil } } func (manager *SNetworkManager) fetchAllOnpremiseNetworks(serverType string, isPublic tristate.TriState) ([]SNetwork, error) { q := manager.Query() wires := WireManager.Query().SubQuery() q = q.Join(wires, sqlchemy.Equals(q.Field("wire_id"), wires.Field("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)) } if isPublic.IsTrue() { q = q.Filter(sqlchemy.IsTrue(q.Field("is_public"))) } else if isPublic.IsFalse() { q = q.Filter(sqlchemy.IsFalse(q.Field("is_public"))) } nets := make([]SNetwork, 0) err := db.FetchModelObjects(manager, q, &nets) if err != nil { return nil, errors.Wrap(err, "FetchModelObjects") } return nets, nil } func (manager *SNetworkManager) GetOnPremiseNetworkOfIP(ipAddr string, serverType string, isPublic tristate.TriState) (*SNetwork, error) { var addr4 netutils.IPV4Addr var addr6 netutils.IPV6Addr var isIpv6Addr = false var err error if strings.Contains(ipAddr, ":") { isIpv6Addr = true } if isIpv6Addr { addr6, err = netutils.NewIPV6Addr(ipAddr) if err != nil { return nil, errors.Wrap(err, "NewIPV6Addr") } } else { addr4, 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 isIpv6Addr { if n.IsAddress6InRange(addr6) { return &n, nil } } else { if n.IsAddressInRange(addr4) { return &n, nil } } } return nil, sql.ErrNoRows } func (manager *SNetworkManager) GetOnPremiseNetworkOfIP6(ip6Addr string, serverType string, isPublic tristate.TriState) (*SNetwork, error) { address, err := netutils.NewIPV6Addr(ip6Addr) if err != nil { return nil, errors.Wrap(err, "NewIPV6Addr") } nets, err := manager.fetchAllOnpremiseNetworks(serverType, isPublic) if err != nil { return nil, errors.Wrap(err, "fetchAllOnpremiseNetworks") } for _, n := range nets { if n.IsAddress6InRange(address) { return &n, nil } } return nil, sql.ErrNoRows } func (manager *SNetworkManager) allNetworksQ(providers []string, brands []string, cloudEnv string, rangeObjs []db.IStandaloneModel) *sqlchemy.SQuery { networks := manager.Query().SubQuery() wires := WireManager.Query().SubQuery() vpcs := VpcManager.Query().SubQuery() q := networks.Query(networks.Field("id")) 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 = CloudProviderFilter(q, vpcs.Field("manager_id"), providers, brands, cloudEnv) q = RangeObjectsFilter(q, rangeObjs, vpcs.Field("cloudregion_id"), wires.Field("zone_id"), vpcs.Field("manager_id"), nil, nil) return q } func (manager *SNetworkManager) totalPortCountQ( ctx context.Context, scope rbacscope.TRbacScope, userCred mcclient.IIdentityProvider, providers []string, brands []string, cloudEnv string, rangeObjs []db.IStandaloneModel, policyResult rbacutils.SPolicyResult, ) *sqlchemy.SQuery { q := manager.allNetworksQ(providers, brands, cloudEnv, rangeObjs) switch scope { case rbacscope.ScopeSystem: case rbacscope.ScopeDomain: q = q.Equals("domain_id", userCred.GetProjectDomainId()) case rbacscope.ScopeProject: q = q.Equals("tenant_id", userCred.GetProjectId()) } q = db.ObjectIdQueryWithPolicyResult(ctx, q, manager, policyResult) return manager.Query().In("id", q.Distinct().SubQuery()) } type NetworkPortStat struct { Count int CountExt int } func (manager *SNetworkManager) TotalPortCount( ctx context.Context, scope rbacscope.TRbacScope, userCred mcclient.IIdentityProvider, providers []string, brands []string, cloudEnv string, rangeObjs []db.IStandaloneModel, policyResult rbacutils.SPolicyResult, ) map[api.TNetworkType]NetworkPortStat { nets := make([]SNetwork, 0) err := manager.totalPortCountQ( ctx, scope, userCred, providers, brands, cloudEnv, rangeObjs, policyResult, ).All(&nets) if err != nil { log.Errorf("TotalPortCount: %v", err) } ret := make(map[api.TNetworkType]NetworkPortStat) for _, net := range nets { var stat NetworkPortStat var allStat NetworkPortStat if len(net.ServerType) > 0 { stat, _ = ret[net.ServerType] } allStat, _ = ret[""] count := net.GetTotalAddressCount() if net.IsExitNetwork() { if len(net.ServerType) > 0 { stat.CountExt += count } allStat.CountExt += count } else { if len(net.ServerType) > 0 { stat.Count += count } allStat.Count += count } if len(net.ServerType) > 0 { ret[net.ServerType] = stat } ret[""] = allStat } return ret } type SNicConfig struct { Mac string Index int Ifname string } func parseNetworkInfo(ctx context.Context, userCred mcclient.TokenCredential, info *api.NetworkConfig) (*api.NetworkConfig, error) { if info.Network != "" { netObj, err := NetworkManager.FetchByIdOrName(ctx, userCred, info.Network) if err != nil { if err == sql.ErrNoRows { return nil, httperrors.NewResourceNotFoundError2(NetworkManager.Keyword(), info.Network) } else { return nil, err } } net := netObj.(*SNetwork) if net.ProjectId == userCred.GetProjectId() || (db.IsDomainAllowGet(ctx, userCred, net) && net.DomainId == userCred.GetProjectDomainId()) || db.IsAdminAllowGet(ctx, userCred, net) || net.IsSharable(userCred) { info.Network = netObj.GetId() } else { return nil, httperrors.NewForbiddenError("no allow to access network %s", info.Network) } } if info.BwLimit == 0 { info.BwLimit = options.Options.DefaultBandwidth } return info, nil } func (snet *SNetwork) GetFreeAddressCount() (int, error) { return snet.getFreeAddressCount() } func (snet *SNetwork) GetTotalAddressCount() int { ipRange := snet.getIPRange() if ipRange != nil { return ipRange.AddressCount() } ip6Range := snet.getIPRange6() if ip6Range == nil { return 0 } cnt := ip6Range.AddressCount() if cnt.Cmp(big.NewInt(math.MaxInt32)) > 0 { return math.MaxInt32 } return int(cnt.Int64()) } func (snet *SNetwork) getFreeAddressCount() (int, error) { vnics, err := NetworkManager.TotalNicCount([]string{snet.Id}) if err != nil { return -1, errors.Wrapf(err, "TotalNicCount") } used := 0 if nics, ok := vnics[snet.Id]; ok { used = nics.Total } return snet.GetTotalAddressCount() - used, nil } func isValidNetworkInfo(ctx context.Context, userCred mcclient.TokenCredential, netConfig *api.NetworkConfig, reuseAddr, reuseAddr6 string) error { if len(netConfig.Network) > 0 { netObj, err := NetworkManager.FetchByIdOrName(ctx, userCred, netConfig.Network) if err != nil { return httperrors.NewResourceNotFoundError("Network %s not found: %v", netConfig.Network, err) } net := netObj.(*SNetwork) /* // scheduler do the check if !netConfig.Vip && !netConfig.Reserved && net.getFreeAddressCount() == 0 { return fmt.Errorf("Address exhausted in network %s") }*/ if len(netConfig.Address) > 0 { ipAddr, err := netutils.NewIPV4Addr(netConfig.Address) if err != nil { return errors.Wrap(err, "NewIPV4Addr") } if !net.IsAddressInRange(ipAddr) { return httperrors.NewInputParameterError("Address %s not in range", netConfig.Address) } if netConfig.Reserved { // the privilege to access reserved ip if db.IsAdminAllowList(userCred, ReservedipManager).Result.IsDeny() { return httperrors.NewForbiddenError("Only system admin allowed to use reserved ip") } if ReservedipManager.GetReservedIP(net, netConfig.Address, api.AddressTypeIPv4) == nil { return httperrors.NewInputParameterError("Address %s not reserved", netConfig.Address) } } else { used, err := net.isAddressUsed(ctx, netConfig.Address) if err != nil { return httperrors.NewInternalServerError("isAddressUsed fail %s", err) } if used && netConfig.Address != reuseAddr { return httperrors.NewInputParameterError("Address %s has been used", netConfig.Address) } } } if len(netConfig.Address6) > 0 { if !net.IsSupportIPv6() { return errors.Wrap(httperrors.ErrNotSupported, "network not enable ipv6") } ipAddr, err := netutils.NewIPV6Addr(netConfig.Address6) if err != nil { return errors.Wrap(err, "NewIPV6Addr") } netConfig.Address6 = ipAddr.String() if !net.IsAddress6InRange(ipAddr) { return httperrors.NewInputParameterError("Address v6 %s not in range", netConfig.Address6) } if netConfig.Reserved { // the privilege to access reserved ip if db.IsAdminAllowList(userCred, ReservedipManager).Result.IsDeny() { return httperrors.NewForbiddenError("Only system admin allowed to use reserved ip") } if ReservedipManager.GetReservedIP(net, netConfig.Address6, api.AddressTypeIPv6) == nil { return httperrors.NewInputParameterError("Address v6 %s not reserved", netConfig.Address6) } } else { used, err := net.isAddress6Used(ctx, netConfig.Address6) if err != nil { return httperrors.NewInternalServerError("isAddress6Used fail %s", err) } if used && netConfig.Address6 != reuseAddr6 { return httperrors.NewInputParameterError("v6 address %s has been used", netConfig.Address6) } } } if netConfig.RequireIPv6 && !net.IsSupportIPv6() { return errors.Wrap(httperrors.ErrNotSupported, "network not enable ipv6") } if netConfig.BwLimit > api.MAX_BANDWIDTH { return httperrors.NewInputParameterError("Bandwidth limit cannot exceed %dMbps", api.MAX_BANDWIDTH) } if net.ServerType == api.NETWORK_TYPE_BAREMETAL { // not check baremetal network free address here // TODO: find better solution ? return nil } freeCnt, err := net.getFreeAddressCount() if err != nil { return httperrors.NewInternalServerError("getFreeAddressCount fail %s", err) } if reuseAddr != "" { freeCnt += 1 } if freeCnt < 1 { return httperrors.NewInputParameterError("network %s(%s) has no free addresses", net.Name, net.Id) } } /* scheduler to the check else if ! netConfig.Vip { ct, ctExit := NetworkManager.to } */ // billing update if netConfig.TxTrafficLimit > 0 || netConfig.RxTrafficLimit > 0 { if len(netConfig.BillingType) == 0 { netConfig.BillingType = billing_api.BILLING_TYPE_PREPAID } else if netConfig.BillingType != billing_api.BILLING_TYPE_PREPAID { return httperrors.NewInputParameterError("nic is limited by traffic, only support prepaid billing type") } if len(netConfig.ChargeType) == 0 { netConfig.ChargeType = billing_api.NET_CHARGE_TYPE_BY_TRAFFIC } else if netConfig.ChargeType != billing_api.NET_CHARGE_TYPE_BY_TRAFFIC { return httperrors.NewInputParameterError("nic is limited by traffic, only support charge traffic") } } else { if netConfig.BillingType == billing_api.BILLING_TYPE_PREPAID && netConfig.ChargeType == billing_api.NET_CHARGE_TYPE_BY_TRAFFIC { return httperrors.NewInputParameterError("nic traffic limited should be set for prepaid traffic billing type") } } if len(netConfig.PortMappings) != 0 { for i := range netConfig.PortMappings { if err := validatePortMapping(netConfig.PortMappings[i]); err != nil { return errors.Wrapf(err, "validate port mapping %s", jsonutils.Marshal(netConfig.PortMappings[i])) } } } return nil } func validatePortRange(portRange *api.GuestPortMappingPortRange) error { if portRange != nil { if portRange.Start > portRange.End { return httperrors.NewInputParameterError("port range start %d is large than %d", portRange.Start, portRange.End) } if portRange.Start <= api.GUEST_PORT_MAPPING_RANGE_START { return httperrors.NewInputParameterError("port range start %d <= %d", api.GUEST_PORT_MAPPING_RANGE_START, portRange.Start) } if portRange.End > api.GUEST_PORT_MAPPING_RANGE_END { return httperrors.NewInputParameterError("port range end %d > %d", api.GUEST_PORT_MAPPING_RANGE_END, portRange.End) } } return nil } func validatePort(port int, start int, end int) error { if port < start || port > end { return httperrors.NewInputParameterError("port number %d isn't within %d to %d", port, start, end) } return nil } func validatePortMapping(pm *api.GuestPortMapping) error { if err := validatePortRange(pm.HostPortRange); err != nil { return err } if pm.HostPort != nil { if err := validatePort(*pm.HostPort, api.GUEST_PORT_MAPPING_RANGE_START, api.GUEST_PORT_MAPPING_RANGE_END); err != nil { return errors.Wrap(err, "validate host_port") } } // -1 端口表示自动分配 if pm.Port != -1 { if err := validatePort(pm.Port, 1, 65535); err != nil { return errors.Wrap(err, "validate port") } } if pm.Protocol == "" { pm.Protocol = api.GuestPortMappingProtocolTCP } if !sets.NewString(string(api.GuestPortMappingProtocolUDP), string(api.GuestPortMappingProtocolTCP)).Has(string(pm.Protocol)) { return httperrors.NewInputParameterError("unsupported protocol %s", pm.Protocol) } if len(pm.RemoteIps) != 0 { for _, ip := range pm.RemoteIps { if !regutils.MatchIPAddr(ip) && !regutils.MatchCIDR(ip) { return httperrors.NewInputParameterError("invalid ip or prefix %s", ip) } } } if pm.Rule != nil { if pm.Rule.FirstPortOffset != nil { if *pm.Rule.FirstPortOffset < 0 { return httperrors.NewInputParameterError("first port offset %d is less than 0", *pm.Rule.FirstPortOffset) } } } for _, env := range pm.Envs { if env.ValueFrom != api.GuestPortMappingEnvValueFromPort && env.ValueFrom != api.GuestPortMappingEnvValueFromHostPort { return httperrors.NewInputParameterError("invalid value from %s", env.ValueFrom) } } return nil } func IsExitNetworkInfo(ctx context.Context, userCred mcclient.TokenCredential, netConfig *api.NetworkConfig) bool { if len(netConfig.Network) > 0 { netObj, _ := NetworkManager.FetchByIdOrName(ctx, userCred, netConfig.Network) net := netObj.(*SNetwork) if net.IsExitNetwork() { return true } } else if netConfig.Exit { return true } return false } func (manager *SNetworkManager) FetchCustomizeColumns( ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool, ) []api.NetworkDetails { rows := make([]api.NetworkDetails, len(objs)) virtRows := manager.SSharableVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) wireRows := manager.SWireResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) netIds := make([]string, len(objs)) for i := range rows { rows[i] = api.NetworkDetails{ SharableVirtualResourceDetails: virtRows[i], WireResourceInfo: wireRows[i], } network := objs[i].(*SNetwork) rows[i].Exit = false if network.IsExitNetwork() { rows[i].Exit = true } if network.IsClassic() { rows[i].IsClassic = true } rows[i].Ports = network.GetTotalAddressCount() rows[i].Routes = network.GetRoutes() rows[i].Schedtags = GetSchedtagsDetailsToResourceV2(network, ctx) rows[i].Dns = network.GetDNS(rows[i].Zone) rows[i].AdditionalWires = network.fetchAdditionalWires() netIds[i] = network.Id } vnics, err := manager.TotalNicCount(netIds) if err != nil { return rows } for i := range rows { rows[i].SNetworkNics, _ = vnics[netIds[i]] } return rows } func (manager *SNetworkManager) GetTotalNicCount(netIds []string) (map[string]int, error) { vnics, err := manager.TotalNicCount(netIds) if err != nil { return nil, errors.Wrapf(err, "TotalNicCount") } result := map[string]int{} for _, id := range netIds { result[id] = 0 if nics, ok := vnics[id]; ok { result[id] = nics.Total } } return result, nil } type SNetworkNics struct { Id string api.SNetworkNics } func (nm *SNetworkManager) query(manager db.IModelManager, field string, netIds []string, filter func(*sqlchemy.SQuery) *sqlchemy.SQuery) *sqlchemy.SSubQuery { q := manager.Query() if filter != nil { q = filter(q) } sq := q.SubQuery() return sq.Query( sq.Field("network_id"), sqlchemy.COUNT(field), ).In("network_id", netIds).GroupBy(sq.Field("network_id")).SubQuery() } func (nm *SNetworkManager) TotalNicCount(netIds []string) (map[string]api.SNetworkNics, error) { // guest vnic vnicSQ := nm.query(GuestnetworkManager, "vnic", netIds, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { return q.IsFalse("virtual") }) vnic4SQ := nm.query(GuestnetworkManager, "vnic4", netIds, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { return q.IsNotEmpty("ip_addr") }) vnic6SQ := nm.query(GuestnetworkManager, "vnic6", netIds, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { return q.IsNotEmpty("ip6_addr") }) // bm vnic bmSQ := nm.query(HostnetworkManager, "bm_vnic", netIds, nil) // lb vnic lbSQ := nm.query(LoadbalancernetworkManager, "lb_vnic", netIds, nil) // eip vnic eipSQ := nm.query(ElasticipManager, "eip_vnic", netIds, nil) // group vnic groupSQ := nm.query(GroupnetworkManager, "group_vnic", netIds, nil) // reserved vnics reserve4SQ := nm.query(ReservedipManager, "reserve_vnic4", netIds, filterExpiredReservedIp4s) reserve6SQ := nm.query(ReservedipManager, "reserve_vnic6", netIds, filterExpiredReservedIp6s) // rds vnics rdsSQ := nm.query(DBInstanceNetworkManager, "rds_vnic", netIds, nil) // nat vnics natSQ := nm.query(NatGatewayManager, "nat_vnic", netIds, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { return q.IsNotEmpty("ip_addr") }) // networkinterface vncis nisSQ := nm.query(NetworkinterfacenetworkManager, "networkinterface_vnic", netIds, nil) // bm reused vnics bmReusedSQ := nm.query(GuestnetworkManager, "bm_reused_vnic", netIds, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { guest := GuestManager.Query().SubQuery() bmn := HostnetworkManager.Query().SubQuery() return q.Join(guest, sqlchemy.Equals(guest.Field("id"), q.Field("guest_id"))).Join(bmn, sqlchemy.AND( sqlchemy.Equals(q.Field("ip_addr"), bmn.Field("ip_addr")), sqlchemy.Equals(guest.Field("host_id"), bmn.Field("baremetal_id")), )) }) nets := nm.Query().SubQuery() netQ := nets.Query( sqlchemy.SUM("vnics", vnicSQ.Field("vnic")), sqlchemy.SUM("vnics4", vnic4SQ.Field("vnic4")), sqlchemy.SUM("vnics6", vnic6SQ.Field("vnic6")), sqlchemy.SUM("bm_vnics", bmSQ.Field("bm_vnic")), sqlchemy.SUM("lb_vnics", lbSQ.Field("lb_vnic")), sqlchemy.SUM("eip_vnics", eipSQ.Field("eip_vnic")), sqlchemy.SUM("group_vnics", groupSQ.Field("group_vnic")), sqlchemy.SUM("reserve_vnics4", reserve4SQ.Field("reserve_vnic4")), sqlchemy.SUM("reserve_vnics6", reserve6SQ.Field("reserve_vnic6")), sqlchemy.SUM("rds_vnics", rdsSQ.Field("rds_vnic")), sqlchemy.SUM("nat_vnics", natSQ.Field("nat_vnic")), sqlchemy.SUM("networkinterface_vnics", nisSQ.Field("networkinterface_vnic")), sqlchemy.SUM("bm_reused_vnics", bmReusedSQ.Field("bm_reused_vnic")), ) netQ.AppendField(netQ.Field("id")) netQ = netQ.LeftJoin(vnicSQ, sqlchemy.Equals(netQ.Field("id"), vnicSQ.Field("network_id"))) netQ = netQ.LeftJoin(vnic4SQ, sqlchemy.Equals(netQ.Field("id"), vnic4SQ.Field("network_id"))) netQ = netQ.LeftJoin(vnic6SQ, sqlchemy.Equals(netQ.Field("id"), vnic6SQ.Field("network_id"))) netQ = netQ.LeftJoin(bmSQ, sqlchemy.Equals(netQ.Field("id"), bmSQ.Field("network_id"))) netQ = netQ.LeftJoin(lbSQ, sqlchemy.Equals(netQ.Field("id"), lbSQ.Field("network_id"))) netQ = netQ.LeftJoin(eipSQ, sqlchemy.Equals(netQ.Field("id"), eipSQ.Field("network_id"))) netQ = netQ.LeftJoin(groupSQ, sqlchemy.Equals(netQ.Field("id"), groupSQ.Field("network_id"))) netQ = netQ.LeftJoin(reserve4SQ, sqlchemy.Equals(netQ.Field("id"), reserve4SQ.Field("network_id"))) netQ = netQ.LeftJoin(reserve6SQ, sqlchemy.Equals(netQ.Field("id"), reserve6SQ.Field("network_id"))) netQ = netQ.LeftJoin(rdsSQ, sqlchemy.Equals(netQ.Field("id"), rdsSQ.Field("network_id"))) netQ = netQ.LeftJoin(natSQ, sqlchemy.Equals(netQ.Field("id"), natSQ.Field("network_id"))) netQ = netQ.LeftJoin(nisSQ, sqlchemy.Equals(netQ.Field("id"), nisSQ.Field("network_id"))) netQ = netQ.LeftJoin(bmReusedSQ, sqlchemy.Equals(netQ.Field("id"), bmReusedSQ.Field("network_id"))) netQ = netQ.Filter(sqlchemy.In(netQ.Field("id"), netIds)).GroupBy(netQ.Field("id")) nics := []SNetworkNics{} err := netQ.All(&nics) if err != nil { return nil, errors.Wrapf(err, "netQ.All") } result := map[string]api.SNetworkNics{} for i := range nics { nics[i].SumTotal() result[nics[i].Id] = nics[i].SNetworkNics } return result, nil } // 预留IP // 预留的IP不会被调度使用 func (snet *SNetwork) PerformReserveIp(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworkReserveIpInput) (jsonutils.JSONObject, error) { if len(input.Ips) == 0 { return nil, httperrors.NewMissingParameterError("ips") } var duration time.Duration if len(input.Duration) > 0 { bc, err := billing.ParseBillingCycle(input.Duration) if err != nil { return nil, httperrors.NewInputParameterError("Duration %s invalid", input.Duration) } duration = bc.Duration() } errs := make([]error, 0) for _, ip := range input.Ips { err := snet.reserveIpWithDurationAndStatus(ctx, userCred, ip, input.Notes, duration, input.Status) if err != nil { errs = append(errs, errors.Wrap(err, "reserveIpWithDurationAndStatus")) } } if len(errs) > 0 { return nil, errors.NewAggregate(errs) } return nil, nil } func (net *SNetwork) reserveIpWithDuration(ctx context.Context, userCred mcclient.TokenCredential, ipstr string, notes string, duration time.Duration) error { return net.reserveIpWithDurationAndStatus(ctx, userCred, ipstr, notes, duration, "") } func (net *SNetwork) reserveIpWithDurationAndStatus(ctx context.Context, userCred mcclient.TokenCredential, ipstr string, notes string, duration time.Duration, status string) error { var used bool var addrType api.TAddressType if regutils.MatchIP6Addr(ipstr) { addr6, err := netutils.NewIPV6Addr(ipstr) if err != nil { return httperrors.NewInputParameterError("not a valid ipv6 address %s: %s", ipstr, err) } if !net.IsSupportIPv6() { return httperrors.NewInputParameterError("network is not ipv6 enabled") } if !net.IsAddress6InRange(addr6) { return httperrors.NewInputParameterError("Address %s not in network", ipstr) } used, err = net.isAddress6Used(ctx, addr6.String()) if err != nil { return httperrors.NewInternalServerError("isAddress6Used fail %s", err) } addrType = api.AddressTypeIPv6 ipstr = addr6.String() } else if regutils.MatchIP4Addr(ipstr) { ipAddr, err := netutils.NewIPV4Addr(ipstr) if err != nil { return httperrors.NewInputParameterError("not a valid ip address %s: %s", ipstr, err) } if !net.IsAddressInRange(ipAddr) { return httperrors.NewInputParameterError("Address %s not in network", ipstr) } used, err = net.isAddressUsed(ctx, ipstr) if err != nil { return httperrors.NewInternalServerError("isAddressUsed fail %s", err) } addrType = api.AddressTypeIPv4 } else { return errors.Wrapf(httperrors.ErrInvalidFormat, "ip %s is neither ipv4 nor ipv6", ipstr) } if used { rip := ReservedipManager.getReservedIP(net, ipstr, addrType) if rip != nil { err := rip.extendWithDuration(notes, duration, status) if err != nil { return errors.Wrap(err, "extendWithDuration") } return nil } return httperrors.NewConflictError("Address %s has been used", ipstr) } err := ReservedipManager.ReserveIPWithDurationAndStatus(ctx, userCred, net, ipstr, notes, duration, status, addrType) if err != nil { return errors.Wrap(err, "ReservedipManager.ReserveIPWithDurationAndStatus") } return nil } // 释放预留IP func (snet *SNetwork) PerformReleaseReservedIp(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworkReleaseReservedIpInput) (jsonutils.JSONObject, error) { if len(input.Ip) == 0 { return nil, httperrors.NewMissingParameterError("ip") } addrType := api.AddressTypeIPv4 if regutils.MatchIP6Addr(input.Ip) { addr, err := netutils.NewIPV6Addr(input.Ip) if err != nil { return nil, errors.Wrap(httperrors.ErrInvalidFormat, err.Error()) } input.Ip = addr.String() } rip := ReservedipManager.getReservedIP(snet, input.Ip, addrType) if rip == nil { return nil, httperrors.NewInvalidStatusError("Address %s not reserved", input.Ip) } rip.Release(ctx, userCred, snet) return nil, nil } func (snet *SNetwork) GetDetailsReservedIps(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { rips := ReservedipManager.GetReservedIPs(snet) if rips == nil { return nil, httperrors.NewInternalServerError("get reserved ip error") } ripV4 := make([]string, 0) ripV6 := make([]string, 0) for i := 0; i < len(rips); i += 1 { if len(rips[i].IpAddr) > 0 { ripV4 = append(ripV4, rips[i].IpAddr) } if len(rips[i].Ip6Addr) > 0 { ripV6 = append(ripV6, rips[i].Ip6Addr) } } ret := jsonutils.NewDict() if len(ripV4) > 0 { ret.Add(jsonutils.Marshal(ripV4), "reserved_ips") } if len(ripV6) > 0 { ret.Add(jsonutils.Marshal(ripV6), "reserved_ip6s") } return ret, nil } func isValidIpv4MaskLen(maskLen int8) bool { if maskLen < 12 || maskLen > 30 { return false } else { return true } } func isValidIpv6MaskLen(maskLen uint8) bool { if maskLen < 48 || maskLen > 126 { return false } else { return true } } func (snet *SNetwork) ensureIfnameHint() { if snet.IfnameHint != "" { return } hint, err := NetworkManager.newIfnameHint(snet.Name) if err != nil { panic(errors.Wrap(err, "ensureIfnameHint: allocate hint")) } _, err = db.Update(snet, func() error { snet.IfnameHint = hint return nil }) if err != nil { panic(errors.Wrap(err, "ensureIfnameHint: db update")) } log.Infof("network %s(%s): initialized ifname hint: %s", snet.Name, snet.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') } isn := func(c byte) bool { return (c >= '0' && c <= '9') } sani := func(r string) string { if r != "" && !isa(r[0]) { r = "a" + r } if len(r) > MAX_HINT_LEN { r = r[:MAX_HINT_LEN] } return r } rand := func(base string) (string, error) { if len(base) > HINT_BASE_LEN { base = base[:HINT_BASE_LEN] } for i := 0; i < 3; i++ { r := sani(base + rand.String(HINT_RAND_LEN)) cnt, err := manager.Query().Equals("ifname_hint", r).CountWithError() if err == nil && cnt == 0 { return r, nil } } /* generate ifname by ifname hint failed * try generate from rand string */ for i := 0; i < 3; i++ { r := sani(rand.String(MAX_HINT_LEN)) cnt, err := manager.Query().Equals("ifname_hint", r).CountWithError() if err == nil && cnt == 0 { return r, nil } } return "", fmt.Errorf("failed finding ifname hint") } r := "" for i := range hint { c := hint[i] if isa(c) || isn(c) || c == '_' { r += string(c) } } r = sani(r) if len(r) < 3 { return rand(r) } if cnt, err := manager.Query().Equals("ifname_hint", r).CountWithError(); err != nil { return "", err } else if cnt > 0 { return rand(r) } return r, nil } func (manager *SNetworkManager) validateEnsureWire(ctx context.Context, userCred mcclient.TokenCredential, input api.NetworkCreateInput) (w *SWire, v *SVpc, cr *SCloudregion, err error) { wObj, err := WireManager.FetchByIdOrName(ctx, userCred, input.Wire) if err != nil { err = errors.Wrapf(err, "wire %s", input.Wire) return } w = wObj.(*SWire) v, _ = w.GetVpc() crObj, err := CloudregionManager.FetchById(v.CloudregionId) if err != nil { err = errors.Wrapf(err, "cloudregion %s", v.CloudregionId) return } cr = crObj.(*SCloudregion) return } func (manager *SNetworkManager) validateEnsureZoneVpc(ctx context.Context, userCred mcclient.TokenCredential, input api.NetworkCreateInput) (*SWire, *SVpc, *SCloudregion, error) { zObj, err := validators.ValidateModel(ctx, userCred, ZoneManager, &input.Zone) if err != nil { return nil, nil, nil, err } z := zObj.(*SZone) vObj, err := validators.ValidateModel(ctx, userCred, VpcManager, &input.Vpc) if err != nil { return nil, nil, nil, err } v := vObj.(*SVpc) cr, err := z.GetRegion() if err != nil { return nil, nil, nil, err } // 华为云,ucloud wire zone_id 为空 var wires []SWire if utils.IsInStringArray(cr.Provider, api.REGIONAL_NETWORK_PROVIDERS) { wires, err = WireManager.getWiresByVpcAndZone(v, nil) } else { wires, err = WireManager.getWiresByVpcAndZone(v, z) } if err != nil { return nil, nil, nil, err } if len(wires) > 1 { return nil, nil, nil, httperrors.NewConflictError("found %d wires for zone %s and vpc %s", len(wires), input.Zone, input.Vpc) } if len(wires) == 1 { return &wires[0], v, cr, nil } externalId := "" if cr.Provider == api.CLOUD_PROVIDER_CLOUDPODS { iVpc, err := v.GetIVpc(ctx) if err != nil { return nil, nil, nil, err } iWire, err := iVpc.CreateIWire(&cloudprovider.SWireCreateOptions{ Name: fmt.Sprintf("vpc-%s", v.Name), ZoneId: z.ExternalId, Bandwidth: 10000, Mtu: 1500, }) if err != nil { return nil, nil, nil, errors.Wrapf(err, "CreateIWire") } externalId = iWire.GetGlobalId() } // wire not found. We auto create one for OneCloud vpc if cr.Provider == api.CLOUD_PROVIDER_ONECLOUD || cr.Provider == api.CLOUD_PROVIDER_CLOUDPODS { w, err := v.initWire(ctx, z, externalId) if err != nil { return nil, nil, nil, errors.Wrapf(err, "vpc %s init wire", v.Id) } return w, v, cr, nil } return nil, nil, nil, httperrors.NewNotFoundError("wire not found for zone %s and vpc %s", input.Zone, input.Vpc) } func (manager *SNetworkManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.NetworkCreateInput) (api.NetworkCreateInput, error) { if input.ServerType == "" { input.ServerType = api.NETWORK_TYPE_GUEST } else if !api.IsInNetworkTypes(input.ServerType, api.ALL_NETWORK_TYPES) { return input, httperrors.NewInputParameterError("Invalid server_type: %s", input.ServerType) } var ( wire *SWire vpc *SVpc region *SCloudregion err error ) if input.WireId != "" { input.Wire = input.WireId } if input.Wire != "" { wire, vpc, region, err = manager.validateEnsureWire(ctx, userCred, input) if err != nil { return input, err } } else if input.Zone != "" && input.Vpc != "" { wire, vpc, region, err = manager.validateEnsureZoneVpc(ctx, userCred, input) if err != nil { return input, err } } else { return input, httperrors.NewInputParameterError("zone and vpc info required when wire is absent") } { if wire.Id == api.DEFAULT_HOST_LOCAL_WIRE_ID { input.ServerType = api.NETWORK_TYPE_HOSTLOCAL } } input.WireId = wire.Id if vpc.Status != api.VPC_STATUS_AVAILABLE { return input, httperrors.NewInvalidStatusError("VPC not ready") } if input.ServerType == api.NETWORK_TYPE_EIP && vpc.Id != api.DEFAULT_VPC_ID { return input, httperrors.NewInputParameterError("eip network can only exist in default vpc, got %s(%s)", vpc.Name, vpc.Id) } // if input.ServerType != api.NETWORK_TYPE_EIP { // input.BgpType = "" // } // check class metadata { // wire != nil var projectId string if len(input.ProjectId) > 0 { projectId = input.ProjectId } else { projectId = ownerId.GetProjectId() } project, err := db.TenantCacheManager.FetchTenantById(ctx, projectId) if err != nil { return input, errors.Wrapf(err, "unable to fetch tenant by id %s", projectId) } ok, err := db.IsInSameClass(ctx, wire, project) if err != nil { return input, errors.Wrapf(err, "unable to check if wire and project is in same class") } if !ok { return input, httperrors.NewForbiddenError("the wire %s and project %s has different class metadata", wire.GetName(), project.GetName()) } } if vpc.Id != api.DEFAULT_VPC_ID { // require prefix if len(input.GuestIpPrefix) == 0 && len(input.GuestIp6Start) > 0 && len(input.GuestIp6End) > 0 { return input, errors.Wrap(httperrors.ErrInputParameter, "guest_ip_prefix is required") } if len(input.GuestIp6Prefix) == 0 && len(input.GuestIp6Start) > 0 && len(input.GuestIp6End) > 0 { return input, errors.Wrap(httperrors.ErrInputParameter, "guest_ip6_prefix is required") } } { defaultVlanId := 1 if input.VlanId == nil { input.VlanId = &defaultVlanId } else if *input.VlanId < 1 { input.VlanId = &defaultVlanId } if *input.VlanId > 4095 { return input, httperrors.NewInputParameterError("valid vlan id") } } { if len(input.IfnameHint) == 0 { input.IfnameHint = input.Name } var err error input.IfnameHint, err = manager.newIfnameHint(input.IfnameHint) if err != nil { return input, httperrors.NewBadRequestError("cannot derive valid ifname hint: %v", err) } } var ( ipRange *netutils.IPV4AddrRange masklen int8 netAddr netutils.IPV4Addr ) if len(input.GuestIpPrefix) > 0 { prefix, err := netutils.NewIPV4Prefix(input.GuestIpPrefix) if err != nil { return input, httperrors.NewInputParameterError("ip_prefix error: %s", err) } ipRangeTmp := prefix.ToIPRange() ipRange = &ipRangeTmp masklen = prefix.MaskLen netAddr = prefix.Address.NetAddr(masklen) input.GuestIpMask = prefix.MaskLen if !isValidIpv4MaskLen(masklen) { return input, httperrors.NewInputParameterError("subnet masklen should be smaller than 30") } // 根据掩码得到合法的GuestIpPrefix input.GuestIpPrefix = prefix.String() if region.Provider == api.CLOUD_PROVIDER_ONECLOUD && vpc.Id != api.DEFAULT_VPC_ID { // reserve addresses for onecloud vpc networks gateway := netAddr.StepUp() brdAddr := gateway.BroadcastAddr(masklen) // NOTE // // - reserve the 1st addr as gateway // - reserve the last ip for broadcasting // - reserve the 2nd-to-last for possible future use // // We do not allow split 192.168.1.0/24 into multiple ranges // like // // - 192.168.1.50-192.168.1.100, // - 192.168.1.100-192.168.1.200 // // This could complicate gateway setting and topology // management without much benefit to end users ipStart := gateway.StepUp() ipEnd := brdAddr.StepDown().StepDown() ipRange.Set(ipStart, ipEnd) input.GuestGateway = gateway.String() } } else if len(input.GuestIpStart) > 0 && len(input.GuestIpEnd) > 0 { if !isValidIpv4MaskLen(input.GuestIpMask) { return input, httperrors.NewInputParameterError("Invalid masklen %d", input.GuestIpMask) } ipStart, err := netutils.NewIPV4Addr(input.GuestIpStart) if err != nil { return input, httperrors.NewInputParameterError("Invalid start ip: %s %s", input.GuestIpStart, err) } ipEnd, err := netutils.NewIPV4Addr(input.GuestIpEnd) if err != nil { return input, httperrors.NewInputParameterError("invalid end ip: %s %s", input.GuestIpEnd, err) } ipRangeTpm := netutils.NewIPV4AddrRange(ipStart, ipEnd) ipRange = &ipRangeTpm masklen = input.GuestIpMask netAddr = ipStart.NetAddr(masklen) if ipEnd.NetAddr(masklen) != netAddr { return input, httperrors.NewInputParameterError("start and end ip not in the same subnet") } if wire.Id == api.DEFAULT_HOST_LOCAL_WIRE_ID && len(input.GuestGateway) == 0 { return input, httperrors.NewInputParameterError("host local wire only support gateway ip") } } var ( ipRange6 *netutils.IPV6AddrRange masklen6 uint8 netAddr6 netutils.IPV6Addr ) if len(input.GuestIp6Prefix) > 0 { prefix, err := netutils.NewIPV6Prefix(input.GuestIp6Prefix) if err != nil { return input, httperrors.NewInputParameterError("ip_prefix error: %s", err) } ipRange6tmp := prefix.ToIPRange() ipRange6 = &ipRange6tmp masklen6 = prefix.MaskLen netAddr6 = prefix.Address.NetAddr(masklen6) input.GuestIp6Mask = prefix.MaskLen if !isValidIpv6MaskLen(masklen6) { return input, httperrors.NewInputParameterError("ipv6 subnet masklen should be between 48~126") } // 根据掩码得到合法的GuestIp6Prefix input.GuestIp6Prefix = prefix.String() if region.Provider == api.CLOUD_PROVIDER_ONECLOUD && vpc.Id != api.DEFAULT_VPC_ID { // reserve addresses for onecloud vpc networks gateway := netAddr6.StepUp() brdAddr := gateway.BroadcastAddr(masklen6) // NOTE // // - reserve the 1st addr as gateway // - reserve the last ip for broadcasting // - reserve the 2nd-to-last for possible future use // // This could complicate gateway setting and topology // management without much benefit to end users ipStart := gateway.StepUp() ipEnd := brdAddr.StepDown().StepDown() ipRange6.Set(ipStart, ipEnd) input.GuestGateway6 = gateway.String() } } else if len(input.GuestIp6Start) > 0 && len(input.GuestIp6End) > 0 { if !isValidIpv6MaskLen(input.GuestIp6Mask) { return input, httperrors.NewInputParameterError("Invalid ipv6 masklen %d", input.GuestIp6Mask) } ip6Start, err := netutils.NewIPV6Addr(input.GuestIp6Start) if err != nil { return input, httperrors.NewInputParameterError("Invalid start v6 ip: %s %s", input.GuestIp6Start, err) } ip6End, err := netutils.NewIPV6Addr(input.GuestIp6End) if err != nil { return input, httperrors.NewInputParameterError("invalid end v6 ip: %s %s", input.GuestIp6End, err) } ipRange6tmp := netutils.NewIPV6AddrRange(ip6Start, ip6End) ipRange6 = &ipRange6tmp masklen6 = input.GuestIp6Mask netAddr6 = ip6Start.NetAddr(masklen6) if !ip6End.NetAddr(masklen6).Equals(netAddr6) { return input, httperrors.NewInputParameterError("v6 start and end ip not in the same subnet") } if wire.Id == api.DEFAULT_HOST_LOCAL_WIRE_ID && len(input.GuestGateway6) == 0 { return input, httperrors.NewInputParameterError("host local wire only support gateway v6 ip") } } if ipRange == nil && ipRange6 == nil { return input, httperrors.NewBadRequestError("No valid ipv4/ipv6 address input") } // do not set default dns // if len(input.GuestDns) == 0 { // input.GuestDns = options.Options.DNSServer // } for key, ipStr := range map[string]string{ "guest_gateway": input.GuestGateway, "guest_dns": input.GuestDns, "guest_dhcp": input.GuestDHCP, "guest_ntp": input.GuestNtp, "guest_gateway6": input.GuestGateway6, } { if ipStr == "" { continue } if key == "guest_dhcp" || key == "guest_dns" { ipList := strings.Split(ipStr, ",") for _, ipstr := range ipList { if !regutils.MatchIPAddr(ipstr) { return input, httperrors.NewInputParameterError("%s: Invalid IP address %s", key, ipstr) } } } else if key == "guest_ntp" { ipList := strings.Split(ipStr, ",") for _, ipstr := range ipList { if !regutils.MatchDomainName(ipstr) && !regutils.MatchIPAddr(ipstr) { return input, httperrors.NewInputParameterError("%s: Invalid domain name or IP address %s", key, ipstr) } } } else if !regutils.MatchIPAddr(ipStr) { return input, httperrors.NewInputParameterError("%s: Invalid IP address %s", key, ipStr) } } if input.GuestGateway != "" { addr, err := netutils.NewIPV4Addr(input.GuestGateway) if err != nil { return input, httperrors.NewInputParameterError("bad gateway ip: %v", err) } if addr.NetAddr(masklen) != netAddr { return input, httperrors.NewInputParameterError("gateway ip must be in the same subnet as start, end ip") } } if input.GuestGateway6 != "" { addr, err := netutils.NewIPV6Addr(input.GuestGateway6) if err != nil { return input, httperrors.NewInputParameterError("bad v6 gateway ip: %v", err) } if !addr.NetAddr(masklen6).Equals(netAddr6) { return input, httperrors.NewInputParameterError("ipv6 gateway ip must be in the same subnet as start, end v6 ip") } } { if ipRange != nil && !vpc.containsIPV4Range(*ipRange) { return input, httperrors.NewInputParameterError("Network not in range of VPC cidrblock %s", vpc.CidrBlock) } if ipRange6 != nil { if !vpc.containsIPV6Range(*ipRange6) { return input, httperrors.NewInputParameterError("Network not in range of VPC ipv6 cidrblock %s", vpc.CidrBlock6) } } } { provider := wire.GetProviderName() nets, err := vpc.GetNetworksByProvider(provider) if err != nil { return input, httperrors.NewInternalServerError("fail to GetNetworks of vpc: %v", err) } if ipRange != nil && isOverlapNetworks(nets, *ipRange) { return input, httperrors.NewInputParameterError("Conflict address space with existing networks in vpc %q", vpc.GetName()) } if ipRange6 != nil && isOverlapNetworks6(nets, *ipRange6) { return input, httperrors.NewInputParameterError("Conflict address space with existing networks in vpc %q", vpc.GetName()) } } if ipRange != nil { input.GuestIpStart = ipRange.StartIp().String() input.GuestIpEnd = ipRange.EndIp().String() } if ipRange6 != nil { // validate v6 ip addr input.GuestIp6Start = ipRange6.StartIp().String() input.GuestIp6End = ipRange6.EndIp().String() } input.SharableVirtualResourceCreateInput, err = manager.SSharableVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.SharableVirtualResourceCreateInput) if err != nil { return input, err } return input, nil } func (snet *SNetwork) validateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.NetworkUpdateInput) (api.NetworkUpdateInput, error) { var ( netAddr netutils.IPV4Addr masklen int8 netAddr6 netutils.IPV6Addr masklen6 uint8 err error ) if input.GuestIpMask > 0 { if !snet.isManaged() && !isValidIpv4MaskLen(input.GuestIpMask) { return input, httperrors.NewInputParameterError("Invalid masklen %d", input.GuestIpMask) } masklen = input.GuestIpMask } else { masklen = snet.GuestIpMask } if input.GuestIp6Mask != nil && *input.GuestIp6Mask > 0 { if !snet.isManaged() && !isValidIpv6MaskLen(*input.GuestIp6Mask) { return input, httperrors.NewInputParameterError("Invalid ipv6 masklen %d", *input.GuestIp6Mask) } masklen6 = *input.GuestIp6Mask } else { masklen6 = snet.GuestIp6Mask } if input.GuestIpStart != "" || input.GuestIpEnd != "" { var ( startIp netutils.IPV4Addr endIp netutils.IPV4Addr ) if input.GuestIpStart != "" { startIp, err = netutils.NewIPV4Addr(input.GuestIpStart) if err != nil { return input, httperrors.NewInputParameterError("Invalid start ip: %s %s", input.GuestIpStart, err) } } else { startIp, _ = netutils.NewIPV4Addr(snet.GuestIpStart) } if input.GuestIpEnd != "" { endIp, err = netutils.NewIPV4Addr(input.GuestIpEnd) if err != nil { return input, httperrors.NewInputParameterError("invalid end ip: %s %s", input.GuestIpEnd, err) } } else { endIp, _ = netutils.NewIPV4Addr(snet.GuestIpEnd) } netRange := netutils.NewIPV4AddrRange(startIp, endIp) nets := NetworkManager.getAllNetworks(snet.WireId, snet.Id) if nets == nil { return input, httperrors.NewInternalServerError("query all networks fail") } if isOverlapNetworks(nets, netRange) { return input, httperrors.NewInputParameterError("Conflict address space with existing networks") } vpc, _ := snet.GetVpc() if !vpc.containsIPV4Range(netRange) { return input, httperrors.NewInputParameterError("Network not in range of VPC cidrblock %s", vpc.CidrBlock) } usedMap := snet.GetUsedAddresses(ctx) for usedIpStr := range usedMap { if usedIp, err := netutils.NewIPV4Addr(usedIpStr); err == nil && !netRange.Contains(usedIp) { return input, httperrors.NewInputParameterError("Address %s been assigned out of new range", usedIpStr) } } input.GuestIpStart = netRange.StartIp().String() input.GuestIpEnd = netRange.EndIp().String() netAddr = startIp.NetAddr(masklen) if endIp.NetAddr(masklen) != netAddr { return input, httperrors.NewInputParameterError("start, end ip must be in the same subnet") } if snet.WireId == api.DEFAULT_HOST_LOCAL_WIRE_ID && input.GuestGateway != nil && len(*input.GuestGateway) == 0 { return input, httperrors.NewInputParameterError("host local wire only support gateway ip") } } else { startIp, _ := netutils.NewIPV4Addr(snet.GuestIpStart) netAddr = startIp.NetAddr(masklen) } if (input.GuestIp6Start != nil && len(*input.GuestIp6Start) > 0) || (input.GuestIp6End != nil && len(*input.GuestIp6End) > 0) { var ( startIp netutils.IPV6Addr endIp netutils.IPV6Addr ) if input.GuestIp6Start != nil && len(*input.GuestIp6Start) > 0 { startIp, err = netutils.NewIPV6Addr(*input.GuestIp6Start) if err != nil { return input, httperrors.NewInputParameterError("Invalid start v6 ip: %s %s", *input.GuestIp6Start, err) } } else if len(snet.GuestIp6Start) > 0 { startIp, _ = netutils.NewIPV6Addr(snet.GuestIp6Start) } else { return input, httperrors.NewInputParameterError("no start v6 ip") } if input.GuestIp6End != nil && len(*input.GuestIp6End) > 0 { endIp, err = netutils.NewIPV6Addr(*input.GuestIp6End) if err != nil { return input, httperrors.NewInputParameterError("invalid end v6 ip: %s %s", *input.GuestIp6End, err) } } else if len(snet.GuestIp6End) > 0 { endIp, _ = netutils.NewIPV6Addr(snet.GuestIp6End) } else { return input, httperrors.NewInputParameterError("no end v6 ip") } netRange := netutils.NewIPV6AddrRange(startIp, endIp) nets := NetworkManager.getAllNetworks(snet.WireId, snet.Id) if nets == nil { return input, httperrors.NewInternalServerError("query all networks fail") } if isOverlapNetworks6(nets, netRange) { return input, httperrors.NewInputParameterError("Conflict v6 address space with existing networks") } vpc, _ := snet.GetVpc() if !vpc.containsIPV6Range(netRange) { return input, httperrors.NewInputParameterError("Network not in range of VPC v6 cidrblock %s", vpc.CidrBlock6) } usedMap := snet.GetUsedAddresses6(ctx) for usedIpStr := range usedMap { if usedIp, err := netutils.NewIPV6Addr(usedIpStr); err == nil && !netRange.Contains(usedIp) { return input, httperrors.NewInputParameterError("v6 address %s been assigned out of new range", usedIpStr) } } startIpStr := netRange.StartIp().String() endIpStr := netRange.EndIp().String() input.GuestIp6Start = &startIpStr input.GuestIp6End = &endIpStr netAddr6 = netRange.StartIp().NetAddr(masklen6) if !netRange.EndIp().NetAddr(masklen6).Equals(netAddr6) { return input, httperrors.NewInputParameterError("start, end v6 ip must be in the same subnet") } if snet.WireId == api.DEFAULT_HOST_LOCAL_WIRE_ID && input.GuestGateway6 != nil && len(*input.GuestGateway6) == 0 { return input, httperrors.NewInputParameterError("host local wire only support gateway ip") } } else if len(snet.GuestIp6Start) > 0 { startIp, _ := netutils.NewIPV6Addr(snet.GuestIp6Start) netAddr6 = startIp.NetAddr(masklen6) } for key, ipStr := range map[string]*string{ "guest_gateway": input.GuestGateway, "guest_dns": input.GuestDns, "guest_dhcp": input.GuestDhcp, "guest_ntp": input.GuestNtp, "guest_gateway6": input.GuestGateway6, } { if ipStr == nil || len(*ipStr) == 0 { continue } if key == "guest_dhcp" || key == "guest_dns" { ipList := strings.Split(*ipStr, ",") for _, ipstr := range ipList { if !regutils.MatchIPAddr(ipstr) { return input, httperrors.NewInputParameterError("%s: Invalid IP address %s", key, ipstr) } } } else if key == "guest_ntp" { ipList := strings.Split(*ipStr, ",") for _, ipstr := range ipList { if !regutils.MatchDomainName(ipstr) && !regutils.MatchIPAddr(ipstr) { return input, httperrors.NewInputParameterError("%s: Invalid domain name or IP address %s", key, ipstr) } } } else if !regutils.MatchIPAddr(*ipStr) { return input, httperrors.NewInputParameterError("%s: Invalid IP address %s", key, *ipStr) } } if input.GuestGateway != nil && len(*input.GuestGateway) > 0 { addr, err := netutils.NewIPV4Addr(*input.GuestGateway) if err != nil { return input, httperrors.NewInputParameterError("bad gateway ip: %v", err) } if addr.NetAddr(masklen) != netAddr { return input, httperrors.NewInputParameterError("gateway ip must be in the same subnet as start, end ip") } } if input.GuestGateway6 != nil && len(*input.GuestGateway6) > 0 { addr, err := netutils.NewIPV6Addr(*input.GuestGateway6) if err != nil { return input, httperrors.NewInputParameterError("bad ipv6 gateway ip: %v", err) } if !addr.NetAddr(masklen6).Equals(netAddr6) { return input, httperrors.NewInputParameterError("ipv6 gateway ip must be in the same subnet as start, end v6 ip") } } if input.IsAutoAlloc != nil && *input.IsAutoAlloc { if snet.ServerType != api.NETWORK_TYPE_GUEST && snet.ServerType != api.NETWORK_TYPE_HOSTLOCAL { return input, httperrors.NewInputParameterError("network server_type %s not support auto alloc", snet.ServerType) } } if len(input.ServerType) > 0 { if !api.IsInNetworkTypes(input.ServerType, api.ALL_NETWORK_TYPES) { return input, errors.Wrapf(httperrors.ErrInputParameter, "invalid server_type %q", input.ServerType) } if snet.WireId == api.DEFAULT_HOST_LOCAL_WIRE_ID && input.ServerType != api.NETWORK_TYPE_HOSTLOCAL { return input, httperrors.NewInputParameterError("host local wire only support hostlocal server_type") } } return input, nil } func (snet *SNetwork) ValidateUpdateData( ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.NetworkUpdateInput, ) (api.NetworkUpdateInput, error) { if snet.isManaged() { // managed network, not allow to update input.GuestIpStart = "" input.GuestIpEnd = "" input.GuestIpMask = 0 input.GuestGateway = nil input.GuestDns = nil input.GuestDomain = nil input.GuestDhcp = nil input.GuestNtp = nil input.GuestIp6Start = nil input.GuestIp6End = nil input.GuestIp6Mask = nil input.GuestGateway6 = nil } var err error input, err = snet.validateUpdateData(ctx, userCred, query, input) if err != nil { return input, errors.Wrap(err, "validateUpdateData") } input.SharableVirtualResourceBaseUpdateInput, err = snet.SSharableVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, input.SharableVirtualResourceBaseUpdateInput) if err != nil { return input, errors.Wrap(err, "SSharableVirtualResourceBase.ValidateUpdateData") } return input, nil } func (manager *SNetworkManager) getAllNetworks(wireId, excludeId string) []SNetwork { nets := make([]SNetwork, 0) q := manager.Query().Equals("wire_id", wireId) if len(excludeId) > 0 { q = q.NotEquals("id", excludeId) } err := db.FetchModelObjects(manager, q, &nets) if err != nil { log.Errorf("getAllNetworks fail %s", err) return nil } return nets } func isOverlapNetworksAddr(nets []SNetwork, startIp, endIp netutils.IPV4Addr) bool { ipRange := netutils.NewIPV4AddrRange(startIp, endIp) for i := 0; i < len(nets); i += 1 { ipRange2 := nets[i].getIPRange() if ipRange2 != nil && ipRange2.IsOverlap(ipRange) { return true } } return false } func isOverlapNetworks(nets []SNetwork, ipRange netutils.IPV4AddrRange) bool { for i := 0; i < len(nets); i += 1 { ipRange2 := nets[i].getIPRange() if ipRange2 != nil && ipRange2.IsOverlap(ipRange) { return true } } return false } func isOverlapNetworks6(nets []SNetwork, ipRange netutils.IPV6AddrRange) bool { for i := 0; i < len(nets); i += 1 { ipRange2 := nets[i].getIPRange6() if ipRange2 != nil && ipRange2.IsOverlap(ipRange) { return true } } return false } func (snet *SNetwork) IsManaged() bool { wire, _ := snet.GetWire() if wire == nil { return false } return wire.IsManaged() } func (snet *SNetwork) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error { if !data.Contains("public_scope") { if snet.ServerType == api.NETWORK_TYPE_GUEST && !snet.IsManaged() { wire, _ := snet.GetWire() if db.IsAdminAllowPerform(ctx, userCred, snet, "public") && ownerId.GetProjectDomainId() == userCred.GetProjectDomainId() && wire != nil && wire.IsPublic && wire.PublicScope == string(rbacscope.ScopeSystem) { snet.SetShare(rbacscope.ScopeSystem) } else if db.IsDomainAllowPerform(ctx, userCred, snet, "public") && ownerId.GetProjectId() == userCred.GetProjectId() && consts.GetNonDefaultDomainProjects() { // only if non_default_domain_projects turned on, share to domain snet.SetShare(rbacscope.ScopeDomain) } else { snet.SetShare(rbacscope.ScopeNone) } } else { snet.SetShare(rbacscope.ScopeNone) } data.(*jsonutils.JSONDict).Set("public_scope", jsonutils.NewString(snet.PublicScope)) } return snet.SSharableVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) } func (net *SNetwork) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { net.SSharableVirtualResourceBase.PostCreate(ctx, userCred, ownerId, query, data) vpc, _ := net.GetVpc() if vpc != nil && vpc.IsManaged() { task, err := taskman.TaskManager.NewTask(ctx, "NetworkCreateTask", net, userCred, data.(*jsonutils.JSONDict), "", "", nil) if err != nil { net.SetStatus(ctx, userCred, apis.STATUS_CREATE_FAILED, err.Error()) return } task.ScheduleRun(nil) } else { { err := net.syncAdditionalWires(ctx, nil) if err != nil { log.Errorf("syncAdditionalWires error: %s", err) } } net.SetStatus(ctx, userCred, api.NETWORK_STATUS_AVAILABLE, "") if err := net.ClearSchedDescCache(); err != nil { log.Errorf("network post create clear schedcache error: %v", err) } notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{ Obj: net, Action: notifyclient.ActionCreate, }) } // reserve gateway IP { err := net.reserveGuestGateway(ctx, userCred) if err != nil { log.Errorf("reserveGuestGateway fail %s", err) } } } func (snet *SNetwork) reserveGuestGateway(ctx context.Context, userCred mcclient.TokenCredential) error { ipRange := snet.GetIPRange() if snet.GuestGateway != "" { gatewayIp, _ := netutils.NewIPV4Addr(snet.GuestGateway) if ipRange.Contains(gatewayIp) { err := snet.reserveIpWithDurationAndStatus(ctx, userCred, snet.GuestGateway, "Reserve GuestGateway IP", 0, api.RESERVEDIP_STATUS_ONLINE) if err != nil { return errors.Wrap(err, "reserveIpWithDurationAndStatus") } } } return nil } func (snet *SNetwork) GetPrefix() (netutils.IPV4Prefix, error) { addr, err := netutils.NewIPV4Addr(snet.GuestIpStart) if err != nil { return netutils.IPV4Prefix{}, err } addr = addr.NetAddr(snet.GuestIpMask) return netutils.IPV4Prefix{Address: addr, MaskLen: snet.GuestIpMask}, nil } func (snet *SNetwork) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { log.Infof("SNetwork delete do nothing") snet.SetStatus(ctx, userCred, api.NETWORK_STATUS_START_DELETE, "") return nil } func (snet *SNetwork) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { if len(snet.ExternalId) > 0 { return snet.StartDeleteNetworkTask(ctx, userCred) } else { return snet.RealDelete(ctx, userCred) } } func (snet *SNetwork) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { DeleteResourceJointSchedtags(snet, ctx, userCred) db.OpsLog.LogEvent(snet, db.ACT_DELOCATE, snet.GetShortDesc(ctx), userCred) snet.SetStatus(ctx, userCred, api.NETWORK_STATUS_DELETED, "real delete") networkinterfaces, err := snet.GetNetworkInterfaces() if err != nil { return errors.Wrap(err, "GetNetworkInterfaces") } for i := range networkinterfaces { err = networkinterfaces[i].purge(ctx, userCred) if err != nil { return errors.Wrapf(err, "networkinterface.purge %s(%s)", networkinterfaces[i].Name, networkinterfaces[i].Id) } } reservedIps := ReservedipManager.GetReservedIPs(snet) for i := range reservedIps { err = reservedIps[i].Release(ctx, userCred, snet) if err != nil { return errors.Wrapf(err, "reservedIps.Release %s/%s(%d)", reservedIps[i].IpAddr, reservedIps[i].Ip6Addr, reservedIps[i].Id) } } gns, err := snet.GetGuestnetworks() // delete virtual nics if err != nil { return errors.Wrapf(err, "GetGuestnetworks") } for i := range gns { err = gns[i].Delete(ctx, userCred) if err != nil { return errors.Wrapf(err, "delete virtual nic %s(%d)", gns[i].Ifname, gns[i].RowId) } } if err := snet.SSharableVirtualResourceBase.Delete(ctx, userCred); err != nil { return err } if err := NetworkAdditionalWireManager.DeleteNetwork(ctx, snet.Id); err != nil { return errors.Wrap(err, "NetworkAdditionalWireManager.DeleteNetwork") } snet.ClearSchedDescCache() return nil } func (snet *SNetwork) StartDeleteNetworkTask(ctx context.Context, userCred mcclient.TokenCredential) error { task, err := taskman.TaskManager.NewTask(ctx, "NetworkDeleteTask", snet, userCred, nil, "", "", nil) if err != nil { log.Errorf("Start NetworkDeleteTask fail %s", err) return err } task.ScheduleRun(nil) return nil } func (snet *SNetwork) GetINetwork(ctx context.Context) (cloudprovider.ICloudNetwork, error) { wire, err := snet.GetWire() if err != nil { return nil, errors.Wrapf(err, "GetWire") } iwire, err := wire.GetIWire(ctx) if err != nil { return nil, err } return iwire.GetINetworkById(snet.GetExternalId()) } func (snet *SNetwork) isManaged() bool { if len(snet.ExternalId) > 0 { return true } else { return false } } func (snet *SNetwork) isOneCloudVpcNetwork() bool { return IsOneCloudVpcResource(snet) } /*func parseIpToIntArray(ip string) ([]int, error) { ipSp := strings.Split(strings.Trim(ip, "."), ".") if len(ipSp) > 4 { return nil, httperrors.NewInputParameterError("Parse Ip Failed") } ipIa := []int{} for i := 0; i < len(ipSp); i++ { val, err := strconv.Atoi(ipSp[i]) if err != nil { return nil, httperrors.NewInputParameterError("Parse Ip Failed") } if val < 0 || val > 255 { return nil, httperrors.NewInputParameterError("Parse Ip Failed") } ipIa = append(ipIa, val) } return ipIa, nil }*/ // IP子网列表 func (manager *SNetworkManager) ListItemFilter( ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.NetworkListInput, ) (*sqlchemy.SQuery, error) { var err error q, err = manager.SSharableVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, input.SharableVirtualResourceListInput) if err != nil { return nil, errors.Wrap(err, "SSharableVirtualResourceBaseManager.ListItemFilter") } q, err = manager.SExternalizedResourceBaseManager.ListItemFilter(ctx, q, userCred, input.ExternalizedResourceBaseListInput) if err != nil { return nil, errors.Wrap(err, "SExternalizedResourceBaseManager.ListItemFilter") } { wireFilter := input.WireResourceInput input.Wire = "" input.WireId = "" q, err = manager.SWireResourceBaseManager.ListItemFilter(ctx, q, userCred, input.WireFilterListInput) if err != nil { return nil, errors.Wrap(err, "SWireResourceBaseManager.ListItemFilter") } if len(wireFilter.WireId) > 0 { wireObj, err := WireManager.FetchByIdOrName(ctx, userCred, wireFilter.WireId) if err != nil { if errors.Cause(err) == sql.ErrNoRows { return nil, httperrors.NewResourceNotFoundError2(WireManager.Keyword(), wireFilter.WireId) } else { return nil, errors.Wrapf(err, "WireManager.FetchByIdOrName %s", wireFilter.WireId) } } wireFilter.WireId = wireObj.GetId() wireFilter.Wire = wireObj.GetName() q = q.Filter(sqlchemy.OR( sqlchemy.Equals(q.Field("wire_id"), wireFilter.WireId), sqlchemy.In(q.Field("id"), NetworkAdditionalWireManager.networkIdQuery(wireFilter.WireId).SubQuery()), )) } input.WireResourceInput = wireFilter } if len(input.RouteTableId) > 0 { sq := RouteTableAssociationManager.Query("associated_resource_id").Equals("route_table_id", input.RouteTableId).Equals("association_type", string(cloudprovider.RouteTableAssociaToSubnet)) q = q.In("id", sq.SubQuery()) } if input.Usable != nil && *input.Usable { regions := CloudregionManager.Query("id").Equals("status", api.CLOUD_REGION_STATUS_INSERVER) zones := ZoneManager.Query("id").Equals("status", api.ZONE_ENABLE).In("cloudregion_id", regions) providerSQ := usableCloudProviders() _vpcs := VpcManager.Query("id").Equals("status", api.VPC_STATUS_AVAILABLE) vpcs := _vpcs.Filter(sqlchemy.OR( sqlchemy.In(_vpcs.Field("manager_id"), providerSQ), sqlchemy.IsNullOrEmpty(_vpcs.Field("manager_id")), )) wires := WireManager.Query("id") wires = wires.In("vpc_id", vpcs). Filter(sqlchemy.OR(sqlchemy.IsNullOrEmpty(wires.Field("zone_id")), sqlchemy.In(wires.Field("zone_id"), zones))) q = q.In("wire_id", wires).Equals("status", api.NETWORK_STATUS_AVAILABLE) } if len(input.HostType) > 0 || len(input.HostId) > 0 { classicWiresIdQ := WireManager.Query("id").Equals("vpc_id", api.DEFAULT_VPC_ID) netifs := NetInterfaceManager.Query("wire_id", "baremetal_id").SubQuery() classicWiresIdQ = classicWiresIdQ.Join(netifs, sqlchemy.Equals(netifs.Field("wire_id"), classicWiresIdQ.Field("id"))) hosts := HostManager.Query("id") if len(input.HostType) > 0 { hosts = hosts.Equals("host_type", input.HostType) } if len(input.HostId) > 0 { hosts = hosts.In("id", input.HostId) } hostsQ := hosts.SubQuery() classicWiresIdQ = classicWiresIdQ.Join(hostsQ, sqlchemy.Equals(netifs.Field("baremetal_id"), hostsQ.Field("id"))) wireIdQueries := []sqlchemy.IQuery{ classicWiresIdQ, } if input.HostType == api.HOST_TYPE_HYPERVISOR || input.HostType == api.HOST_TYPE_CONTAINER { vpcHostQ := HostManager.Query().Equals("host_type", input.HostType) if len(input.HostId) > 0 { vpcHostQ = vpcHostQ.In("id", input.HostId) } hostCnt, err := vpcHostQ.CountWithError() if err != nil { return nil, errors.Wrap(err, "vpcHostQ.CountWithError") } if hostCnt > 0 { // should consider host local wire hostLocalWireQ := WireManager.Query("id").Equals("id", api.DEFAULT_HOST_LOCAL_WIRE_ID) wireIdQueries = append(wireIdQueries, hostLocalWireQ) } vpcHostQ = vpcHostQ.IsNotEmpty("ovn_version") vpcHostCnt, err := vpcHostQ.CountWithError() if err != nil { return nil, errors.Wrap(err, "vpcHostQ.CountWithError") } if vpcHostCnt > 0 { // should consider VPC network wire vpcWiresIdQ := WireManager.Query("id").NotEquals("vpc_id", api.DEFAULT_VPC_ID) wireIdQueries = append(wireIdQueries, vpcWiresIdQ) } } var wireIdQ *sqlchemy.SSubQuery if len(wireIdQueries) > 1 { wireIdQ = sqlchemy.Union(wireIdQueries...).Query().SubQuery() } else { wireIdQ = classicWiresIdQ.SubQuery() } additionalQ := NetworkAdditionalWireManager.Query("network_id") additionalQ = additionalQ.Join(wireIdQ, sqlchemy.Equals(wireIdQ.Field("id"), additionalQ.Field("wire_id"))) q = q.Filter(sqlchemy.OR( sqlchemy.In(q.Field("wire_id"), wireIdQ), sqlchemy.In(q.Field("id"), additionalQ.SubQuery()), )) } storageStr := input.StorageId if len(storageStr) > 0 { storage, err := StorageManager.FetchByIdOrName(ctx, userCred, storageStr) if err != nil { return nil, errors.Wrapf(err, "unable to fetch storage %q", storageStr) } hoststorages := HoststorageManager.Query("host_id").Equals("storage_id", storage.GetId()).SubQuery() hostSq := HostManager.Query("id").In("id", hoststorages).SubQuery() sq := NetInterfaceManager.Query("wire_id").In("baremetal_id", hostSq) ovnHosts := HostManager.Query().In("id", hoststorages).IsNotEmpty("ovn_version") if n, _ := ovnHosts.CountWithError(); n > 0 { wireQuery := WireManager.Query("id").IsNotNull("vpc_id") q = q.Filter(sqlchemy.OR( sqlchemy.In(q.Field("wire_id"), wireQuery.SubQuery()), sqlchemy.In(q.Field("wire_id"), sq.SubQuery())), ) } else { q = q.Filter(sqlchemy.In(q.Field("wire_id"), sq.SubQuery())) } } ips := []string{} exactIpMatch := false if len(input.Ip) > 0 { exactIpMatch = true ips = input.Ip } else if len(input.IpMatch) > 0 { ips = input.IpMatch } if len(ips) > 0 { conditions := []sqlchemy.ICondition{} for _, ipstr := range ips { if len(ipstr) == 0 { continue } var ipCondtion sqlchemy.ICondition if ip4Addr, err := netutils.NewIPV4Addr(ipstr); err == nil { // ipv4 address, exactly ipStart := sqlchemy.INET_ATON(q.Field("guest_ip_start")) ipEnd := sqlchemy.INET_ATON(q.Field("guest_ip_end")) ipConst := sqlchemy.INET_ATON(q.StringField(ip4Addr.String())) ipCondtion = sqlchemy.AND( sqlchemy.IsNotNull(q.Field("guest_ip_start")), sqlchemy.IsNotNull(q.Field("guest_ip_end")), sqlchemy.IsNotEmpty(q.Field("guest_ip_start")), sqlchemy.IsNotEmpty(q.Field("guest_ip_end")), sqlchemy.GE(ipEnd, ipConst), sqlchemy.LE(ipStart, ipConst), ) if !exactIpMatch { ipCondtion = sqlchemy.OR( ipCondtion, sqlchemy.Contains(q.Field("guest_ip_start"), ipstr), sqlchemy.Contains(q.Field("guest_ip_end"), ipstr), ) } } else if ip6Addr, err := netutils.NewIPV6Addr(ipstr); err == nil { // ipv6 address, exactly ipStart := sqlchemy.INET6_ATON(q.Field("guest_ip6_start")) ipEnd := sqlchemy.INET6_ATON(q.Field("guest_ip6_end")) ipConst := sqlchemy.INET6_ATON(q.StringField(ip6Addr.String())) ipCondtion = sqlchemy.AND( sqlchemy.IsNotNull(q.Field("guest_ip6_start")), sqlchemy.IsNotNull(q.Field("guest_ip6_end")), sqlchemy.IsNotEmpty(q.Field("guest_ip6_start")), sqlchemy.IsNotEmpty(q.Field("guest_ip6_end")), sqlchemy.GE(ipEnd, ipConst), sqlchemy.LE(ipStart, ipConst), ) if !exactIpMatch { ipCondtion = sqlchemy.OR( ipCondtion, sqlchemy.Contains(q.Field("guest_ip6_start"), ip6Addr.String()), sqlchemy.Contains(q.Field("guest_ip6_end"), ip6Addr.String()), ) } } else { ipCondtion = sqlchemy.OR( sqlchemy.Contains(q.Field("guest_ip_start"), ipstr), sqlchemy.Contains(q.Field("guest_ip_end"), ipstr), sqlchemy.Contains(q.Field("guest_ip6_start"), ipstr), sqlchemy.Contains(q.Field("guest_ip6_end"), ipstr), ) } conditions = append(conditions, ipCondtion) } q = q.Filter(sqlchemy.OR(conditions...)) } if len(input.SchedtagId) > 0 { schedTag, err := SchedtagManager.FetchByIdOrName(ctx, nil, input.SchedtagId) if err != nil { if errors.Cause(err) == sql.ErrNoRows { return nil, httperrors.NewResourceNotFoundError2(SchedtagManager.Keyword(), input.SchedtagId) } return nil, httperrors.NewGeneralError(err) } sq := NetworkschedtagManager.Query("network_id").Equals("schedtag_id", schedTag.GetId()).SubQuery() q = q.In("id", sq) } if len(input.IfnameHint) > 0 { q = q.In("ifname_hint", input.IfnameHint) } if len(input.GuestIpStart) > 0 { q = q.Filter(sqlchemy.ContainsAny(q.Field("guest_ip_start"), input.GuestIpStart)) } if len(input.GuestIpEnd) > 0 { q = q.Filter(sqlchemy.ContainsAny(q.Field("guest_ip_end"), input.GuestIpEnd)) } if len(input.GuestIpMask) > 0 { q = q.In("guest_ip_mask", input.GuestIpMask) } if len(input.GuestGateway) > 0 { q = q.In("guest_gateway", input.GuestGateway) } if len(input.GuestDns) > 0 { q = q.In("guest_dns", input.GuestDns) } if len(input.GuestDhcp) > 0 { q = q.In("guest_dhcp", input.GuestDhcp) } if len(input.GuestNtp) > 0 { q = q.In("guest_ntp", input.GuestNtp) } if len(input.GuestDomain) > 0 { q = q.In("guest_domain", input.GuestDomain) } if len(input.GuestIp6Start) > 0 { q = q.In("guest_ip6_start", input.GuestIp6Start) } if len(input.GuestIp6End) > 0 { q = q.In("guest_ip6_end", input.GuestIp6End) } if len(input.GuestIp6Mask) > 0 { q = q.In("guest_ip6_mask", input.GuestIp6Mask) } if len(input.GuestGateway6) > 0 { q = q.In("guest_gateway6", input.GuestGateway6) } if len(input.GuestDns6) > 0 { q = q.In("guest_dns6", input.GuestDns6) } if len(input.GuestDomain6) > 0 { q = q.In("guest_domain6", input.GuestDomain6) } if len(input.VlanId) > 0 { q = q.In("vlan_id", input.VlanId) } if len(input.ServerType) > 0 { q = q.In("server_type", input.ServerType) } if len(input.AllocPolicy) > 0 { q = q.In("alloc_policy", input.AllocPolicy) } if len(input.BgpType) > 0 { q = q.In("bgp_type", input.BgpType) } if input.IsAutoAlloc != nil { if *input.IsAutoAlloc { q = q.IsTrue("is_auto_alloc") } else { q = q.IsFalse("is_auto_alloc") } } if input.IsClassic != nil { subq := manager.Query("id") wires := WireManager.Query("id", "vpc_id").SubQuery() subq = subq.Join(wires, sqlchemy.Equals(wires.Field("id"), subq.Field("wire_id"))) if *input.IsClassic { subq = subq.Filter(sqlchemy.Equals(wires.Field("vpc_id"), api.DEFAULT_VPC_ID)) } else { subq = subq.Filter(sqlchemy.NotEquals(wires.Field("vpc_id"), api.DEFAULT_VPC_ID)) } q = q.In("id", subq.SubQuery()) } if len(input.HostSchedtagId) > 0 { schedTagObj, err := SchedtagManager.FetchByIdOrName(ctx, userCred, input.HostSchedtagId) if err != nil { if errors.Cause(err) == sql.ErrNoRows { return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", SchedtagManager.Keyword(), input.HostSchedtagId) } else { return nil, errors.Wrap(err, "SchedtagManager.FetchByIdOrName") } } subq := NetInterfaceManager.Query("wire_id") hostschedtags := HostschedtagManager.Query().Equals("schedtag_id", schedTagObj.GetId()).SubQuery() subq = subq.Join(hostschedtags, sqlchemy.Equals(hostschedtags.Field("host_id"), subq.Field("baremetal_id"))) q = q.In("wire_id", subq.SubQuery()) } return q, nil } func (manager *SNetworkManager) OrderByExtraFields( ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.NetworkListInput, ) (*sqlchemy.SQuery, error) { var err error q, err = manager.SSharableVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, input.SharableVirtualResourceListInput) if err != nil { return nil, errors.Wrap(err, "SSharableVirtualResourceBaseManager.OrderByExtraFields") } q, err = manager.SWireResourceBaseManager.OrderByExtraFields(ctx, q, userCred, input.WireFilterListInput) if err != nil { return nil, errors.Wrap(err, "SWireResourceBaseManager.OrderByExtraFields") } if db.NeedOrderQuery([]string{input.OrderByIpStart}) { q = db.OrderByFields(q, []string{input.OrderByIpStart}, []sqlchemy.IQueryField{sqlchemy.INET_ATON(q.Field("guest_ip_start"))}) } if db.NeedOrderQuery([]string{input.OrderByIpEnd}) { q = db.OrderByFields(q, []string{input.OrderByIpEnd}, []sqlchemy.IQueryField{sqlchemy.INET_ATON(q.Field("guest_ip_end"))}) } return q, nil } func (manager *SNetworkManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { var err error q, err = manager.SSharableVirtualResourceBaseManager.QueryDistinctExtraField(q, field) if err == nil { return q, nil } q, err = manager.SWireResourceBaseManager.QueryDistinctExtraField(q, field) if err == nil { return q, nil } return q, httperrors.ErrNotFound } func (manager *SNetworkManager) InitializeData() error { // set network status networks := make([]SNetwork, 0) q := manager.Query() err := db.FetchModelObjects(manager, q, &networks) if err != nil { return err } for _, n := range networks { if n.ExternalId != "" { var statusNew string if n.WireId != "" && n.Status == api.NETWORK_STATUS_INIT { statusNew = api.NETWORK_STATUS_AVAILABLE } db.Update(&n, func() error { if statusNew != "" { n.Status = statusNew } return nil }) } else { var ifnameHintNew string if n.IfnameHint == "" { ifnameHintNew = n.Name } db.Update(&n, func() error { if ifnameHintNew != "" { n.IfnameHint = ifnameHintNew } return nil }) } if n.IsAutoAlloc.IsNone() { db.Update(&n, func() error { if n.IsPublic && n.ServerType == api.NETWORK_TYPE_GUEST { n.IsAutoAlloc = tristate.True } else { n.IsAutoAlloc = tristate.False } return nil }) } } return nil } func (snet *SNetwork) ValidateUpdateCondition(ctx context.Context) error { /*if len(snet.ExternalId) > 0 { return httperrors.NewConflictError("Cannot update external resource") }*/ return snet.SSharableVirtualResourceBase.ValidateUpdateCondition(ctx) } func (net *SNetwork) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query, data jsonutils.JSONObject) { net.SSharableVirtualResourceBase.PostUpdate(ctx, userCred, query, data) // reserve guest gateway IP { input := api.NetworkUpdateInput{} err := data.Unmarshal(&input) if err != nil { log.Errorf("Unmarshal NetworkUpdateInput fail %s", err) } else if input.GuestGateway != nil && len(*input.GuestGateway) > 0 { err := net.reserveGuestGateway(ctx, userCred) if err != nil { log.Errorf("reserveGuestGateway fail %s", err) } } } net.ClearSchedDescCache() if net.IsClassic() { err := net.syncAdditionalWires(ctx, nil) if err != nil { log.Errorf("syncAdditionalWires error %s", err) } } } // 清除IP子网数据 // 要求IP子网内没有被分配IP,若清除接入云,要求接入云账号处于禁用状态 func (snet *SNetwork) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworkPurgeInput) (jsonutils.JSONObject, error) { err := snet.ValidateDeleteCondition(ctx, nil) if err != nil { return nil, err } wire, _ := snet.GetWire() if wire != nil && len(wire.ExternalId) > 0 { provider := wire.GetCloudprovider() if provider != nil && provider.GetEnabled() { return nil, httperrors.NewInvalidStatusError("Cannot purge network on enabled cloud provider") } } err = snet.RealDelete(ctx, userCred) return nil, err } func (manager *SNetworkManager) handleNetworkIdChange(ctx context.Context, args *networkIdChangeArgs) error { var handlers = []networkIdChangeHandler{ GuestnetworkManager, HostnetworkManager, ReservedipManager, GroupnetworkManager, LoadbalancernetworkManager, LoadbalancerManager, } errs := []error{} for _, h := range handlers { if err := h.handleNetworkIdChange(ctx, args); err != nil { errs = append(errs, err) } } if len(errs) > 0 { err := errors.NewAggregate(errs) return httperrors.NewGeneralError(err) } return nil } // 合并IP子网 // 将两个相连的IP子网合并成一个IP子网 func (snet *SNetwork) PerformMerge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworkMergeInput) (jsonutils.JSONObject, error) { if len(input.Target) == 0 { return nil, httperrors.NewMissingParameterError("target") } iNet, err := NetworkManager.FetchByIdOrName(ctx, userCred, input.Target) if err == sql.ErrNoRows { err = httperrors.NewNotFoundError("Network %s not found", input.Target) logclient.AddActionLogWithContext(ctx, snet, logclient.ACT_MERGE, err.Error(), userCred, false) return nil, err } else if err != nil { logclient.AddActionLogWithContext(ctx, snet, logclient.ACT_MERGE, err.Error(), userCred, false) return nil, err } net := iNet.(*SNetwork) if net == nil { err = fmt.Errorf("Network is nil") logclient.AddActionLogWithContext(ctx, snet, logclient.ACT_MERGE, err.Error(), userCred, false) return nil, err } startIp, endIp, err := snet.CheckInvalidToMerge(ctx, net, nil) if err != nil { logclient.AddActionLogWithContext(ctx, snet, logclient.ACT_MERGE, err.Error(), userCred, false) return nil, err } return nil, snet.MergeToNetworkAfterCheck(ctx, userCred, net, startIp, endIp) } func (snet *SNetwork) MergeToNetworkAfterCheck(ctx context.Context, userCred mcclient.TokenCredential, net *SNetwork, startIp string, endIp string) error { lockman.LockClass(ctx, NetworkManager, db.GetLockClassKey(NetworkManager, userCred)) defer lockman.ReleaseClass(ctx, NetworkManager, db.GetLockClassKey(NetworkManager, userCred)) _, err := db.Update(net, func() error { net.GuestIpStart = startIp net.GuestIpEnd = endIp return nil }) if err != nil { logclient.AddActionLogWithContext(ctx, snet, logclient.ACT_MERGE, err.Error(), userCred, false) return err } if err := NetworkManager.handleNetworkIdChange(ctx, &networkIdChangeArgs{ action: logclient.ACT_MERGE, oldNet: snet, newNet: net, userCred: userCred, }); err != nil { return err } note := map[string]string{"start_ip": startIp, "end_ip": endIp} db.OpsLog.LogEvent(snet, db.ACT_MERGE, note, userCred) logclient.AddActionLogWithContext(ctx, snet, logclient.ACT_MERGE, note, userCred, true) if err = snet.RealDelete(ctx, userCred); err != nil { return err } note = map[string]string{"network": snet.Id} db.OpsLog.LogEvent(snet, db.ACT_DELETE, note, userCred) logclient.AddActionLogWithContext(ctx, snet, logclient.ACT_DELOCATE, note, userCred, true) return nil } func (snet *SNetwork) CheckInvalidToMerge(ctx context.Context, net *SNetwork, allNets []*SNetwork) (string, string, error) { failReason := make([]string, 0) if snet.WireId != net.WireId { failReason = append(failReason, "wire_id") } if snet.GuestGateway != net.GuestGateway { failReason = append(failReason, "guest_gateway") } if snet.VlanId != net.VlanId { failReason = append(failReason, "vlan_id") } // Qiujian: allow merge networks of different server_type /*if snet.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, ",")) return "", "", err } var startIp, endIp string ipNE, _ := netutils.NewIPV4Addr(net.GuestIpEnd) ipNS, _ := netutils.NewIPV4Addr(net.GuestIpStart) ipSS, _ := netutils.NewIPV4Addr(snet.GuestIpStart) ipSE, _ := netutils.NewIPV4Addr(snet.GuestIpEnd) var wireNets []SNetwork if allNets == nil { wireSubq := WireManager.Query("vpc_id").Equals("id", snet.WireId).SubQuery() wiresQ := WireManager.Query("id") wiresSubQ := wiresQ.Join(wireSubq, sqlchemy.Equals(wiresQ.Field("vpc_id"), wireSubq.Field("vpc_id"))).SubQuery() q := NetworkManager.Query().In("wire_id", wiresSubQ).NotEquals("id", snet.Id).NotEquals("id", net.Id) err := db.FetchModelObjects(NetworkManager, q, &wireNets) if err != nil && errors.Cause(err) != sql.ErrNoRows { return "", "", errors.Wrap(err, "Query nets of same wire") } } else { wireNets = make([]SNetwork, len(allNets)) for i := range wireNets { wireNets[i] = *allNets[i] } } if ipNE.StepUp() == ipSS || (ipNE.StepUp() < ipSS && !isOverlapNetworksAddr(wireNets, ipNE.StepUp(), ipSS.StepDown())) { startIp, endIp = net.GuestIpStart, snet.GuestIpEnd } else if ipSE.StepUp() == ipNS || (ipSE.StepUp() < ipNS && !isOverlapNetworksAddr(wireNets, ipSE.StepUp(), ipNS.StepDown())) { startIp, endIp = snet.GuestIpStart, net.GuestIpEnd } else { note := "Incontinuity Network for %s and %s" return "", "", httperrors.NewBadRequestError(note, snet.Name, net.Name) } return startIp, endIp, nil } // 分割IP子网 // 将一个IP子网分割成两个子网,仅本地IDC支持此操作 func (snet *SNetwork) PerformSplit(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworkSplitInput) (jsonutils.JSONObject, error) { if len(snet.ExternalId) > 0 { return nil, httperrors.NewNotSupportedError("only on premise support this operation") } if !snet.IsClassic() { return nil, httperrors.NewNotSupportedError("only on premise classic network support this operation") } if snet.IsSupportIPv6() { return nil, httperrors.NewNotSupportedError("only on premise pure-ipv4 classic network support this operation") } if len(input.SplitIp) == 0 { return nil, httperrors.NewMissingParameterError("split_ip") } if !regutils.MatchIPAddr(input.SplitIp) { return nil, httperrors.NewInputParameterError("Invalid IP %s", input.SplitIp) } if input.SplitIp == snet.GuestIpStart { return nil, httperrors.NewInputParameterError("Split IP %s is the start ip", input.SplitIp) } iSplitIp, err := netutils.NewIPV4Addr(input.SplitIp) if err != nil { return nil, err } if !snet.IsAddressInRange(iSplitIp) { return nil, httperrors.NewInputParameterError("Split IP %s out of range", input.SplitIp) } network := &SNetwork{} network.GuestIpStart = input.SplitIp network.GuestIpEnd = snet.GuestIpEnd network.GuestIpMask = snet.GuestIpMask network.GuestGateway = snet.GuestGateway network.GuestDns = snet.GuestDns network.GuestDhcp = snet.GuestDhcp network.GuestNtp = snet.GuestNtp network.GuestDomain = snet.GuestDomain network.VlanId = snet.VlanId network.WireId = snet.WireId network.ServerType = snet.ServerType network.IsPublic = snet.IsPublic network.Status = snet.Status network.ProjectId = snet.ProjectId network.DomainId = snet.DomainId // network.UserId = snet.UserId network.IsSystem = snet.IsSystem network.Description = snet.Description network.IsAutoAlloc = snet.IsAutoAlloc err = func() error { lockman.LockRawObject(ctx, NetworkManager.Keyword(), "name") defer lockman.ReleaseRawObject(ctx, NetworkManager.Keyword(), "name") if len(input.Name) > 0 { if err := db.NewNameValidator(ctx, NetworkManager, userCred, input.Name, nil); err != nil { return httperrors.NewInputParameterError("Duplicate name %s", input.Name) } } else { input.Name, err = db.GenerateName(ctx, NetworkManager, userCred, fmt.Sprintf("%s#", snet.Name)) if err != nil { return httperrors.NewInternalServerError("GenerateName fail %s", err) } } network.Name = input.Name network.IfnameHint, err = NetworkManager.newIfnameHint(input.Name) if err != nil { return httperrors.NewBadRequestError("Generate ifname hint failed %s", err) } return NetworkManager.TableSpec().Insert(ctx, network) }() if err != nil { return nil, err } network.SetModelManager(NetworkManager, network) db.Update(snet, func() error { snet.GuestIpEnd = iSplitIp.StepDown().String() return nil }) if err := NetworkManager.handleNetworkIdChange(ctx, &networkIdChangeArgs{ action: logclient.ACT_SPLIT, oldNet: snet, newNet: network, userCred: userCred, }); err != nil { return nil, err } note := map[string]string{"split_ip": input.SplitIp, "end_ip": network.GuestIpEnd} db.OpsLog.LogEvent(snet, db.ACT_SPLIT, note, userCred) logclient.AddActionLogWithContext(ctx, snet, logclient.ACT_SPLIT, note, userCred, true) db.OpsLog.LogEvent(network, db.ACT_CREATE, map[string]string{"network": snet.Id}, userCred) return nil, nil } func (manager *SNetworkManager) PerformTryCreateNetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworkTryCreateNetworkInput) (jsonutils.JSONObject, error) { if len(input.Ip) == 0 { return nil, httperrors.NewMissingParameterError("ip") } var ipV4, ipV4NetAddr netutils.IPV4Addr var ipV6, ipV6NetAddr netutils.IPV6Addr var err error if input.Mask == 0 { return nil, httperrors.NewMissingParameterError("mask") } if regutils.MatchIP4Addr(input.Ip) { ipV4, err = netutils.NewIPV4Addr(input.Ip) if err != nil { return nil, errors.Wrapf(err, "invalid ipv4 address %s", input.Ip) } if ipV4.IsZero() { return nil, errors.Wrapf(errors.ErrInvalidFormat, "invalid ipv4 address %s", input.Ip) } ipV4NetAddr = ipV4.NetAddr(int8(input.Mask)) } else if regutils.MatchIP6Addr(input.Ip) { ipV6, err = netutils.NewIPV6Addr(input.Ip) if err != nil { return nil, errors.Wrapf(err, "invalid ipv6 address %s", input.Ip) } if ipV6.IsZero() { return nil, errors.Wrapf(errors.ErrInvalidFormat, "invalid ipv6 address %s", input.Ip) } ipV6NetAddr = ipV6.NetAddr(uint8(input.Mask)) } else { return nil, errors.Wrapf(errors.ErrInvalidFormat, "invalid ip address %s", input.Ip) } if len(input.ServerType) == 0 { return nil, httperrors.NewMissingParameterError("server_type") } if input.ServerType != api.NETWORK_TYPE_BAREMETAL { return nil, httperrors.NewBadRequestError("Only support server type %s", api.NETWORK_TYPE_BAREMETAL) } if !input.IsOnPremise { return nil, httperrors.NewBadRequestError("Only support on premise network") } var ( nm *SNetwork matched bool ) q := NetworkManager.Query().Equals("server_type", input.ServerType) if !ipV4.IsZero() { q = q.Equals("guest_ip_mask", input.Mask) } else if !ipV6.IsZero() { q = q.Equals("guest_ip6_mask", input.Mask) } listQuery := api.NetworkListInput{} err = query.Unmarshal(&listQuery) if err != nil { return nil, errors.Wrap(err, "query.Unmarshal") } q, err = managedResourceFilterByAccount(ctx, q, listQuery.ManagedResourceListInput, "wire_id", func() *sqlchemy.SQuery { wires := WireManager.Query().SubQuery() vpcs := VpcManager.Query().SubQuery() subq := wires.Query(wires.Field("id")) subq = subq.Join(vpcs, sqlchemy.Equals(vpcs.Field("id"), wires.Field("vpc_id"))) return subq }) if err != nil { return nil, errors.Wrap(err, "managedResourceFilterByAccount") } rows, err := q.Rows() if err != nil { return nil, err } defer rows.Close() for rows.Next() { item, err := db.NewModelObject(NetworkManager) if err != nil { return nil, err } err = q.Row2Struct(rows, item) if err != nil { return nil, err } n := item.(*SNetwork) if !ipV4.IsZero() { if n.getIPRange().Contains(ipV4) { nm = n matched = true break } else if nIpV4, _ := netutils.NewIPV4Addr(n.GuestIpStart); nIpV4.NetAddr(n.GuestIpMask) == ipV4NetAddr { nm = n matched = false break } } else if !ipV6.IsZero() { if n.getIPRange6().Contains(ipV6) { nm = n matched = true break } else if nIpV6, _ := netutils.NewIPV6Addr(n.GuestIp6Start); nIpV6.NetAddr(n.GuestIp6Mask) == ipV6NetAddr { nm = n matched = false break } } } ret := jsonutils.NewDict() if nm == nil { ret.Set("find_matched", jsonutils.JSONFalse) return ret, nil } ret.Set("find_matched", jsonutils.JSONTrue) ret.Set("wire_id", jsonutils.NewString(nm.WireId)) if !matched { log.Infof("Find same subnet network %s %s/%d %s/%d", nm.Name, nm.GuestGateway, nm.GuestIpMask, nm.GuestGateway6, nm.GuestIp6Mask) newNetwork := new(SNetwork) newNetwork.SetModelManager(NetworkManager, newNetwork) if !ipV4.IsZero() { newNetwork.GuestIpStart = ipV4.String() newNetwork.GuestIpEnd = ipV4.String() newNetwork.GuestGateway = nm.GuestGateway newNetwork.GuestIpMask = int8(input.Mask) } else if !ipV6.IsZero() { newNetwork.GuestIp6Start = ipV6.String() newNetwork.GuestIp6End = ipV6.String() newNetwork.GuestGateway6 = nm.GuestGateway6 newNetwork.GuestIp6Mask = uint8(input.Mask) } newNetwork.GuestDns = nm.GuestDns newNetwork.GuestDhcp = nm.GuestDhcp newNetwork.GuestNtp = nm.GuestNtp newNetwork.WireId = nm.WireId newNetwork.ServerType = input.ServerType newNetwork.IsPublic = nm.IsPublic newNetwork.ProjectId = userCred.GetProjectId() newNetwork.DomainId = userCred.GetProjectDomainId() err = func() error { lockman.LockRawObject(ctx, NetworkManager.Keyword(), "name") defer lockman.ReleaseRawObject(ctx, NetworkManager.Keyword(), "name") newNetwork.Name, err = db.GenerateName(ctx, NetworkManager, userCred, fmt.Sprintf("%s#", nm.Name)) if err != nil { return httperrors.NewInternalServerError("GenerateName fail %s", err) } return NetworkManager.TableSpec().Insert(ctx, newNetwork) }() if err != nil { return nil, err } err = newNetwork.CustomizeCreate(ctx, userCred, userCred, query, input.JSON(input)) if err != nil { return nil, err } newNetwork.PostCreate(ctx, userCred, userCred, query, input.JSON(input)) // inherit wire's class metadata wire, err := newNetwork.GetWire() if err != nil { return nil, errors.Wrap(err, "unable to get wire") } err = db.InheritFromTo(ctx, userCred, wire, newNetwork) if err != nil { return nil, errors.Wrap(err, "unable to inherit wire") } } return ret, nil } func (network *SNetwork) getAllocTimoutDuration() time.Duration { tos := network.AllocTimoutSeconds if tos < options.Options.MinimalIpAddrReusedIntervalSeconds { tos = options.Options.MinimalIpAddrReusedIntervalSeconds } return time.Duration(tos) * time.Second } func (network *SNetwork) GetSchedtags() []SSchedtag { return GetSchedtags(NetworkschedtagManager, network.Id) } func (network *SNetwork) GetDynamicConditionInput() *jsonutils.JSONDict { return jsonutils.Marshal(network).(*jsonutils.JSONDict) } func (network *SNetwork) PerformSetSchedtag(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { return PerformSetResourceSchedtag(network, ctx, userCred, query, data) } func (network *SNetwork) GetSchedtagJointManager() ISchedtagJointManager { return NetworkschedtagManager } func (network *SNetwork) ClearSchedDescCache() error { wire, _ := network.GetWire() if wire == nil { return nil } return wire.clearHostSchedDescCache() } func (network *SNetwork) PerformChangeOwner(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformChangeProjectOwnerInput) (jsonutils.JSONObject, error) { wire, err := network.GetWire() if err != nil { return nil, errors.Wrap(err, "unable to get wire") } project, err := db.TenantCacheManager.FetchTenantById(ctx, input.ProjectId) if err != nil { return nil, errors.Wrapf(err, "unable to get project %s", input.ProjectId) } ok, err := db.IsInSameClass(ctx, wire, project) if err != nil { return nil, errors.Wrap(err, "unable to check if the wire and project is in same class") } if !ok { return nil, httperrors.NewForbiddenError("the wire %s and the project %s has different class metadata", wire.GetName(), project.GetName()) } ret, err := network.SSharableVirtualResourceBase.PerformChangeOwner(ctx, userCred, query, input) if err != nil { return nil, err } network.ClearSchedDescCache() return ret, nil } func (network *SNetwork) getUsedAddressQuery(ctx context.Context, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope, addrOnly bool) *sqlchemy.SQuery { usedAddressQueryProviders := getUsedAddressQueryProviders() var ( args = &usedAddressQueryArgs{ network: network, userCred: userCred, owner: owner, scope: scope, addrOnly: addrOnly, addrType: api.AddressTypeIPv4, } queries = make([]sqlchemy.IQuery, 0, len(usedAddressQueryProviders)) ) for _, provider := range usedAddressQueryProviders { q := provider.usedAddressQuery(ctx, args) queries = append(queries, q) } return sqlchemy.Union(queries...).Query() } func (network *SNetwork) getUsedAddressQuery6(ctx context.Context, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope, addrOnly bool) *sqlchemy.SQuery { usedAddress6QueryProviders := getUsedAddress6QueryProviders() var ( args = &usedAddressQueryArgs{ network: network, userCred: userCred, owner: owner, scope: scope, addrOnly: addrOnly, addrType: api.AddressTypeIPv6, } queries = make([]sqlchemy.IQuery, 0, len(usedAddress6QueryProviders)) ) for _, provider := range usedAddress6QueryProviders { q := provider.usedAddressQuery(ctx, args) queries = append(queries, q) } return sqlchemy.Union(queries...).Query() } func (snet *SNetwork) Contains(ip string) bool { start, _ := netutils.NewIPV4Addr(snet.GuestIpStart) end, _ := netutils.NewIPV4Addr(snet.GuestIpEnd) addr, _ := netutils.NewIPV4Addr(ip) return netutils.NewIPV4AddrRange(start, end).Contains(addr) } type SNetworkUsedAddressList []api.SNetworkUsedAddress func (a SNetworkUsedAddressList) Len() int { return len(a) } func (a SNetworkUsedAddressList) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a SNetworkUsedAddressList) Less(i, j int) bool { ipI, _ := netutils.NewIPV4Addr(a[i].IpAddr) ipJ, _ := netutils.NewIPV4Addr(a[j].IpAddr) return ipI < ipJ } type SNetworkUsedAddress6List []api.SNetworkUsedAddress func (a SNetworkUsedAddress6List) Len() int { return len(a) } func (a SNetworkUsedAddress6List) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a SNetworkUsedAddress6List) Less(i, j int) bool { ipI, _ := netutils.NewIPV6Addr(a[i].Ip6Addr) ipJ, _ := netutils.NewIPV6Addr(a[j].Ip6Addr) return ipI.Lt(ipJ) } func (network *SNetwork) GetDetailsAddresses( ctx context.Context, userCred mcclient.TokenCredential, input api.GetNetworkAddressesInput, ) (api.GetNetworkAddressesOutput, error) { output := api.GetNetworkAddressesOutput{} allowScope, _ := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, network.KeywordPlural(), policy.PolicyActionGet, "addresses") scope := rbacscope.String2ScopeDefault(input.Scope, allowScope) if scope.HigherThan(allowScope) { return output, errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", scope, allowScope) } output, err := network.fetchAddressDetails(ctx, userCred, userCred, scope) if err != nil { return output, errors.Wrap(err, "fetchAddressDetails") } return output, nil } func (network *SNetwork) GetUsedAddressDetails(ctx context.Context, addr string) (*api.SNetworkUsedAddress, error) { address, err := network.GetAddressDetails(ctx, nil, nil, rbacscope.ScopeSystem) if err != nil { return nil, errors.Wrapf(err, "GetAddressDetails") } for i := range address { if address[i].IpAddr == addr || address[i].Ip6Addr == addr { return &address[i], nil } } return nil, errors.Wrapf(errors.ErrNotFound, "%s", addr) } func (network *SNetwork) GetAddressDetails(ctx context.Context, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) ([]api.SNetworkUsedAddress, error) { netAddrs := make([]api.SNetworkUsedAddress, 0) q := network.getUsedAddressQuery(ctx, userCred, owner, scope, false) err := q.All(&netAddrs) if err != nil { return nil, httperrors.NewGeneralError(err) } sort.Sort(SNetworkUsedAddressList(netAddrs)) return netAddrs, nil } func (network *SNetwork) fetchAddressDetails(ctx context.Context, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) (api.GetNetworkAddressesOutput, error) { output := api.GetNetworkAddressesOutput{} { var err error output.Addresses, err = network.GetAddressDetails(ctx, userCred, owner, scope) if err != nil { return output, err } } { netAddrs6 := make([]api.SNetworkUsedAddress, 0) q := network.getUsedAddressQuery6(ctx, userCred, owner, scope, false) err := q.All(&netAddrs6) if err != nil { return output, httperrors.NewGeneralError(err) } sort.Sort(SNetworkUsedAddress6List(netAddrs6)) output.Addresses6 = netAddrs6 } return output, nil } func (network *SNetwork) GetDetailsAvailableAddresses( ctx context.Context, userCred mcclient.TokenCredential, input api.GetNetworkAvailableAddressesInput, ) (api.GetNetworkAvailableAddressesOutput, error) { const maxCount = 1024 var availables []string var availables6 []string { addrTable := network.GetUsedAddresses(ctx) recentUsedAddrTable := GuestnetworkManager.getRecentlyReleasedIPAddresses(network.Id, network.getAllocTimoutDuration()) addrRange := network.getIPRange() for addr := addrRange.StartIp(); addr <= addrRange.EndIp() && len(availables) < maxCount; addr = addr.StepUp() { addrStr := addr.String() if _, ok := addrTable[addrStr]; !ok { if _, ok := recentUsedAddrTable[addrStr]; !ok { availables = append(availables, addrStr) } } } } if network.IsSupportIPv6() { addrTable6 := network.GetUsedAddresses6(ctx) recentUsedAddrTable6 := GuestnetworkManager.getRecentlyReleasedIPAddresses6(network.Id, network.getAllocTimoutDuration()) addrRange6 := network.getIPRange6() for addr6 := addrRange6.StartIp(); addr6.Le(addrRange6.EndIp()) && len(availables6) < maxCount; addr6 = addr6.StepUp() { addrStr6 := addr6.String() if _, ok := addrTable6[addrStr6]; !ok { if _, ok := recentUsedAddrTable6[addrStr6]; !ok { availables6 = append(availables6, addrStr6) } } } } return api.GetNetworkAvailableAddressesOutput{ Addresses: availables, Addresses6: availables6, }, nil } // 同步接入云IP子网状态 // 本地IDC不支持此操作 func (net *SNetwork) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworkSyncInput) (jsonutils.JSONObject, error) { return net.PerformSync(ctx, userCred, query, input) } // 同步接入云IP子网状态 // 本地IDC不支持此操作 func (net *SNetwork) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworkSyncInput) (jsonutils.JSONObject, error) { vpc, _ := net.GetVpc() if vpc != nil && vpc.IsManaged() { return nil, net.StartSyncstatusTask(ctx, userCred, "") } return nil, httperrors.NewUnsupportOperationError("on-premise network cannot sync status") } func (net *SNetwork) StartSyncstatusTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { return StartResourceSyncStatusTask(ctx, userCred, net, "NetworkSyncstatusTask", parentTaskId) } // 更改IP子网状态 func (net *SNetwork) PerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformStatusInput) (jsonutils.JSONObject, error) { if len(input.Status) == 0 { return nil, httperrors.NewMissingParameterError("status") } vpc, _ := net.GetVpc() if vpc != nil && vpc.IsManaged() { return nil, httperrors.NewUnsupportOperationError("managed network cannot change status") } if !utils.IsInStringArray(input.Status, []string{api.NETWORK_STATUS_AVAILABLE, api.NETWORK_STATUS_UNAVAILABLE}) { return nil, httperrors.NewInputParameterError("invalid status %s", input.Status) } return net.SSharableVirtualResourceBase.PerformStatus(ctx, userCred, query, input) } func (net *SNetwork) GetChangeOwnerCandidateDomainIds() []string { candidates := [][]string{} wire, _ := net.GetWire() if wire != nil { vpc, _ := wire.GetVpc() if vpc != nil { candidates = append(candidates, vpc.GetChangeOwnerCandidateDomainIds()) } candidates = append(candidates, db.ISharableChangeOwnerCandidateDomainIds(wire)) } return db.ISharableMergeChangeOwnerCandidateDomainIds(net, candidates...) } func (manager *SNetworkManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, keys stringutils2.SSortedStrings, ) (*sqlchemy.SQuery, error) { var err error q, err = manager.SSharableVirtualResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) if err != nil { return nil, errors.Wrap(err, "SSharableVirtualResourceBaseManager.ListItemExportKeys") } if keys.ContainsAny(manager.SWireResourceBaseManager.GetExportKeys()...) { q, err = manager.SWireResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) if err != nil { return nil, errors.Wrap(err, "SWireResourceBaseManager.ListItemExportKeys") } } return q, nil } func (manager *SNetworkManager) AllowScope(userCred mcclient.TokenCredential) rbacscope.TRbacScope { scope, _ := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, NetworkManager.KeywordPlural(), policy.PolicyActionGet) return scope } func (snet *SNetwork) PerformSetBgpType(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworkSetBgpTypeInput) (jsonutils.JSONObject, error) { if snet.BgpType == input.BgpType { return nil, nil } if snet.ServerType != api.NETWORK_TYPE_EIP { return nil, httperrors.NewInputParameterError("BgpType attribute is only useful for eip network") } { var eips []SElasticip q := ElasticipManager.Query(). Equals("network_id", snet.Id). NotEquals("bgp_type", input.BgpType) if err := db.FetchModelObjects(ElasticipManager, q, &eips); err != nil { return nil, err } for i := range eips { eip := &eips[i] if diff, err := db.UpdateWithLock(ctx, eip, func() error { eip.BgpType = input.BgpType return nil }); err != nil { // no need to retry/restore here. return error // and retry after user resolves the error return nil, err } else { db.OpsLog.LogEvent(eip, db.ACT_UPDATE, diff, userCred) } } } if diff, err := db.Update(snet, func() error { snet.BgpType = input.BgpType return nil }); err != nil { return nil, err } else { logclient.AddActionLogWithContext(ctx, snet, logclient.ACT_UPDATE, diff, userCred, true) db.OpsLog.LogEvent(snet, db.ACT_UPDATE, diff, userCred) } return nil, nil } func (net *SNetwork) IsClassic() bool { vpc, _ := net.GetVpc() if vpc != nil && vpc.Id == api.DEFAULT_VPC_ID { return true } return false } func (net *SNetwork) getAttachedHosts() ([]SHost, error) { guestsQ := GuestManager.Query() gnsQ := GuestnetworkManager.Query().Equals("network_id", net.Id).SubQuery() guestsQ = guestsQ.Join(gnsQ, sqlchemy.Equals(guestsQ.Field("id"), gnsQ.Field("guest_id"))) guestsQ = guestsQ.IsNotEmpty("host_id") guestsQ = guestsQ.AppendField(guestsQ.Field("host_id")) guestsSubQ := guestsQ.SubQuery() // unionQ := sqlchemy.Union(hns, guestsQ).Query().SubQuery() q := HostManager.Query() q = q.Join(guestsSubQ, sqlchemy.Equals(q.Field("id"), guestsSubQ.Field("host_id"))) q = q.Distinct() hosts := make([]SHost, 0) err := db.FetchModelObjects(HostManager, q, &hosts) if err != nil { return nil, errors.Wrap(err, "FetchModelObjects") } return hosts, nil } func (net *SNetwork) PerformSwitchWire( ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworkSwitchWireInput, ) (jsonutils.JSONObject, error) { if !net.IsClassic() { return nil, errors.Wrap(httperrors.ErrNotSupported, "default vpc only") } wireObj, err := WireManager.FetchByIdOrName(ctx, userCred, input.WireId) if err != nil { if errors.Cause(err) == sql.ErrNoRows { return nil, httperrors.NewResourceNotFoundError2(WireManager.Keyword(), input.WireId) } else { return nil, errors.Wrapf(err, "WireManager.FetchByIdOrName %s", input.WireId) } } wire := wireObj.(*SWire) if net.WireId == wire.Id { return nil, nil } oldWire, _ := net.GetWire() if oldWire.VpcId != wire.VpcId { return nil, errors.Wrapf(httperrors.ErrConflict, "cannot switch wires of other vpc") } hosts, err := net.getAttachedHosts() if err != nil { return nil, errors.Wrap(err, "getAttachedHosts") } unreachedHost := make([]string, 0) for i := range hosts { if hosts[i].HostType == api.HOST_TYPE_ESXI { continue } if !hosts[i].IsAttach2Wire(wire.Id) { unreachedHost = append(unreachedHost, hosts[i].Name) } } if len(unreachedHost) > 0 { return nil, errors.Wrapf(httperrors.ErrConflict, "wire %s not reachable for hosts %s", wire.Name, strings.Join(unreachedHost, ",")) } diff, err := db.Update(net, func() error { net.WireId = wire.Id return nil }) if err != nil { return nil, errors.Wrap(err, "update wire_id") } { err := net.syncAdditionalWires(ctx, nil) if err != nil { log.Errorf("syncAdditionalWires fail %s", err) } } logclient.AddActionLogWithContext(ctx, net, logclient.ACT_UPDATE, diff, userCred, true) db.OpsLog.LogEvent(net, db.ACT_UPDATE, diff, userCred) // fix vmware hostnics wire hns, err := HostnetworkManager.fetchHostnetworksByNetwork(net.Id) if err != nil { return nil, errors.Wrap(err, "HostnetworkManager.fetchHostnetworksByNetwork") } for i := range hns { nic, err := hns[i].GetNetInterface() if err != nil { return nil, errors.Wrap(err, "Hostnetwork.GetNetInterface") } log.Errorf("PerformSwitchWire: change wireId for nic %s for hostnetwork %s", jsonutils.Marshal(nic), jsonutils.Marshal(hns[i])) if len(nic.Bridge) > 0 { log.Warningf("PerformSwitchWire: non-empty wireId %s for hostnetwork %s", jsonutils.Marshal(nic), jsonutils.Marshal(hns[i])) continue } if nic.WireId != wire.Id { _, err := db.Update(nic, func() error { nic.WireId = wire.Id return nil }) if err != nil { return nil, errors.Wrap(err, "Update NetInterface") } } } return nil, nil } func (net *SNetwork) fetchAdditionalWires() []api.SSimpleWire { wires, err := NetworkAdditionalWireManager.FetchNetworkAdditionalWires(net.Id) if err != nil { log.Errorf("NetworkAdditionalWireManager.FetchNetworkAdditionalWires error %s", err) } return wires } func (net *SNetwork) PerformSyncAdditionalWires( ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworSyncAdditionalWiresInput, ) (jsonutils.JSONObject, error) { if !net.IsClassic() { return nil, errors.Wrap(httperrors.ErrNotSupported, "default vpc only") } wireIds := make([]string, 0) errs := make([]error, 0) for _, wireId := range input.WireIds { wireObj, err := WireManager.FetchByIdOrName(ctx, userCred, wireId) if err != nil { if errors.Cause(err) == sql.ErrNoRows { errs = append(errs, httperrors.NewResourceNotFoundError2(WireManager.Keyword(), wireId)) } else { errs = append(errs, errors.Wrapf(err, "WireManager.FetchByIdOrNam %s", wireId)) } } wireIds = append(wireIds, wireObj.GetId()) } if len(errs) > 0 { return nil, errors.NewAggregate(errs) } err := net.syncAdditionalWires(ctx, wireIds) if err != nil { return nil, errors.Wrap(err, "syncAdditionalWires") } return nil, nil } func (net *SNetwork) IsSupportIPv4() bool { return len(net.GuestIpStart) > 0 && len(net.GuestIpEnd) > 0 } func (net *SNetwork) IsSupportIPv6() bool { return len(net.GuestIp6Start) > 0 && len(net.GuestIp6End) > 0 } func (net *SNetwork) OnMetadataUpdated(ctx context.Context, userCred mcclient.TokenCredential) { if len(net.ExternalId) == 0 || options.Options.KeepTagLocalization { return } vpc, err := net.GetVpc() if err != nil { return } if account := vpc.GetCloudaccount(); account != nil && account.ReadOnly { return } err = net.StartRemoteUpdateTask(ctx, userCred, true, "") if err != nil { log.Errorf("StartRemoteUpdateTask fail: %s", err) } } func (net *SNetwork) StartRemoteUpdateTask(ctx context.Context, userCred mcclient.TokenCredential, replaceTags bool, parentTaskId string) error { data := jsonutils.NewDict() if replaceTags { data.Add(jsonutils.JSONTrue, "replace_tags") } task, err := taskman.TaskManager.NewTask(ctx, "NetworkRemoteUpdateTask", net, userCred, data, parentTaskId, "", nil) if err != nil { return errors.Wrap(err, "Start NetworkRemoteUpdateTask") } net.SetStatus(ctx, userCred, apis.STATUS_UPDATE_TAGS, "StartRemoteUpdateTask") return task.ScheduleRun(nil) } func (net SNetwork) HasIPv4Addr() bool { return len(net.GuestIpStart) > 0 && len(net.GuestIpEnd) > 0 } func (net SNetwork) HasIPv6Addr() bool { return len(net.GuestIp6Start) > 0 && len(net.GuestIp6End) > 0 } func (manager *SNetworkManager) CustomizeFilterList(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*db.CustomizeListFilters, error) { filters := db.NewCustomizeListFilters() if query.Contains("usable") { isUsable := jsonutils.QueryBoolean(query, "usable", false) addrCountFilter := func(obj jsonutils.JSONObject) (bool, error) { portsStats := struct { PortsUsed int Ports6Used int Ports int Ports6 int }{} err := obj.Unmarshal(&portsStats) if err != nil { return false, err } if portsStats.PortsUsed < portsStats.Ports || portsStats.Ports6Used < portsStats.Ports6 { return true, nil } return false, nil } if isUsable { filters.Append(addrCountFilter) } } return filters, nil }