github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/address.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"reflect"
    10  	"sort"
    11  	"strconv"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/mgo/v3"
    15  	"github.com/juju/mgo/v3/bson"
    16  	"github.com/juju/mgo/v3/txn"
    17  	jujutxn "github.com/juju/txn/v3"
    18  
    19  	k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants"
    20  	"github.com/juju/juju/core/network"
    21  	"github.com/juju/juju/mongo"
    22  )
    23  
    24  // controllerAddresses returns the list of internal addresses of the state
    25  // server machines.
    26  func (st *State) controllerAddresses() ([]string, error) {
    27  	cinfo, err := st.ControllerInfo()
    28  	if err != nil {
    29  		return nil, errors.Trace(err)
    30  	}
    31  
    32  	var machines mongo.Collection
    33  	var closer SessionCloser
    34  	model, err := st.Model()
    35  	if err != nil {
    36  		return nil, errors.Trace(err)
    37  	}
    38  	if model.ModelTag() == cinfo.ModelTag {
    39  		machines, closer = st.db().GetCollection(machinesC)
    40  	} else {
    41  		machines, closer = st.db().GetCollectionFor(cinfo.ModelTag.Id(), machinesC)
    42  	}
    43  	defer closer()
    44  
    45  	type addressMachine struct {
    46  		Addresses []address
    47  	}
    48  	var allAddresses []addressMachine
    49  	// TODO(rog) 2013/10/14 index machines on jobs.
    50  	err = machines.Find(bson.D{{"jobs", JobManageModel}}).All(&allAddresses)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	if len(allAddresses) == 0 {
    55  		return nil, errors.New("no controller machines found")
    56  	}
    57  	apiAddrs := make([]string, 0, len(allAddresses))
    58  	for _, addrs := range allAddresses {
    59  		addr, ok := networkAddresses(addrs.Addresses).OneMatchingScope(network.ScopeMatchCloudLocal)
    60  		if ok {
    61  			apiAddrs = append(apiAddrs, addr.Value)
    62  		}
    63  	}
    64  	if len(apiAddrs) == 0 {
    65  		return nil, errors.New("no controller machines with addresses found")
    66  	}
    67  	return apiAddrs, nil
    68  }
    69  
    70  func appendPort(addrs []string, port int) []string {
    71  	newAddrs := make([]string, len(addrs))
    72  	for i, addr := range addrs {
    73  		newAddrs[i] = net.JoinHostPort(addr, strconv.Itoa(port))
    74  	}
    75  	return newAddrs
    76  }
    77  
    78  // Addresses returns the list of cloud-internal addresses that
    79  // can be used to connect to the state.
    80  func (st *State) Addresses() ([]string, error) {
    81  	addrs, err := st.controllerAddresses()
    82  	if err != nil {
    83  		return nil, errors.Trace(err)
    84  	}
    85  	config, err := st.ControllerConfig()
    86  	if err != nil {
    87  		return nil, errors.Trace(err)
    88  	}
    89  	return appendPort(addrs, config.StatePort()), nil
    90  }
    91  
    92  const (
    93  	// Key for *all* addresses at which controllers are accessible.
    94  	apiHostPortsKey = "apiHostPorts"
    95  	// Key for addresses at which controllers are accessible by agents.
    96  	apiHostPortsForAgentsKey = "apiHostPortsForAgents"
    97  )
    98  
    99  type apiHostPortsDoc struct {
   100  	APIHostPorts [][]hostPort `bson:"apihostports"`
   101  	TxnRevno     int64        `bson:"txn-revno"`
   102  }
   103  
   104  // SetAPIHostPorts sets the addresses, if changed, of two collections:
   105  //   - The list of *all* addresses at which the API is accessible.
   106  //   - The list of addresses at which the API can be accessed by agents according
   107  //     to the controller management space configuration.
   108  //
   109  // Each server is represented by one element in the top level slice.
   110  func (st *State) SetAPIHostPorts(newHostPorts []network.SpaceHostPorts) error {
   111  	controllers, closer := st.db().GetCollection(controllersC)
   112  	defer closer()
   113  
   114  	buildTxn := func(attempt int) ([]txn.Op, error) {
   115  		ops, err := st.getOpsForHostPortsChange(controllers, apiHostPortsKey, newHostPorts)
   116  		if err != nil {
   117  			return nil, errors.Trace(err)
   118  		}
   119  
   120  		newHostPortsForAgents, err := st.filterHostPortsForManagementSpace(newHostPorts)
   121  		if err != nil {
   122  			return nil, errors.Trace(err)
   123  		}
   124  		agentAddrOps, err := st.getOpsForHostPortsChange(controllers, apiHostPortsForAgentsKey, newHostPortsForAgents)
   125  		if err != nil {
   126  			return nil, errors.Trace(err)
   127  		}
   128  		ops = append(ops, agentAddrOps...)
   129  
   130  		if len(ops) == 0 {
   131  			return nil, jujutxn.ErrNoOperations
   132  		}
   133  		return ops, nil
   134  	}
   135  
   136  	if err := st.db().Run(buildTxn); err != nil {
   137  		return errors.Annotate(err, "cannot set API addresses")
   138  	}
   139  	return nil
   140  }
   141  
   142  // getOpsForHostPortsChange returns a slice of operations used to update an
   143  // API host/collection in the DB.
   144  // If the current document indicates the same host/port collection as the
   145  // input, no operations are returned.
   146  func (st *State) getOpsForHostPortsChange(
   147  	mc mongo.Collection, key string, newHostPorts []network.SpaceHostPorts,
   148  ) ([]txn.Op, error) {
   149  	var ops []txn.Op
   150  
   151  	// Retrieve the current document. Return an insert operation if not found.
   152  	var extantHostPortDoc apiHostPortsDoc
   153  	err := mc.Find(bson.D{{"_id", key}}).One(&extantHostPortDoc)
   154  	if err != nil {
   155  		if err == mgo.ErrNotFound {
   156  			return []txn.Op{{
   157  				C:      controllersC,
   158  				Id:     key,
   159  				Insert: bson.D{{"apihostports", fromNetworkHostsPorts(newHostPorts)}},
   160  			}}, nil
   161  		}
   162  		return ops, err
   163  	}
   164  
   165  	// Queue an update operation if the host/port collections differ.
   166  	extantHostPorts := networkHostsPorts(extantHostPortDoc.APIHostPorts)
   167  	if !hostsPortsEqual(newHostPorts, extantHostPorts) {
   168  		ops = []txn.Op{{
   169  			C:  controllersC,
   170  			Id: key,
   171  			Assert: bson.D{{
   172  				"txn-revno", extantHostPortDoc.TxnRevno,
   173  			}},
   174  			Update: bson.D{{
   175  				"$set", bson.D{{"apihostports", fromNetworkHostsPorts(newHostPorts)}},
   176  			}},
   177  		}}
   178  		logger.Debugf("setting %s: %v", key, newHostPorts)
   179  	}
   180  	return ops, nil
   181  }
   182  
   183  // filterHostPortsForManagementSpace filters the collection of API addresses
   184  // based on the configured management space for the controller.
   185  // If there is no space configured, or if one of the slices is filtered down
   186  // to zero elements, just use the unfiltered slice for safety - we do not
   187  // want to cut off communication to the controller based on erroneous config.
   188  func (st *State) filterHostPortsForManagementSpace(
   189  	apiHostPorts []network.SpaceHostPorts,
   190  ) ([]network.SpaceHostPorts, error) {
   191  	config, err := st.ControllerConfig()
   192  	if err != nil {
   193  		return nil, errors.Trace(err)
   194  	}
   195  
   196  	var hostPortsForAgents []network.SpaceHostPorts
   197  	if mgmtSpace := config.JujuManagementSpace(); mgmtSpace != "" {
   198  		sp, err := st.SpaceByName(mgmtSpace)
   199  		if err != nil {
   200  			return nil, errors.Trace(err)
   201  		}
   202  		spaceInfo, err := sp.NetworkSpace()
   203  		if err != nil {
   204  			return nil, errors.Trace(err)
   205  		}
   206  
   207  		hostPortsForAgents = make([]network.SpaceHostPorts, len(apiHostPorts))
   208  		for i := range apiHostPorts {
   209  			if filtered, ok := apiHostPorts[i].InSpaces(spaceInfo); ok {
   210  				hostPortsForAgents[i] = filtered
   211  			} else {
   212  				hostPortsForAgents[i] = apiHostPorts[i]
   213  			}
   214  		}
   215  	} else {
   216  		hostPortsForAgents = apiHostPorts
   217  	}
   218  
   219  	return hostPortsForAgents, nil
   220  }
   221  
   222  // APIHostPortsForClients returns the collection of *all* known API addresses.
   223  func (st *State) APIHostPortsForClients() ([]network.SpaceHostPorts, error) {
   224  	isCAASCtrl, err := st.isCAASController()
   225  	if err != nil {
   226  		return nil, errors.Trace(err)
   227  	}
   228  	if isCAASCtrl {
   229  		// TODO(caas): add test for this once we have the replacement for Jujuconnsuite.
   230  		return st.apiHostPortsForCAAS(true)
   231  	}
   232  
   233  	hp, err := st.apiHostPortsForKey(apiHostPortsKey)
   234  	if err != nil {
   235  		err = errors.Trace(err)
   236  	}
   237  	return hp, err
   238  }
   239  
   240  // APIHostPortsForAgents returns the collection of API addresses that should
   241  // be used by agents.
   242  // If the controller model is CAAS type, the return will be the controller
   243  // k8s service addresses in cloud service.
   244  // If there is no management network space configured for the controller,
   245  // or if the space is misconfigured, the return will be the same as
   246  // APIHostPortsForClients.
   247  // Otherwise the returned addresses will correspond with the management net space.
   248  // If there is no document at all, we simply fall back to APIHostPortsForClients.
   249  func (st *State) APIHostPortsForAgents() ([]network.SpaceHostPorts, error) {
   250  	isCAASCtrl, err := st.isCAASController()
   251  	if err != nil {
   252  		return nil, errors.Trace(err)
   253  	}
   254  	if isCAASCtrl {
   255  		// TODO(caas): add test for this once we have the replacement for Jujuconnsuite.
   256  		return st.apiHostPortsForCAAS(false)
   257  	}
   258  
   259  	hps, err := st.apiHostPortsForKey(apiHostPortsForAgentsKey)
   260  	if err != nil {
   261  		if err == mgo.ErrNotFound {
   262  			logger.Debugf("No document for %s; using %s", apiHostPortsForAgentsKey, apiHostPortsKey)
   263  			return st.APIHostPortsForClients()
   264  		}
   265  		return nil, errors.Trace(err)
   266  	}
   267  	return hps, nil
   268  }
   269  
   270  func (st *State) isCAASController() (bool, error) {
   271  	m := &Model{st: st}
   272  	if err := m.refresh(st.ControllerModelUUID()); err != nil {
   273  		return false, errors.Trace(err)
   274  	}
   275  	return m.IsControllerModel() && m.Type() == ModelTypeCAAS, nil
   276  }
   277  
   278  func (st *State) apiHostPortsForCAAS(public bool) (addresses []network.SpaceHostPorts, err error) {
   279  	defer func() {
   280  		logger.Debugf("getting api hostports for CAAS: public %t, addresses %v", public, addresses)
   281  	}()
   282  
   283  	if st.ModelUUID() != st.controllerModelTag.Id() {
   284  		return nil, errors.Errorf("CAAS API host ports only available on the controller model, not %q", st.ModelUUID())
   285  	}
   286  
   287  	controllerConfig, err := st.ControllerConfig()
   288  	if err != nil {
   289  		return nil, errors.Trace(err)
   290  	}
   291  
   292  	apiPort := controllerConfig.APIPort()
   293  	svc, err := st.CloudService(controllerConfig.ControllerUUID())
   294  	if err != nil {
   295  		return nil, errors.Trace(err)
   296  	}
   297  	addrs := svc.Addresses()
   298  
   299  	addrsToHostPorts := func(addrs ...network.SpaceAddress) []network.SpaceHostPorts {
   300  		return []network.SpaceHostPorts{network.SpaceAddressesWithPort(addrs, apiPort)}
   301  	}
   302  
   303  	// Return all publicly available addresses for clients.
   304  	// Scope matching falls back through a hierarchy,
   305  	// so these may actually be local-cloud addresses.
   306  	publicAddrs := addrs.AllMatchingScope(network.ScopeMatchPublic)
   307  	if public {
   308  		return addrsToHostPorts(publicAddrs...), nil
   309  	}
   310  
   311  	var hostAddresses network.SpaceAddresses
   312  	// Add in the FQDN of the controller service for agents to use as an option.
   313  	controllerName := controllerConfig.ControllerName()
   314  	if controllerName != "" {
   315  		hostAddresses = append(
   316  			hostAddresses, network.NewSpaceAddress(
   317  				fmt.Sprintf(k8sconstants.ControllerServiceFQDNTemplate, controllerName),
   318  				network.WithScope(network.ScopeCloudLocal),
   319  			))
   320  	}
   321  
   322  	// TODO(wallyworld) - for now, return all addresses for agents to try, public last.
   323  
   324  	// If we are after local-cloud addresses and those were all that public
   325  	// matching turned up, just return those.
   326  	if len(publicAddrs) > 0 && publicAddrs[0].Scope == network.ScopeCloudLocal {
   327  		return addrsToHostPorts(append(hostAddresses, publicAddrs...)...), nil
   328  	}
   329  
   330  	localAddrs := addrs.AllMatchingScope(network.ScopeMatchCloudLocal)
   331  
   332  	// If there were no local-cloud addresses, return the public ones.
   333  	if len(localAddrs) == 0 || localAddrs[0].Scope == network.ScopePublic {
   334  		return addrsToHostPorts(append(hostAddresses, publicAddrs...)...), nil
   335  	}
   336  
   337  	// Otherwise return everything, local-cloud first.
   338  	hostAddresses = append(hostAddresses, localAddrs...)
   339  	hostAddresses = append(hostAddresses, publicAddrs...)
   340  	return addrsToHostPorts(hostAddresses...), nil
   341  }
   342  
   343  // apiHostPortsForKey returns API addresses extracted from the document
   344  // identified by the input key.
   345  func (st *State) apiHostPortsForKey(key string) ([]network.SpaceHostPorts, error) {
   346  	var doc apiHostPortsDoc
   347  	controllers, closer := st.db().GetCollection(controllersC)
   348  	defer closer()
   349  	err := controllers.Find(bson.D{{"_id", key}}).One(&doc)
   350  	if err != nil {
   351  		return nil, err
   352  	}
   353  	return networkHostsPorts(doc.APIHostPorts), nil
   354  }
   355  
   356  // address represents the location of a machine, including metadata
   357  // about what kind of location the address describes.
   358  //
   359  // TODO(dimitern) Make sure we integrate this with other networking
   360  // stuff at some point. We want to use juju-specific network names
   361  // that point to existing documents in the networks collection.
   362  type address struct {
   363  	Value       string `bson:"value"`
   364  	AddressType string `bson:"addresstype"`
   365  	Scope       string `bson:"networkscope,omitempty"`
   366  	Origin      string `bson:"origin,omitempty"`
   367  	SpaceID     string `bson:"spaceid,omitempty"`
   368  }
   369  
   370  // fromNetworkAddress is a convenience helper to create a state type
   371  // out of the network type, here for Address with a given Origin.
   372  func fromNetworkAddress(netAddr network.SpaceAddress, origin network.Origin) address {
   373  	return address{
   374  		Value:       netAddr.Value,
   375  		AddressType: string(netAddr.Type),
   376  		Scope:       string(netAddr.Scope),
   377  		Origin:      string(origin),
   378  		SpaceID:     netAddr.SpaceID,
   379  	}
   380  }
   381  
   382  // networkAddress is a convenience helper to return the state type
   383  // as network type, here for Address.
   384  func (addr *address) networkAddress() network.SpaceAddress {
   385  	return network.SpaceAddress{
   386  		MachineAddress: network.MachineAddress{
   387  			Value: addr.Value,
   388  			Type:  network.AddressType(addr.AddressType),
   389  			Scope: network.Scope(addr.Scope),
   390  		},
   391  		SpaceID: addr.SpaceID,
   392  	}
   393  }
   394  
   395  // fromNetworkAddresses is a convenience helper to create a state type
   396  // out of the network type, here for a slice of Address with a given origin.
   397  func fromNetworkAddresses(netAddrs network.SpaceAddresses, origin network.Origin) []address {
   398  	addrs := make([]address, len(netAddrs))
   399  	for i, netAddr := range netAddrs {
   400  		addrs[i] = fromNetworkAddress(netAddr, origin)
   401  	}
   402  	return addrs
   403  }
   404  
   405  // networkAddresses is a convenience helper to return the state type
   406  // as network type, here for a slice of Address.
   407  func networkAddresses(addrs []address) network.SpaceAddresses {
   408  	netAddrs := make(network.SpaceAddresses, len(addrs))
   409  	for i, addr := range addrs {
   410  		netAddrs[i] = addr.networkAddress()
   411  	}
   412  	return netAddrs
   413  }
   414  
   415  // hostPort associates an address with a port. See also network.SpaceHostPort,
   416  // from/to which this is transformed.
   417  //
   418  // TODO(dimitern) Make sure we integrate this with other networking
   419  // stuff at some point. We want to use juju-specific network names
   420  // that point to existing documents in the networks collection.
   421  type hostPort struct {
   422  	Value       string `bson:"value"`
   423  	AddressType string `bson:"addresstype"`
   424  	Scope       string `bson:"networkscope,omitempty"`
   425  	Port        int    `bson:"port"`
   426  	SpaceID     string `bson:"spaceid,omitempty"`
   427  }
   428  
   429  // fromNetworkHostPort is a convenience helper to create a state type
   430  // out of the network type, here for SpaceHostPort.
   431  func fromNetworkHostPort(netHostPort network.SpaceHostPort) hostPort {
   432  	return hostPort{
   433  		Value:       netHostPort.Value,
   434  		AddressType: string(netHostPort.Type),
   435  		Scope:       string(netHostPort.Scope),
   436  		Port:        netHostPort.Port(),
   437  		SpaceID:     netHostPort.SpaceID,
   438  	}
   439  }
   440  
   441  // networkHostPort is a convenience helper to return the state type
   442  // as network type, here for SpaceHostPort.
   443  func (hp *hostPort) networkHostPort() network.SpaceHostPort {
   444  	return network.SpaceHostPort{
   445  		SpaceAddress: network.SpaceAddress{
   446  			MachineAddress: network.MachineAddress{
   447  				Value: hp.Value,
   448  				Type:  network.AddressType(hp.AddressType),
   449  				Scope: network.Scope(hp.Scope),
   450  			},
   451  			SpaceID: hp.SpaceID,
   452  		},
   453  		NetPort: network.NetPort(hp.Port),
   454  	}
   455  }
   456  
   457  // fromNetworkHostsPorts is a helper to create a state type
   458  // out of the network type, here for a nested slice of SpaceHostPort.
   459  func fromNetworkHostsPorts(netHostsPorts []network.SpaceHostPorts) [][]hostPort {
   460  	hsps := make([][]hostPort, len(netHostsPorts))
   461  	for i, netHostPorts := range netHostsPorts {
   462  		hsps[i] = make([]hostPort, len(netHostPorts))
   463  		for j, netHostPort := range netHostPorts {
   464  			hsps[i][j] = fromNetworkHostPort(netHostPort)
   465  		}
   466  	}
   467  	return hsps
   468  }
   469  
   470  // networkHostsPorts is a convenience helper to return the state type
   471  // as network type, here for a nested slice of SpaceHostPort.
   472  func networkHostsPorts(hsps [][]hostPort) []network.SpaceHostPorts {
   473  	netHostsPorts := make([]network.SpaceHostPorts, len(hsps))
   474  	for i, hps := range hsps {
   475  		netHostsPorts[i] = make(network.SpaceHostPorts, len(hps))
   476  		for j, hp := range hps {
   477  			netHostsPorts[i][j] = hp.networkHostPort()
   478  		}
   479  	}
   480  	return netHostsPorts
   481  }
   482  
   483  // addressEqual checks that two slices of network addresses are equal.
   484  func addressesEqual(a, b []network.SpaceAddress) bool {
   485  	return reflect.DeepEqual(a, b)
   486  }
   487  
   488  func dupeAndSort(a []network.SpaceHostPorts) []network.SpaceHostPorts {
   489  	var result []network.SpaceHostPorts
   490  
   491  	for _, val := range a {
   492  		var inner network.SpaceHostPorts
   493  		inner = append(inner, val...)
   494  		sort.Sort(inner)
   495  		result = append(result, inner)
   496  	}
   497  	sort.Sort(hostsPortsSlice(result))
   498  	return result
   499  }
   500  
   501  type hostsPortsSlice []network.SpaceHostPorts
   502  
   503  func (hp hostsPortsSlice) Len() int      { return len(hp) }
   504  func (hp hostsPortsSlice) Swap(i, j int) { hp[i], hp[j] = hp[j], hp[i] }
   505  func (hp hostsPortsSlice) Less(i, j int) bool {
   506  	lhs := (hostPortsSlice)(hp[i]).String()
   507  	rhs := (hostPortsSlice)(hp[j]).String()
   508  	return lhs < rhs
   509  }
   510  
   511  type hostPortsSlice []network.SpaceHostPort
   512  
   513  func (hp hostPortsSlice) String() string {
   514  	var result string
   515  	for _, val := range hp {
   516  		result += fmt.Sprintf("%s-%d ", val.SpaceAddress, val.Port())
   517  	}
   518  	return result
   519  }
   520  
   521  // hostsPortsEqual checks that two arrays of network hostports are equal.
   522  func hostsPortsEqual(a, b []network.SpaceHostPorts) bool {
   523  	// Make a copy of all the values so we don't mutate the args in order
   524  	// to determine if they are the same while we mutate the slice to order them.
   525  	aPrime := dupeAndSort(a)
   526  	bPrime := dupeAndSort(b)
   527  	return reflect.DeepEqual(aPrime, bPrime)
   528  }
   529  
   530  func (st *State) ConvertSpaceHostPorts(sHPs network.SpaceHostPorts) (network.ProviderHostPorts, error) {
   531  	addrs := make(network.ProviderHostPorts, len(sHPs))
   532  	for i, sAddr := range sHPs {
   533  		var err error
   534  		if addrs[i], err = st.ConvertSpaceHostPort(sAddr); err != nil {
   535  			return nil, errors.Trace(err)
   536  		}
   537  	}
   538  	return addrs, nil
   539  }
   540  
   541  func (st *State) ConvertSpaceHostPort(sHP network.SpaceHostPort) (network.ProviderHostPort, error) {
   542  	hp := network.ProviderHostPort{
   543  		ProviderAddress: network.ProviderAddress{MachineAddress: sHP.MachineAddress},
   544  		NetPort:         sHP.NetPort,
   545  	}
   546  	if sHP.SpaceID != "" {
   547  		space, err := st.Space(sHP.SpaceID)
   548  		if err != nil {
   549  			return hp, errors.Trace(err)
   550  		}
   551  		hp.SpaceName = network.SpaceName(space.Name())
   552  	}
   553  	return hp, nil
   554  }