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  }