github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/network/hostport.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package network 5 6 import ( 7 "net" 8 "strconv" 9 "strings" 10 11 "github.com/juju/collections/set" 12 "github.com/juju/errors" 13 ) 14 15 // HostPort describes methods on an object that 16 // represents a network connection endpoint. 17 type HostPort interface { 18 Address 19 Port() int 20 } 21 22 // HostPorts derives from a slice of HostPort 23 // and allows bulk operations on its members. 24 type HostPorts []HostPort 25 26 // FilterUnusable returns a copy of the receiver HostPorts after removing 27 // any addresses unlikely to be usable (ScopeMachineLocal or ScopeLinkLocal). 28 func (hps HostPorts) FilterUnusable() HostPorts { 29 filtered := make(HostPorts, 0, len(hps)) 30 for _, addr := range hps { 31 switch addr.AddressScope() { 32 case ScopeMachineLocal, ScopeLinkLocal: 33 continue 34 } 35 filtered = append(filtered, addr) 36 } 37 return filtered 38 } 39 40 // Strings returns the HostPorts as a slice of 41 // strings suitable for passing to net.Dial. 42 func (hps HostPorts) Strings() []string { 43 result := make([]string, len(hps)) 44 for i, addr := range hps { 45 result[i] = DialAddress(addr) 46 } 47 return result 48 } 49 50 // Unique returns a copy of the receiver HostPorts with duplicate endpoints 51 // removed. Note that this only applies to dial addresses; spaces are ignored. 52 func (hps HostPorts) Unique() HostPorts { 53 results := make([]HostPort, 0, len(hps)) 54 seen := set.NewStrings() 55 56 for _, addr := range hps { 57 da := DialAddress(addr) 58 if seen.Contains(da) { 59 continue 60 } 61 62 seen.Add(da) 63 results = append(results, addr) 64 } 65 return results 66 } 67 68 // PrioritizedForScope orders the HostPorts by best match for the input scope 69 // matching function and returns them in NetAddr form. 70 // If there are no suitable addresses then an empty slice is returned. 71 func (hps HostPorts) PrioritizedForScope(getMatcher ScopeMatchFunc) []string { 72 indexes := indexesByScopeMatch(hps, getMatcher) 73 out := make([]string, len(indexes)) 74 for i, index := range indexes { 75 out[i] = DialAddress(hps[index]) 76 } 77 return out 78 } 79 80 // DialAddress returns a string value for the input HostPort, 81 // suitable for passing as an argument to net.Dial. 82 func DialAddress(a HostPort) string { 83 return net.JoinHostPort(a.Host(), strconv.Itoa(a.Port())) 84 } 85 86 // NetPort represents a network port. 87 // TODO (manadart 2019-08-15): Finish deprecation of `Port` and use that name. 88 type NetPort int 89 90 // Port returns the port number. 91 func (p NetPort) Port() int { 92 return int(p) 93 } 94 95 // MachineHostPort associates a space-unaware address with a port. 96 type MachineHostPort struct { 97 MachineAddress 98 NetPort 99 } 100 101 var _ HostPort = MachineHostPort{} 102 103 // String implements Stringer. 104 func (hp MachineHostPort) String() string { 105 return DialAddress(hp) 106 } 107 108 // GoString implements fmt.GoStringer. 109 func (hp MachineHostPort) GoString() string { 110 return hp.String() 111 } 112 113 // MachineHostPorts is a slice of MachineHostPort 114 // allowing use as a receiver for bulk operations. 115 type MachineHostPorts []MachineHostPort 116 117 // HostPorts returns the slice as a new slice of the HostPort indirection. 118 func (hp MachineHostPorts) HostPorts() HostPorts { 119 addrs := make(HostPorts, len(hp)) 120 for i, hp := range hp { 121 addrs[i] = hp 122 } 123 return addrs 124 } 125 126 // NewMachineHostPorts creates a list of MachineHostPorts 127 // from each given string address and port. 128 func NewMachineHostPorts(port int, addresses ...string) MachineHostPorts { 129 hps := make(MachineHostPorts, len(addresses)) 130 for i, addr := range addresses { 131 hps[i] = MachineHostPort{ 132 MachineAddress: NewMachineAddress(addr), 133 NetPort: NetPort(port), 134 } 135 } 136 return hps 137 } 138 139 // ParseMachineHostPort converts a string containing a 140 // single host and port value to a MachineHostPort. 141 func ParseMachineHostPort(hp string) (*MachineHostPort, error) { 142 host, port, err := net.SplitHostPort(hp) 143 if err != nil { 144 return nil, errors.Annotatef(err, "cannot parse %q as address:port", hp) 145 } 146 numPort, err := strconv.Atoi(port) 147 if err != nil { 148 return nil, errors.Annotatef(err, "cannot parse %q port", hp) 149 } 150 return &MachineHostPort{ 151 MachineAddress: NewMachineAddress(host), 152 NetPort: NetPort(numPort), 153 }, nil 154 } 155 156 // CollapseToHostPorts returns the input nested slice of MachineHostPort 157 // as a flat slice of HostPort, preserving the order. 158 func CollapseToHostPorts(serversHostPorts []MachineHostPorts) HostPorts { 159 var collapsed HostPorts 160 for _, hps := range serversHostPorts { 161 for _, hp := range hps { 162 collapsed = append(collapsed, hp) 163 } 164 } 165 return collapsed 166 } 167 168 // ProviderHostPort associates a provider/space aware address with a port. 169 type ProviderHostPort struct { 170 ProviderAddress 171 NetPort 172 } 173 174 var _ HostPort = ProviderHostPort{} 175 176 // String implements Stringer. 177 func (hp ProviderHostPort) String() string { 178 return DialAddress(hp) 179 } 180 181 // GoString implements fmt.GoStringer. 182 func (hp ProviderHostPort) GoString() string { 183 return hp.String() 184 } 185 186 // ProviderHostPorts is a slice of ProviderHostPort 187 // allowing use as a receiver for bulk operations. 188 type ProviderHostPorts []ProviderHostPort 189 190 // Addresses extracts the ProviderAddress from each member of the collection, 191 // then returns them as a new collection, effectively discarding the port. 192 func (hp ProviderHostPorts) Addresses() ProviderAddresses { 193 addrs := make(ProviderAddresses, len(hp)) 194 for i, hp := range hp { 195 addrs[i] = hp.ProviderAddress 196 } 197 return addrs 198 } 199 200 // HostPorts returns the slice as a new slice of the HostPort indirection. 201 func (hp ProviderHostPorts) HostPorts() HostPorts { 202 addrs := make(HostPorts, len(hp)) 203 for i, hp := range hp { 204 addrs[i] = hp 205 } 206 return addrs 207 } 208 209 // ParseProviderHostPorts creates a slice of MachineHostPorts parsing 210 // each given string containing address:port. 211 // An error is returned if any string cannot be parsed as a MachineHostPort. 212 func ParseProviderHostPorts(hostPorts ...string) (ProviderHostPorts, error) { 213 hps := make(ProviderHostPorts, len(hostPorts)) 214 for i, hp := range hostPorts { 215 mhp, err := ParseMachineHostPort(hp) 216 if err != nil { 217 return nil, errors.Trace(err) 218 } 219 hps[i] = ProviderHostPort{ 220 ProviderAddress: ProviderAddress{MachineAddress: mhp.MachineAddress}, 221 NetPort: mhp.NetPort, 222 } 223 } 224 return hps, nil 225 } 226 227 // SpaceHostPort associates a space ID decorated address with a port. 228 type SpaceHostPort struct { 229 SpaceAddress 230 NetPort 231 } 232 233 var _ HostPort = SpaceHostPort{} 234 235 // String implements Stringer. 236 func (hp SpaceHostPort) String() string { 237 return DialAddress(hp) 238 } 239 240 // GoString implements fmt.GoStringer. 241 func (hp SpaceHostPort) GoString() string { 242 return hp.String() 243 } 244 245 // Less reports whether hp is ordered before hp2 246 // according to the criteria used by SortHostPorts. 247 func (hp SpaceHostPort) Less(hp2 SpaceHostPort) bool { 248 order1 := SortOrderMostPublic(hp) 249 order2 := SortOrderMostPublic(hp2) 250 if order1 == order2 { 251 if hp.SpaceAddress.Value == hp2.SpaceAddress.Value { 252 return hp.Port() < hp2.Port() 253 } 254 return hp.SpaceAddress.Value < hp2.SpaceAddress.Value 255 } 256 return order1 < order2 257 } 258 259 // SpaceHostPorts is a slice of SpaceHostPort 260 // allowing use as a receiver for bulk operations. 261 type SpaceHostPorts []SpaceHostPort 262 263 // NewSpaceHostPorts creates a list of SpaceHostPorts 264 // from each input string address and port. 265 func NewSpaceHostPorts(port int, addresses ...string) SpaceHostPorts { 266 hps := make(SpaceHostPorts, len(addresses)) 267 for i, addr := range addresses { 268 hps[i] = SpaceHostPort{ 269 SpaceAddress: NewSpaceAddress(addr), 270 NetPort: NetPort(port), 271 } 272 } 273 return hps 274 } 275 276 // HostPorts returns the slice as a new slice of the HostPort indirection. 277 func (hps SpaceHostPorts) HostPorts() HostPorts { 278 addrs := make(HostPorts, len(hps)) 279 for i, hp := range hps { 280 addrs[i] = hp 281 } 282 return addrs 283 } 284 285 // InSpaces returns the SpaceHostPorts that are in the input spaces. 286 func (hps SpaceHostPorts) InSpaces(spaces ...SpaceInfo) (SpaceHostPorts, bool) { 287 if len(spaces) == 0 { 288 logger.Errorf("host ports not filtered - no spaces given.") 289 return hps, false 290 } 291 292 spaceInfos := SpaceInfos(spaces) 293 var selectedHostPorts SpaceHostPorts 294 for _, hp := range hps { 295 if space := spaceInfos.GetByID(hp.SpaceID); space != nil { 296 logger.Debugf("selected %q as a hostPort in space %q", hp.Value, space.Name) 297 selectedHostPorts = append(selectedHostPorts, hp) 298 } 299 } 300 301 if len(selectedHostPorts) > 0 { 302 return selectedHostPorts, true 303 } 304 305 logger.Errorf("no hostPorts found in spaces %s", spaceInfos) 306 return hps, false 307 } 308 309 // AllMatchingScope returns the HostPorts that best satisfy the input scope 310 // matching function, as strings usable as arguments to net.Dial. 311 func (hps SpaceHostPorts) AllMatchingScope(getMatcher ScopeMatchFunc) []string { 312 indexes := indexesForScope(hps, getMatcher) 313 out := make([]string, 0, len(indexes)) 314 for _, index := range indexes { 315 out = append(out, DialAddress(hps[index])) 316 } 317 return out 318 } 319 320 // ToProviderHostPorts transforms the SpaceHostPorts to ProviderHostPorts 321 // by using the input lookup for conversion of space ID to space info. 322 func (hps SpaceHostPorts) ToProviderHostPorts(lookup SpaceLookup) (ProviderHostPorts, error) { 323 if hps == nil { 324 return nil, nil 325 } 326 327 var spaces SpaceInfos 328 if len(hps) > 0 { 329 var err error 330 if spaces, err = lookup.AllSpaceInfos(); err != nil { 331 return nil, errors.Trace(err) 332 } 333 } 334 335 pHPs := make(ProviderHostPorts, len(hps)) 336 for i, hp := range hps { 337 pHPs[i] = ProviderHostPort{ 338 ProviderAddress: ProviderAddress{MachineAddress: hp.MachineAddress}, 339 NetPort: hp.NetPort, 340 } 341 342 if hp.SpaceID != "" { 343 info := spaces.GetByID(hp.SpaceID) 344 if info == nil { 345 return nil, errors.NotFoundf("space with ID %q", hp.SpaceID) 346 } 347 pHPs[i].SpaceName = info.Name 348 pHPs[i].ProviderSpaceID = info.ProviderId 349 } 350 } 351 return pHPs, nil 352 } 353 354 func (hps SpaceHostPorts) Len() int { return len(hps) } 355 func (hps SpaceHostPorts) Swap(i, j int) { hps[i], hps[j] = hps[j], hps[i] } 356 func (hps SpaceHostPorts) Less(i, j int) bool { 357 return hps[i].Less(hps[j]) 358 } 359 360 // SpaceAddressesWithPort returns the input SpaceAddresses 361 // all associated with the given port. 362 func SpaceAddressesWithPort(addrs SpaceAddresses, port int) SpaceHostPorts { 363 hps := make(SpaceHostPorts, len(addrs)) 364 for i, addr := range addrs { 365 hps[i] = SpaceHostPort{ 366 SpaceAddress: addr, 367 NetPort: NetPort(port), 368 } 369 } 370 return hps 371 } 372 373 // APIHostPortsToNoProxyString converts list of lists of NetAddrs() to 374 // a NoProxy-like comma separated string, ignoring local addresses 375 func APIHostPortsToNoProxyString(ahp []SpaceHostPorts) string { 376 noProxySet := set.NewStrings() 377 for _, host := range ahp { 378 for _, hp := range host { 379 if hp.SpaceAddress.Scope == ScopeMachineLocal || hp.SpaceAddress.Scope == ScopeLinkLocal { 380 continue 381 } 382 noProxySet.Add(hp.SpaceAddress.Value) 383 } 384 } 385 return strings.Join(noProxySet.SortedValues(), ",") 386 } 387 388 // EnsureFirstHostPort scans the given list of SpaceHostPorts and if 389 // "first" is found, it moved to index 0. Otherwise, if "first" is not 390 // in the list, it's inserted at index 0. 391 func EnsureFirstHostPort(first SpaceHostPort, hps SpaceHostPorts) SpaceHostPorts { 392 var result []SpaceHostPort 393 found := false 394 for _, hp := range hps { 395 if hp.String() == first.String() && !found { 396 // Found, so skip it. 397 found = true 398 continue 399 } 400 result = append(result, hp) 401 } 402 // Insert it at the top. 403 result = append(SpaceHostPorts{first}, result...) 404 return result 405 }