yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/esxi/net_topology_pro.go (about) 1 // Copyright 2019 Yunion 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package esxi 16 17 import ( 18 "context" 19 "fmt" 20 "sort" 21 "sync" 22 23 "github.com/vmware/govmomi/vim25/mo" 24 "github.com/vmware/govmomi/vim25/types" 25 "golang.org/x/sync/errgroup" 26 27 "yunion.io/x/jsonutils" 28 "yunion.io/x/log" 29 "yunion.io/x/pkg/errors" 30 "yunion.io/x/pkg/util/netutils" 31 "yunion.io/x/pkg/util/regutils" 32 ) 33 34 var ( 35 SIMPLE_HOST_PROPS = []string{"name", "config.network", "vm"} 36 SIMPLE_VM_PROPS = []string{"name", "guest.net", "config.template", "config.hardware.device"} 37 SIMPLE_DVPG_PROPS = []string{"key", "config.defaultPortConfig", "config.distributedVirtualSwitch"} 38 SIMPLE_DVS_PROPS = []string{"name", "config"} 39 SIMPLE_NETWORK_PROPS = []string{"name"} 40 ) 41 42 type SIPVlan struct { 43 IP netutils.IPV4Addr 44 VlanId int32 45 } 46 47 type SSimpleVM struct { 48 Name string 49 IPVlans []SIPVlan 50 } 51 52 type SSimpleHostDev struct { 53 Name string 54 Id string 55 Mac string 56 } 57 58 func (cli *SESXiClient) scanAllDvPortgroups() ([]*SDistributedVirtualPortgroup, error) { 59 var modvpgs []mo.DistributedVirtualPortgroup 60 err := cli.scanAllMObjects(SIMPLE_DVPG_PROPS, &modvpgs) 61 if err != nil { 62 return nil, errors.Wrap(err, "scanAllMObjects") 63 } 64 dvpgs := make([]*SDistributedVirtualPortgroup, 0, len(modvpgs)) 65 for i := range modvpgs { 66 dvpgs = append(dvpgs, NewDistributedVirtualPortgroup(cli, &modvpgs[i], nil)) 67 } 68 return dvpgs, nil 69 } 70 71 func (cli *SESXiClient) scanAllNetworks() ([]mo.Network, error) { 72 var monets []mo.Network 73 err := cli.scanAllMObjects(SIMPLE_NETWORK_PROPS, &monets) 74 if err != nil { 75 return nil, errors.Wrap(err, "scanAllMObjects") 76 } 77 return monets, nil 78 } 79 80 func (cli *SESXiClient) networkName(refV string) (string, error) { 81 if cli.networkQueryMap == nil { 82 nets, err := cli.scanAllNetworks() 83 if err != nil { 84 return "", err 85 } 86 cli.networkQueryMap = &sync.Map{} 87 for i := range nets { 88 cli.networkQueryMap.Store(nets[i].Reference().Value, nets[i].Name) 89 } 90 } 91 iter, ok := cli.networkQueryMap.Load(refV) 92 if !ok { 93 return "", nil 94 } 95 return iter.(string), nil 96 } 97 98 func (cli *SESXiClient) hostVMIPsPro(ctx context.Context, hosts []mo.HostSystem) (SNetworkInfoPro, error) { 99 ret := SNetworkInfoPro{ 100 SNetworkInfoBase: SNetworkInfoBase{ 101 VMs: []SSimpleVM{}, 102 IPPool: SIPPool{}, 103 }, 104 HostIps: make(map[string][]netutils.IPV4Addr), 105 } 106 vsMap, err := cli.getVirtualSwitchs(hosts) 107 if err != nil { 108 return ret, errors.Wrap(err, "unable to getVirtualSwitchs") 109 } 110 ret.VsMap = vsMap 111 dvpgMap, err := cli.getDVPGMap() 112 if err != nil { 113 return ret, errors.Wrap(err, "unable to get dvpgKeyVlanMap") 114 } 115 group, ctx := errgroup.WithContext(ctx) 116 collection := make([]*SNetworkInfoPro, len(hosts)) 117 for i := range hosts { 118 j := i 119 group.Go(func() error { 120 nInfo, err := cli.vmIPsPro(&hosts[j], dvpgMap) 121 if err != nil { 122 return err 123 } 124 collection[j] = nInfo.(*SNetworkInfoPro) 125 return nil 126 }) 127 } 128 129 err = group.Wait() 130 if err != nil { 131 return ret, err 132 } 133 cli.mergeNetworInfoPro(&ret, collection) 134 vsList := vsMap.List() 135 for i := range vsList { 136 vs := vsList[i] 137 for hName, ips := range vs.HostIps { 138 sort.Slice(ips, func(i, j int) bool { 139 return ips[i] < ips[j] 140 }) 141 for _, ip := range ips { 142 ret.IPPool.Insert(ip, SIPProc{ 143 VlanId: 0, 144 VSId: vs.Id, 145 IsHost: true, 146 }) 147 } 148 ret.HostIps[hName] = append(ret.HostIps[hName], ips...) 149 } 150 } 151 return ret, nil 152 } 153 154 func (cli *SESXiClient) mergeNetworInfoPro(ret *SNetworkInfoPro, nInfos []*SNetworkInfoPro) { 155 log.Infof("nInfos before mergeNetworInfoPro: %s", jsonutils.Marshal(nInfos)) 156 var vmsLen, ipPoolLen int 157 for i := range nInfos { 158 vmsLen += len(nInfos[i].VMs) 159 ipPoolLen += nInfos[i].IPPool.Len() 160 } 161 ret.VMs = make([]SSimpleVM, 0, vmsLen) 162 ret.IPPool = NewIPPool(ipPoolLen) 163 for i := range nInfos { 164 ret.VMs = append(ret.VMs, nInfos[i].VMs...) 165 ret.IPPool.Merge(&nInfos[i].IPPool) 166 ret.VsMap.Merge(&nInfos[i].VsMap) 167 } 168 } 169 170 func (cli *SESXiClient) HostVmIPsPro(ctx context.Context) (SNetworkInfoPro, error) { 171 var hosts []mo.HostSystem 172 err := cli.scanAllMObjects(SIMPLE_HOST_PROPS, &hosts) 173 if err != nil { 174 return SNetworkInfoPro{}, errors.Wrap(err, "scanAllMObjects") 175 } 176 filtedHosts := make([]mo.HostSystem, 0, len(hosts)) 177 for i := range hosts { 178 if hosts[i].Config == nil || hosts[i].Config.Network == nil { 179 continue 180 } 181 filtedHosts = append(filtedHosts, hosts[i]) 182 } 183 return cli.hostVMIPsPro(ctx, filtedHosts) 184 } 185 186 func vpgMapKey(prefix, key string) string { 187 if len(prefix) == 0 { 188 return key 189 } 190 return fmt.Sprintf("%s-%s", prefix, key) 191 } 192 193 func (cli *SESXiClient) macVlanMap(mohost *mo.HostSystem, movm *mo.VirtualMachine, dvpgMap sVPGMap) (map[string]SIPProc, error) { 194 vpgMap := cli.getVPGMap(mohost) 195 ret := make(map[string]SIPProc, 2) 196 for _, device := range movm.Config.Hardware.Device { 197 bcard, ok := device.(types.BaseVirtualEthernetCard) 198 if !ok { 199 continue 200 } 201 card := bcard.GetVirtualEthernetCard() 202 mac := card.MacAddress 203 switch bk := card.Backing.(type) { 204 case *types.VirtualEthernetCardDistributedVirtualPortBackingInfo: 205 key := vpgMapKey("", bk.Port.PortgroupKey) 206 proc, ok := dvpgMap.Get(key) 207 if !ok { 208 log.Errorf("dvpg %s not found in key-vlanid map", key) 209 continue 210 } 211 ret[mac] = proc 212 case *types.VirtualEthernetCardNetworkBackingInfo: 213 netName, err := cli.networkName(bk.Network.Value) 214 if err != nil { 215 return nil, errors.Wrapf(err, "cli.networkName of %q", bk.Network.Value) 216 } 217 key := vpgMapKey(mohost.Reference().Value, netName) 218 proc, ok := vpgMap.Get(key) 219 if !ok { 220 log.Errorf("vpg %s not found in key-vlanid map", key) 221 continue 222 } 223 ret[mac] = proc 224 } 225 } 226 return ret, nil 227 } 228 229 type SNetworkInfoPro struct { 230 SNetworkInfoBase 231 232 HostIps map[string][]netutils.IPV4Addr 233 VsMap SVirtualSwitchMap 234 } 235 236 func (s *SNetworkInfoPro) Insert(proc SIPProc, ip netutils.IPV4Addr) { 237 s.SNetworkInfoBase.Insert(proc, ip) 238 s.VsMap.AddVlanIp(proc.VSId, proc.VlanId, ip) 239 } 240 241 type SNetworkInfoBase struct { 242 VMs []SSimpleVM 243 IPPool SIPPool 244 } 245 246 func (s *SNetworkInfoBase) Insert(proc SIPProc, ip netutils.IPV4Addr) { 247 s.IPPool.Insert(ip, proc) 248 } 249 250 func (s *SNetworkInfoBase) AppendVMs(name string, IpVlans []SIPVlan) { 251 s.VMs = append(s.VMs, SSimpleVM{name, IpVlans}) 252 } 253 254 type INetworkInfo interface { 255 Insert(SIPProc, netutils.IPV4Addr) 256 AppendVMs(name string, IpVlans []SIPVlan) 257 } 258 259 func NewNetworkInfo(dvs bool, size int) INetworkInfo { 260 base := SNetworkInfoBase{ 261 VMs: make([]SSimpleVM, 0, size), 262 IPPool: NewIPPool(int(size)), 263 } 264 if dvs { 265 return &SNetworkInfoPro{ 266 SNetworkInfoBase: base, 267 VsMap: NewVirtualSwitchMap(), 268 } 269 } 270 return &SNetworkInfo{ 271 SNetworkInfoBase: base, 272 HostIps: make(map[string]netutils.IPV4Addr), 273 VlanIps: make(map[int32][]netutils.IPV4Addr), 274 } 275 } 276 277 func (cli *SESXiClient) vmIPsPro(host *mo.HostSystem, vpgMap sVPGMap) (INetworkInfo, error) { 278 nInfo := NewNetworkInfo(true, len(host.Vm)) 279 if len(host.Vm) == 0 { 280 return nInfo, nil 281 } 282 var vms []mo.VirtualMachine 283 err := cli.references2Objects(host.Vm, SIMPLE_VM_PROPS, &vms) 284 if err != nil { 285 return nInfo, errors.Wrap(err, "references2Objects") 286 } 287 for i := range vms { 288 vm := vms[i] 289 if vm.Config == nil || vm.Config.Template { 290 continue 291 } 292 if vm.Guest == nil { 293 continue 294 } 295 macVlanMap, err := cli.macVlanMap(host, &vm, vpgMap) 296 if err != nil { 297 return nInfo, err 298 } 299 guestIps := make([]SIPVlan, 0) 300 for _, net := range vm.Guest.Net { 301 if len(net.Network) == 0 { 302 continue 303 } 304 mac := net.MacAddress 305 for _, ip := range net.IpAddress { 306 if !regutils.MatchIP4Addr(ip) { 307 continue 308 } 309 if !vmIPV4Filter.Contains(ip) { 310 continue 311 } 312 ipaddr, _ := netutils.NewIPV4Addr(ip) 313 if netutils.IsLinkLocal(ipaddr) { 314 continue 315 } 316 proc := macVlanMap[mac] 317 guestIps = append(guestIps, SIPVlan{ 318 IP: ipaddr, 319 VlanId: proc.VlanId, 320 }) 321 nInfo.Insert(proc, ipaddr) 322 break 323 } 324 } 325 nInfo.AppendVMs(vm.Name, guestIps) 326 } 327 return nInfo, nil 328 } 329 330 type SVirtualSwitchSpec struct { 331 Name string 332 Id string 333 Distributed bool 334 Hosts []SSimpleHostDev 335 HostIps map[string][]netutils.IPV4Addr 336 Vlans map[int32][]netutils.IPV4Addr 337 } 338 339 func NewVirtualSwitch() *SVirtualSwitchSpec { 340 return &SVirtualSwitchSpec{ 341 HostIps: make(map[string][]netutils.IPV4Addr), 342 Vlans: make(map[int32][]netutils.IPV4Addr), 343 } 344 } 345 346 func (vs *SVirtualSwitchSpec) Merge(vsc *SVirtualSwitchSpec) { 347 for vlan, ips := range vsc.Vlans { 348 vs.Vlans[vlan] = insert(vs.Vlans[vlan], ips) 349 } 350 } 351 352 func insert(base []netutils.IPV4Addr, add []netutils.IPV4Addr) []netutils.IPV4Addr { 353 for _, ip := range add { 354 index := sort.Search(len(base), func(n int) bool { 355 return base[n] > ip 356 }) 357 base = append(base, 0) 358 base = append(base[:index+1], base[index:len(base)-1]...) 359 base[index] = ip 360 } 361 return base 362 } 363 364 type SVirtualSwitchMap struct { 365 vsList []SVirtualSwitchSpec 366 vsMap map[string]*SVirtualSwitchSpec 367 } 368 369 func NewVirtualSwitchMap(length ...int) SVirtualSwitchMap { 370 initLen := 0 371 if len(length) > 0 { 372 initLen = length[0] 373 } 374 return SVirtualSwitchMap{ 375 vsList: make([]SVirtualSwitchSpec, 0, initLen), 376 vsMap: make(map[string]*SVirtualSwitchSpec, initLen), 377 } 378 } 379 380 func (vsm *SVirtualSwitchMap) Insert(id string, vs SVirtualSwitchSpec) { 381 ovs, ok := vsm.vsMap[id] 382 if ok { 383 ovs.Merge(&vs) 384 return 385 } 386 vsm.vsList = append(vsm.vsList, vs) 387 vsm.vsMap[id] = &vsm.vsList[len(vsm.vsList)-1] 388 } 389 390 func (vms *SVirtualSwitchMap) Merge(ovsm *SVirtualSwitchMap) { 391 for i := range ovsm.vsList { 392 vs := ovsm.vsList[i] 393 vms.Insert(vs.Id, vs) 394 } 395 } 396 397 func (vsm *SVirtualSwitchMap) AddVlanIp(id string, vlanId int32, ip netutils.IPV4Addr) bool { 398 vs, ok := vsm.vsMap[id] 399 if !ok { 400 vs = NewVirtualSwitch() 401 vs.Id = id 402 vsm.Insert(id, *vs) 403 vs = vsm.vsMap[id] 404 } 405 vs.Vlans[vlanId] = append(vs.Vlans[vlanId], ip) 406 return true 407 } 408 409 func (vsm *SVirtualSwitchMap) List() []SVirtualSwitchSpec { 410 return vsm.vsList 411 } 412 413 func findIp(hs *mo.HostSystem, nic string) netutils.IPV4Addr { 414 for i := range hs.Config.Network.Pnic { 415 pnic := hs.Config.Network.Pnic[i] 416 if pnic.Device != nic { 417 continue 418 } 419 ip := pnic.Spec.Ip.IpAddress 420 if len(ip) == 0 { 421 return netutils.IPV4Addr(0) 422 } 423 if !regutils.MatchIP4Addr(ip) { 424 return netutils.IPV4Addr(0) 425 } 426 ipaddr, _ := netutils.NewIPV4Addr(ip) 427 if netutils.IsLinkLocal(ipaddr) { 428 return netutils.IPV4Addr(0) 429 } 430 return ipaddr 431 } 432 return netutils.IPV4Addr(0) 433 } 434 435 func getNicDevice(vs *types.HostVirtualSwitch) []string { 436 bridge := vs.Spec.Bridge 437 switch b := bridge.(type) { 438 case *types.HostVirtualSwitchAutoBridge: 439 return b.ExcludedNicDevice 440 case *types.HostVirtualSwitchBondBridge: 441 return b.NicDevice 442 case *types.HostVirtualSwitchSimpleBridge: 443 return []string{b.NicDevice} 444 default: 445 return nil 446 } 447 } 448 449 func (cli *SESXiClient) getVirtualSwitchs(hss []mo.HostSystem) (SVirtualSwitchMap, error) { 450 nic2Ip := make(map[string]netutils.IPV4Addr, len(hss)) 451 nic2Mac := make(map[string]string, len(hss)) 452 for j := range hss { 453 hs := hss[j] 454 macToIp := make(map[string]string, len(hs.Config.Network.Vnic)) 455 for i := range hs.Config.Network.Vnic { 456 vnic := hs.Config.Network.Vnic[i] 457 if len(vnic.Spec.Ip.IpAddress) == 0 { 458 continue 459 } 460 macToIp[vnic.Spec.Mac] = vnic.Spec.Ip.IpAddress 461 } 462 for i := range hs.Config.Network.ConsoleVnic { 463 vnic := hs.Config.Network.Vnic[i] 464 if len(vnic.Spec.Ip.IpAddress) == 0 { 465 continue 466 } 467 macToIp[vnic.Spec.Mac] = vnic.Spec.Ip.IpAddress 468 } 469 for i := range hs.Config.Network.Pnic { 470 pnic := hs.Config.Network.Pnic[i] 471 ip := pnic.Spec.Ip.IpAddress 472 nicKey := fmt.Sprintf("%s-%s", hs.Self.Value, pnic.Device) 473 nic2Mac[nicKey] = pnic.Mac 474 if len(ip) == 0 { 475 ip = macToIp[pnic.Mac] 476 } 477 if len(ip) == 0 { 478 log.Warningf("host %s pnic %s has no ipaddress", hs.Name, pnic.Device) 479 continue 480 } 481 if !regutils.MatchIP4Addr(ip) { 482 log.Warningf("host %s pnic %s ipaddress %q is not IPV4Addr", hs.Name, pnic.Device, ip) 483 continue 484 } 485 ipaddr, _ := netutils.NewIPV4Addr(ip) 486 if netutils.IsLinkLocal(ipaddr) { 487 log.Warningf("host %s pnic %s ipaddress %q is link local addr", hs.Name, pnic.Device, ip) 488 continue 489 } 490 nic2Ip[nicKey] = ipaddr 491 } 492 } 493 value2name := make(map[string]string, len(hss)) 494 for i := range hss { 495 value2name[hss[i].Self.Value] = hss[i].Name 496 } 497 // dvs 498 var dvss []mo.DistributedVirtualSwitch 499 err := cli.scanAllMObjects(SIMPLE_DVS_PROPS, &dvss) 500 if err != nil { 501 return SVirtualSwitchMap{}, err 502 } 503 ret := NewVirtualSwitchMap(len(dvss) + len(hss)) 504 for i := range dvss { 505 dvs := dvss[i] 506 vsId := dvs.Self.Value 507 vsName := fmt.Sprintf("%s", dvs.Name) 508 hosts := dvs.Config.GetDVSConfigInfo().Host 509 vs := SVirtualSwitchSpec{ 510 Name: vsName, 511 Id: vsId, 512 Distributed: true, 513 HostIps: make(map[string][]netutils.IPV4Addr, len(hosts)), 514 Vlans: make(map[int32][]netutils.IPV4Addr), 515 } 516 for _, host := range hosts { 517 hostName := value2name[host.Config.Host.Value] 518 vs.HostIps[hostName] = []netutils.IPV4Addr{} 519 hostDev := SSimpleHostDev{ 520 Id: host.Config.Host.Value, 521 Name: hostName, 522 } 523 switch back := host.Config.Backing.(type) { 524 case *types.DistributedVirtualSwitchHostMemberPnicBacking: 525 for i := range back.PnicSpec { 526 nicDevice := back.PnicSpec[i].PnicDevice 527 nicKey := fmt.Sprintf("%s-%s", host.Config.Host.Value, nicDevice) 528 hostDev.Mac = nic2Mac[nicKey] 529 ip, ok := nic2Ip[nicKey] 530 if ok { 531 vs.HostIps[hostName] = append(vs.HostIps[hostName], ip) 532 } 533 } 534 case *types.DistributedVirtualSwitchHostMemberBacking: 535 } 536 vs.Hosts = append(vs.Hosts, hostDev) 537 } 538 ret.Insert(vsId, vs) 539 } 540 // vs 541 for i := range hss { 542 hs := hss[i] 543 if hs.Config == nil || hs.Config.Network == nil { 544 continue 545 } 546 for i := range hs.Config.Network.Vswitch { 547 ivs := hs.Config.Network.Vswitch[i] 548 vsId := fmt.Sprintf("%s/%s", hs.Self.Value, ivs.Name) 549 vsName := fmt.Sprintf("%s/%s", hs.Name, ivs.Name) 550 vs := SVirtualSwitchSpec{ 551 Name: vsName, 552 Id: vsId, 553 Distributed: false, 554 HostIps: make(map[string][]netutils.IPV4Addr), 555 Vlans: make(map[int32][]netutils.IPV4Addr), 556 } 557 hostDev := SSimpleHostDev{ 558 Id: hs.Self.Value, 559 Name: hs.Name, 560 } 561 for _, nic := range getNicDevice(&ivs) { 562 nicKey := fmt.Sprintf("%s-%s", hs.Self.Value, nic) 563 log.Infof("nic: %s, nicKey: %s", nic, nicKey) 564 hostDev.Mac = nic2Mac[nicKey] 565 ip, ok := nic2Ip[nicKey] 566 if !ok { 567 continue 568 } 569 vs.HostIps[hs.Name] = append(vs.HostIps[hs.Name], ip) 570 } 571 vs.Hosts = append(vs.Hosts, hostDev) 572 ret.Insert(vsId, vs) 573 } 574 } 575 return ret, nil 576 } 577 578 func (cli *SESXiClient) getVPGMap(mohost *mo.HostSystem) sVPGMap { 579 sm := newVPGMap() 580 if mohost.Config == nil || mohost.Config.Network == nil { 581 return sm 582 } 583 for i := range mohost.Config.Network.Portgroup { 584 ipg := mohost.Config.Network.Portgroup[i] 585 key := vpgMapKey(mohost.Reference().Value, ipg.Spec.Name) 586 vlan := ipg.Spec.VlanId 587 vsId := fmt.Sprintf("%s/%s", mohost.Reference().Value, ipg.Spec.VswitchName) 588 sm.Insert(key, SIPProc{ 589 VlanId: vlan, 590 VSId: vsId, 591 }) 592 } 593 return sm 594 } 595 596 func (cli *SESXiClient) getDVPGMap() (sVPGMap, error) { 597 sm := newVPGMap() 598 dvpgs, err := cli.scanAllDvPortgroups() 599 if err != nil { 600 return sm, err 601 } 602 for i := range dvpgs { 603 modvpg := dvpgs[i].getMODVPortgroup() 604 key := vpgMapKey("", modvpg.Key) 605 vlanid := dvpgs[i].GetVlanId() 606 vsId := modvpg.Config.DistributedVirtualSwitch.Value 607 sm.Insert(key, SIPProc{ 608 VlanId: vlanid, 609 VSId: vsId, 610 }) 611 } 612 return sm, nil 613 } 614 615 func newVPGMap() sVPGMap { 616 return sVPGMap{m: &sync.Map{}} 617 } 618 619 type sVPGMap struct { 620 m *sync.Map 621 } 622 623 func (vm *sVPGMap) Insert(key string, proc SIPProc) { 624 vm.m.Store(key, proc) 625 } 626 627 func (vm *sVPGMap) Get(key string) (SIPProc, bool) { 628 v, ok := vm.m.Load(key) 629 if !ok { 630 return SIPProc{}, ok 631 } 632 r := v.(SIPProc) 633 return r, ok 634 } 635 636 type SIPProc struct { 637 VSId string 638 VlanId int32 639 IsHost bool 640 } 641 642 type SIPPool struct { 643 p map[netutils.IPV4Addr]SIPProc 644 } 645 646 func NewIPPool(size ...int) SIPPool { 647 if len(size) > 0 { 648 return SIPPool{p: make(map[netutils.IPV4Addr]SIPProc, size[0])} 649 } 650 return SIPPool{p: make(map[netutils.IPV4Addr]SIPProc)} 651 } 652 653 func (p *SIPPool) Has(ip netutils.IPV4Addr) bool { 654 _, ok := p.p[ip] 655 return ok 656 } 657 658 func (p *SIPPool) Get(ip netutils.IPV4Addr) (SIPProc, bool) { 659 r, ok := p.p[ip] 660 return r, ok 661 } 662 663 func (p *SIPPool) Insert(ip netutils.IPV4Addr, proc SIPProc) { 664 p.p[ip] = proc 665 } 666 667 func (p *SIPPool) Merge(op *SIPPool) { 668 for ip, proc := range op.p { 669 if p.Has(ip) { 670 continue 671 } 672 p.Insert(ip, proc) 673 } 674 } 675 676 func (p *SIPPool) Len() int { 677 return len(p.p) 678 }