github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/network/address.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package network 5 6 import ( 7 "bytes" 8 "encoding/binary" 9 "fmt" 10 "net" 11 "sort" 12 "strings" 13 14 "github.com/juju/errors" 15 "github.com/juju/utils/set" 16 ) 17 18 // Private network ranges for IPv4 and IPv6. 19 // See: http://tools.ietf.org/html/rfc1918 20 // Also: http://tools.ietf.org/html/rfc4193 21 var ( 22 classAPrivate = mustParseCIDR("10.0.0.0/8") 23 classBPrivate = mustParseCIDR("172.16.0.0/12") 24 classCPrivate = mustParseCIDR("192.168.0.0/16") 25 ipv6UniqueLocal = mustParseCIDR("fc00::/7") 26 ) 27 28 const ( 29 // LoopbackIPv4CIDR is the loopback CIDR range for IPv4. 30 LoopbackIPv4CIDR = "127.0.0.0/8" 31 32 // LoopbackIPv6CIDR is the loopback CIDR range for IPv6. 33 LoopbackIPv6CIDR = "::1/128" 34 ) 35 36 func mustParseCIDR(s string) *net.IPNet { 37 _, net, err := net.ParseCIDR(s) 38 if err != nil { 39 panic(err) 40 } 41 return net 42 } 43 44 // AddressType represents the possible ways of specifying a machine location by 45 // either a hostname resolvable by dns lookup, or IPv4 or IPv6 address. 46 type AddressType string 47 48 const ( 49 HostName AddressType = "hostname" 50 IPv4Address AddressType = "ipv4" 51 IPv6Address AddressType = "ipv6" 52 ) 53 54 // Scope denotes the context a location may apply to. If a name or 55 // address can be reached from the wider internet, it is considered 56 // public. A private network address is either specific to the cloud 57 // or cloud subnet a machine belongs to, or to the machine itself for 58 // containers. 59 type Scope string 60 61 // SpaceName holds the Juju space name of an address. 62 type SpaceName string 63 type spaceNameList []SpaceName 64 65 func (s spaceNameList) String() string { 66 namesString := make([]string, len(s)) 67 for i, v := range s { 68 namesString[i] = string(v) 69 } 70 71 return strings.Join(namesString, ", ") 72 } 73 74 func (s spaceNameList) IndexOf(name SpaceName) int { 75 for i := range s { 76 if s[i] == name { 77 return i 78 } 79 } 80 return -1 81 } 82 83 const ( 84 ScopeUnknown Scope = "" 85 ScopePublic Scope = "public" 86 ScopeCloudLocal Scope = "local-cloud" 87 ScopeMachineLocal Scope = "local-machine" 88 ScopeLinkLocal Scope = "link-local" 89 ) 90 91 // Address represents the location of a machine, including metadata 92 // about what kind of location the address describes. 93 type Address struct { 94 Value string 95 Type AddressType 96 Scope 97 SpaceName 98 SpaceProviderId Id 99 } 100 101 // String returns a string representation of the address, in the form: 102 // `<scope>:<address-value>@<space-name>(id:<space-provider-id)`; for example: 103 // 104 // public:c2-54-226-162-124.compute-1.amazonaws.com@public-api(id:42) 105 // 106 // If the scope is ScopeUnknown, the initial "<scope>:" prefix will be omitted. 107 // If the SpaceName is blank, the "@<space-name>" suffix will be omitted. 108 // Finally, if the SpaceProviderId is empty the suffix 109 // "(id:<space-provider-id>)" part will be omitted as well. 110 func (a Address) String() string { 111 var buf bytes.Buffer 112 if a.Scope != ScopeUnknown { 113 buf.WriteString(string(a.Scope)) 114 buf.WriteByte(':') 115 } 116 buf.WriteString(a.Value) 117 118 var spaceFound bool 119 if a.SpaceName != "" { 120 spaceFound = true 121 buf.WriteByte('@') 122 buf.WriteString(string(a.SpaceName)) 123 } 124 if a.SpaceProviderId != Id("") { 125 if !spaceFound { 126 buf.WriteByte('@') 127 } 128 buf.WriteString(fmt.Sprintf("(id:%v)", string(a.SpaceProviderId))) 129 } 130 return buf.String() 131 } 132 133 // GoString implements fmt.GoStringer. 134 func (a Address) GoString() string { 135 return a.String() 136 } 137 138 // NewAddress creates a new Address, deriving its type from the value 139 // and using ScopeUnknown as scope. It's a shortcut to calling 140 // NewScopedAddress(value, ScopeUnknown). 141 func NewAddress(value string) Address { 142 return NewScopedAddress(value, ScopeUnknown) 143 } 144 145 // NewScopedAddress creates a new Address, deriving its type from the 146 // value. 147 // 148 // If the specified scope is ScopeUnknown, then NewScopedAddress will 149 // attempt derive the scope based on reserved IP address ranges. 150 // Because passing ScopeUnknown is fairly common, NewAddress() above 151 // does exactly that. 152 func NewScopedAddress(value string, scope Scope) Address { 153 addr := Address{ 154 Value: value, 155 Type: DeriveAddressType(value), 156 Scope: scope, 157 } 158 if scope == ScopeUnknown { 159 addr.Scope = deriveScope(addr) 160 } 161 return addr 162 } 163 164 // NewAddressOnSpace creates a new Address, deriving its type and scope from the 165 // value and associating it with the given spaceName. 166 func NewAddressOnSpace(spaceName string, value string) Address { 167 addr := NewAddress(value) 168 addr.SpaceName = SpaceName(spaceName) 169 return addr 170 } 171 172 // NewAddresses is a convenience function to create addresses from a a variable 173 // number of string arguments. 174 func NewAddresses(inAddresses ...string) (outAddresses []Address) { 175 outAddresses = make([]Address, len(inAddresses)) 176 for i, address := range inAddresses { 177 outAddresses[i] = NewAddress(address) 178 } 179 return outAddresses 180 } 181 182 // NewAddressesOnSpace is a convenience function to create addresses on the same 183 // space, from a a variable number of string arguments. 184 func NewAddressesOnSpace(spaceName string, inAddresses ...string) (outAddresses []Address) { 185 outAddresses = make([]Address, len(inAddresses)) 186 for i, address := range inAddresses { 187 outAddresses[i] = NewAddressOnSpace(spaceName, address) 188 } 189 return outAddresses 190 } 191 192 // DeriveAddressType attempts to detect the type of address given. 193 func DeriveAddressType(value string) AddressType { 194 ip := net.ParseIP(value) 195 switch { 196 case ip == nil: 197 // TODO(gz): Check value is a valid hostname 198 return HostName 199 case ip.To4() != nil: 200 return IPv4Address 201 case ip.To16() != nil: 202 return IPv6Address 203 default: 204 panic("Unknown form of IP address") 205 } 206 } 207 208 func isIPv4PrivateNetworkAddress(addrType AddressType, ip net.IP) bool { 209 if addrType != IPv4Address { 210 return false 211 } 212 return classAPrivate.Contains(ip) || 213 classBPrivate.Contains(ip) || 214 classCPrivate.Contains(ip) 215 } 216 217 func isIPv6UniqueLocalAddress(addrType AddressType, ip net.IP) bool { 218 if addrType != IPv6Address { 219 return false 220 } 221 return ipv6UniqueLocal.Contains(ip) 222 } 223 224 // deriveScope attempts to derive the network scope from an address's 225 // type and value, returning the original network scope if no 226 // deduction can be made. 227 func deriveScope(addr Address) Scope { 228 if addr.Type == HostName { 229 return addr.Scope 230 } 231 ip := net.ParseIP(addr.Value) 232 if ip == nil { 233 return addr.Scope 234 } 235 if ip.IsLoopback() { 236 return ScopeMachineLocal 237 } 238 if isIPv4PrivateNetworkAddress(addr.Type, ip) || 239 isIPv6UniqueLocalAddress(addr.Type, ip) { 240 return ScopeCloudLocal 241 } 242 if ip.IsLinkLocalMulticast() || 243 ip.IsLinkLocalUnicast() || 244 ip.IsInterfaceLocalMulticast() { 245 return ScopeLinkLocal 246 } 247 if ip.IsGlobalUnicast() { 248 return ScopePublic 249 } 250 return addr.Scope 251 } 252 253 // ExactScopeMatch checks if an address exactly matches any of the specified 254 // scopes. An address will not match if globalPreferIPv6 is set and it isn't an 255 // IPv6 address. 256 func ExactScopeMatch(addr Address, addrScopes ...Scope) bool { 257 if PreferIPv6() && addr.Type != IPv6Address { 258 return false 259 } 260 for _, scope := range addrScopes { 261 if addr.Scope == scope { 262 return true 263 } 264 } 265 return false 266 } 267 268 // SelectAddressBySpaces picks the first address from the given slice that has 269 // the given space name associated. 270 func SelectAddressBySpaces(addresses []Address, spaceNames ...SpaceName) (Address, bool) { 271 for _, addr := range addresses { 272 if spaceNameList(spaceNames).IndexOf(addr.SpaceName) >= 0 { 273 logger.Debugf("selected %q as first address in space %q", addr.Value, addr.SpaceName) 274 return addr, true 275 } 276 } 277 278 if len(spaceNames) == 0 { 279 logger.Warningf("no spaces to select addresses from") 280 } else { 281 logger.Warningf("no addresses found in spaces %s", spaceNames) 282 } 283 return Address{}, false 284 } 285 286 // SelectHostsPortBySpaces picks the first HostPort from the given slice that has 287 // the given space name associated. 288 func SelectHostsPortBySpaces(hps []HostPort, spaceNames ...SpaceName) ([]HostPort, bool) { 289 if len(spaceNames) == 0 { 290 logger.Warningf("host ports not filtered - no spaces given.") 291 return hps, false 292 } 293 294 var selectedHostPorts []HostPort 295 for _, hp := range hps { 296 if spaceNameList(spaceNames).IndexOf(hp.SpaceName) >= 0 { 297 logger.Debugf("selected %q as a hostPort in space %q", hp.Value, hp.SpaceName) 298 selectedHostPorts = append(selectedHostPorts, hp) 299 } 300 } 301 302 if len(selectedHostPorts) > 0 { 303 return selectedHostPorts, true 304 } 305 306 logger.Warningf("no hostPorts found in spaces %s", spaceNames) 307 return hps, false 308 } 309 310 // SelectControllerAddress returns the most suitable address to use as a Juju 311 // Controller (API/state server) endpoint given the list of addresses. 312 // The second return value is false when no address can be returned. 313 // When machineLocal is true both ScopeCloudLocal and ScopeMachineLocal 314 // addresses are considered during the selection, otherwise just ScopeCloudLocal are. 315 func SelectControllerAddress(addresses []Address, machineLocal bool) (Address, bool) { 316 internalAddress, ok := SelectInternalAddress(addresses, machineLocal) 317 logger.Debugf( 318 "selected %q as controller address, using scope %q", 319 internalAddress.Value, internalAddress.Scope, 320 ) 321 return internalAddress, ok 322 } 323 324 // SelectMongoHostPorts returns the most suitable HostPort (as string) to 325 // use as a Juju Controller (API/state server) endpoint given the list of 326 // hostPorts. It first tries to find the first HostPort bound to the 327 // spaces provided, then, if that fails, uses the older selection method based on scope. 328 // When machineLocal is true and an address can't be selected by space both 329 // ScopeCloudLocal and ScopeMachineLocal addresses are considered during the 330 // selection, otherwise just ScopeCloudLocal are. 331 func SelectMongoHostPortsBySpaces(hostPorts []HostPort, spaces []SpaceName) ([]string, bool) { 332 filteredHostPorts, ok := SelectHostsPortBySpaces(hostPorts, spaces...) 333 if ok { 334 logger.Debugf( 335 "selected %q as controller host:port, using spaces %q", 336 filteredHostPorts, spaces, 337 ) 338 } 339 return HostPortsToStrings(filteredHostPorts), ok 340 } 341 342 func SelectMongoHostPortsByScope(hostPorts []HostPort, machineLocal bool) []string { 343 // Fallback to using the legacy and error-prone approach using scope 344 // selection instead. 345 internalHP := SelectInternalHostPort(hostPorts, machineLocal) 346 logger.Debugf( 347 "selected %q as controller host:port, using scope selection", 348 internalHP, 349 ) 350 return []string{internalHP} 351 } 352 353 // SelectPublicAddress picks one address from a slice that would be 354 // appropriate to display as a publicly accessible endpoint. If there 355 // are no suitable addresses, then ok is false (and an empty address is 356 // returned). If a suitable address is then ok is true. 357 func SelectPublicAddress(addresses []Address) (Address, bool) { 358 index := bestAddressIndex(len(addresses), PreferIPv6(), func(i int) Address { 359 return addresses[i] 360 }, publicMatch) 361 if index < 0 { 362 return Address{}, false 363 } 364 return addresses[index], true 365 } 366 367 // SelectPublicHostPort picks one HostPort from a slice that would be 368 // appropriate to display as a publicly accessible endpoint. If there 369 // are no suitable candidates, the empty string is returned. 370 func SelectPublicHostPort(hps []HostPort) string { 371 index := bestAddressIndex(len(hps), PreferIPv6(), func(i int) Address { 372 return hps[i].Address 373 }, publicMatch) 374 if index < 0 { 375 return "" 376 } 377 return hps[index].NetAddr() 378 } 379 380 // SelectInternalAddress picks one address from a slice that can be 381 // used as an endpoint for juju internal communication. If there are 382 // are no suitable addresses, then ok is false (and an empty address is 383 // returned). If a suitable address was found then ok is true. 384 func SelectInternalAddress(addresses []Address, machineLocal bool) (Address, bool) { 385 index := bestAddressIndex(len(addresses), PreferIPv6(), func(i int) Address { 386 return addresses[i] 387 }, internalAddressMatcher(machineLocal)) 388 if index < 0 { 389 return Address{}, false 390 } 391 return addresses[index], true 392 } 393 394 // SelectInternalHostPort picks one HostPort from a slice that can be 395 // used as an endpoint for juju internal communication and returns it 396 // in its NetAddr form. If there are no suitable addresses, the empty 397 // string is returned. 398 func SelectInternalHostPort(hps []HostPort, machineLocal bool) string { 399 index := bestAddressIndex(len(hps), PreferIPv6(), func(i int) Address { 400 return hps[i].Address 401 }, internalAddressMatcher(machineLocal)) 402 if index < 0 { 403 return "" 404 } 405 return hps[index].NetAddr() 406 } 407 408 // SelectInternalHostPorts picks the best matching HostPorts from a 409 // slice that can be used as an endpoint for juju internal 410 // communication and returns them in NetAddr form. If there are no 411 // suitable addresses, an empty slice is returned. 412 func SelectInternalHostPorts(hps []HostPort, machineLocal bool) []string { 413 indexes := bestAddressIndexes(len(hps), PreferIPv6(), func(i int) Address { 414 return hps[i].Address 415 }, internalAddressMatcher(machineLocal)) 416 417 out := make([]string, 0, len(indexes)) 418 for _, index := range indexes { 419 out = append(out, hps[index].NetAddr()) 420 } 421 return out 422 } 423 424 // PrioritizeInternalHostPorts orders the provided addresses by best 425 // match for use as an endpoint for juju internal communication and 426 // returns them in NetAddr form. If there are no suitable addresses 427 // then an empty slice is returned. 428 func PrioritizeInternalHostPorts(hps []HostPort, machineLocal bool) []string { 429 indexes := prioritizedAddressIndexes(len(hps), PreferIPv6(), func(i int) Address { 430 return hps[i].Address 431 }, internalAddressMatcher(machineLocal)) 432 433 out := make([]string, 0, len(indexes)) 434 for _, index := range indexes { 435 out = append(out, hps[index].NetAddr()) 436 } 437 return out 438 } 439 440 func publicMatch(addr Address, preferIPv6 bool) scopeMatch { 441 switch addr.Scope { 442 case ScopePublic: 443 return mayPreferIPv6(addr, exactScope, preferIPv6) 444 case ScopeCloudLocal, ScopeUnknown: 445 return mayPreferIPv6(addr, fallbackScope, preferIPv6) 446 } 447 return invalidScope 448 } 449 450 // mayPreferIPv6 returns mismatchedTypeExactScope or 451 // mismatchedTypeFallbackScope (depending on originalScope) if addr's 452 // type is IPv4, and preferIPv6 is true. When preferIPv6 is false, or 453 // addr's type is IPv6 and preferIPv6 is true, returns the 454 // originalScope unchanged. 455 func mayPreferIPv6(addr Address, originalScope scopeMatch, preferIPv6 bool) scopeMatch { 456 if preferIPv6 && addr.Type != IPv6Address { 457 switch originalScope { 458 case exactScope: 459 return mismatchedTypeExactScope 460 case fallbackScope: 461 return mismatchedTypeFallbackScope 462 } 463 return invalidScope 464 } 465 return originalScope 466 } 467 468 func internalAddressMatcher(machineLocal bool) func(Address, bool) scopeMatch { 469 if machineLocal { 470 return cloudOrMachineLocalMatch 471 } 472 return cloudLocalMatch 473 } 474 475 func cloudLocalMatch(addr Address, preferIPv6 bool) scopeMatch { 476 switch addr.Scope { 477 case ScopeCloudLocal: 478 return mayPreferIPv6(addr, exactScope, preferIPv6) 479 case ScopePublic, ScopeUnknown: 480 return mayPreferIPv6(addr, fallbackScope, preferIPv6) 481 } 482 return invalidScope 483 } 484 485 func cloudOrMachineLocalMatch(addr Address, preferIPv6 bool) scopeMatch { 486 if addr.Scope == ScopeMachineLocal { 487 return mayPreferIPv6(addr, exactScope, preferIPv6) 488 } 489 return cloudLocalMatch(addr, preferIPv6) 490 } 491 492 type scopeMatch int 493 494 const ( 495 invalidScope scopeMatch = iota 496 exactScope 497 fallbackScope 498 mismatchedTypeExactScope 499 mismatchedTypeFallbackScope 500 ) 501 502 // bestAddressIndexes returns the indexes of the addresses with the 503 // best matching scope (according to the match func). An empty slice 504 // is returned if there were no suitable addresses. 505 func bestAddressIndex(numAddr int, preferIPv6 bool, getAddr func(i int) Address, match func(addr Address, preferIPv6 bool) scopeMatch) int { 506 indexes := bestAddressIndexes(numAddr, preferIPv6, getAddr, match) 507 if len(indexes) > 0 { 508 return indexes[0] 509 } 510 return -1 511 } 512 513 // bestAddressIndexes returns the indexes of the addresses with the 514 // best matching scope and type (according to the match func). An 515 // empty slice is returned if there were no suitable addresses. 516 func bestAddressIndexes(numAddr int, preferIPv6 bool, getAddr func(i int) Address, match func(addr Address, preferIPv6 bool) scopeMatch) []int { 517 // Categorise addresses by scope and type matching quality. 518 matches := filterAndCollateAddressIndexes(numAddr, preferIPv6, getAddr, match) 519 520 // Retrieve the indexes of the addresses with the best scope and type match. 521 allowedMatchTypes := []scopeMatch{exactScope, fallbackScope} 522 if preferIPv6 { 523 allowedMatchTypes = append(allowedMatchTypes, mismatchedTypeExactScope, mismatchedTypeFallbackScope) 524 } 525 for _, matchType := range allowedMatchTypes { 526 indexes, ok := matches[matchType] 527 if ok && len(indexes) > 0 { 528 return indexes 529 } 530 } 531 return []int{} 532 } 533 534 func prioritizedAddressIndexes(numAddr int, preferIPv6 bool, getAddr func(i int) Address, match func(addr Address, preferIPv6 bool) scopeMatch) []int { 535 // Categorise addresses by scope and type matching quality. 536 matches := filterAndCollateAddressIndexes(numAddr, preferIPv6, getAddr, match) 537 538 // Retrieve the indexes of the addresses with the best scope and type match. 539 allowedMatchTypes := []scopeMatch{exactScope, fallbackScope} 540 if preferIPv6 { 541 allowedMatchTypes = append(allowedMatchTypes, mismatchedTypeExactScope, mismatchedTypeFallbackScope) 542 } 543 var prioritized []int 544 for _, matchType := range allowedMatchTypes { 545 indexes, ok := matches[matchType] 546 if ok && len(indexes) > 0 { 547 prioritized = append(prioritized, indexes...) 548 } 549 } 550 return prioritized 551 } 552 553 func filterAndCollateAddressIndexes(numAddr int, preferIPv6 bool, getAddr func(i int) Address, match func(addr Address, preferIPv6 bool) scopeMatch) map[scopeMatch][]int { 554 // Categorise addresses by scope and type matching quality. 555 matches := make(map[scopeMatch][]int) 556 for i := 0; i < numAddr; i++ { 557 matchType := match(getAddr(i), preferIPv6) 558 switch matchType { 559 case exactScope, fallbackScope, mismatchedTypeExactScope, mismatchedTypeFallbackScope: 560 matches[matchType] = append(matches[matchType], i) 561 } 562 } 563 return matches 564 } 565 566 // sortOrder calculates the "weight" of the address when sorting, 567 // taking into account the preferIPv6 flag: 568 // - public IPs first; 569 // - hostnames after that, but "localhost" will be last if present; 570 // - cloud-local next; 571 // - machine-local next; 572 // - link-local next; 573 // - non-hostnames with unknown scope last. 574 // 575 // When preferIPv6 flag and the address type do not match, the order 576 // is incremented to put non-preferred addresses after preferred. 577 func (a Address) sortOrder(preferIPv6 bool) int { 578 order := 0xFF 579 switch a.Scope { 580 case ScopePublic: 581 order = 0x00 582 case ScopeCloudLocal: 583 order = 0x20 584 case ScopeMachineLocal: 585 order = 0x40 586 case ScopeLinkLocal: 587 order = 0x80 588 } 589 switch a.Type { 590 case HostName: 591 order = 0x10 592 if a.Value == "localhost" { 593 order++ 594 } 595 case IPv4Address: 596 if preferIPv6 { 597 order++ 598 } 599 case IPv6Address: 600 if !preferIPv6 { 601 order++ 602 } 603 } 604 return order 605 } 606 607 type addressesPreferringIPv4Slice []Address 608 609 func (a addressesPreferringIPv4Slice) Len() int { return len(a) } 610 func (a addressesPreferringIPv4Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 611 func (a addressesPreferringIPv4Slice) Less(i, j int) bool { 612 addr1 := a[i] 613 addr2 := a[j] 614 order1 := addr1.sortOrder(false) 615 order2 := addr2.sortOrder(false) 616 if order1 == order2 { 617 return addr1.Value < addr2.Value 618 } 619 return order1 < order2 620 } 621 622 type addressesPreferringIPv6Slice struct { 623 addressesPreferringIPv4Slice 624 } 625 626 func (a addressesPreferringIPv6Slice) Less(i, j int) bool { 627 addr1 := a.addressesPreferringIPv4Slice[i] 628 addr2 := a.addressesPreferringIPv4Slice[j] 629 order1 := addr1.sortOrder(true) 630 order2 := addr2.sortOrder(true) 631 if order1 == order2 { 632 return addr1.Value < addr2.Value 633 } 634 return order1 < order2 635 } 636 637 // SortAddresses sorts the given Address slice according to the 638 // sortOrder of each address and the preferIpv6 flag. See 639 // Address.sortOrder() for more info. 640 func SortAddresses(addrs []Address, preferIPv6 bool) { 641 if preferIPv6 { 642 sort.Sort(addressesPreferringIPv6Slice{addressesPreferringIPv4Slice(addrs)}) 643 } else { 644 sort.Sort(addressesPreferringIPv4Slice(addrs)) 645 } 646 } 647 648 // DecimalToIPv4 converts a decimal to the dotted quad IP address format. 649 func DecimalToIPv4(addr uint32) net.IP { 650 bytes := make([]byte, 4) 651 binary.BigEndian.PutUint32(bytes, addr) 652 return net.IP(bytes) 653 } 654 655 // IPv4ToDecimal converts a dotted quad IP address to its decimal equivalent. 656 func IPv4ToDecimal(ipv4Addr net.IP) (uint32, error) { 657 ip := ipv4Addr.To4() 658 if ip == nil { 659 return 0, errors.Errorf("%q is not a valid IPv4 address", ipv4Addr.String()) 660 } 661 return binary.BigEndian.Uint32([]byte(ip)), nil 662 } 663 664 // ResolvableHostnames returns the set of all DNS resolvable names 665 // from addrs. Note that 'localhost' is always considered resolvable 666 // because it can be used both as an IPv4 or IPv6 endpoint (e.g., in 667 // IPv6-only networks). 668 func ResolvableHostnames(addrs []Address) []Address { 669 resolveableAddrs := make([]Address, 0, len(addrs)) 670 for _, addr := range addrs { 671 if addr.Value == "localhost" || net.ParseIP(addr.Value) != nil { 672 resolveableAddrs = append(resolveableAddrs, addr) 673 continue 674 } 675 _, err := netLookupIP(addr.Value) 676 if err != nil { 677 logger.Infof("removing unresolvable address %q: %v", addr.Value, err) 678 continue 679 } 680 resolveableAddrs = append(resolveableAddrs, addr) 681 } 682 return resolveableAddrs 683 } 684 685 // MergedAddresses provides a single list of addresses without duplicates 686 // suitable for returning as an address list for a machine. 687 // TODO (cherylj) Add explicit unit tests - tracked with bug #1544158 688 func MergedAddresses(machineAddresses, providerAddresses []Address) []Address { 689 merged := make([]Address, 0, len(providerAddresses)+len(machineAddresses)) 690 providerValues := set.NewStrings() 691 for _, address := range providerAddresses { 692 // Older versions of Juju may have stored an empty address so ignore it here. 693 if address.Value == "" || providerValues.Contains(address.Value) { 694 continue 695 } 696 providerValues.Add(address.Value) 697 merged = append(merged, address) 698 } 699 for _, address := range machineAddresses { 700 if !providerValues.Contains(address.Value) { 701 merged = append(merged, address) 702 } 703 } 704 return merged 705 }