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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/mgo/v3"
    12  	"github.com/juju/mgo/v3/bson"
    13  	"github.com/juju/mgo/v3/txn"
    14  	"github.com/juju/names/v5"
    15  
    16  	jujucontroller "github.com/juju/juju/controller"
    17  )
    18  
    19  const (
    20  	// ControllerSettingsGlobalKey is the key for the controller and its settings.
    21  	ControllerSettingsGlobalKey = "controllerSettings"
    22  
    23  	// controllerGlobalKey is the key for controller.
    24  	controllerGlobalKey = "c"
    25  )
    26  
    27  // controllerKey will return the key for a given controller using the
    28  // controller uuid and the controllerGlobalKey.
    29  func controllerKey(controllerUUID string) string {
    30  	return fmt.Sprintf("%s#%s", controllerGlobalKey, controllerUUID)
    31  }
    32  
    33  // Controller encapsulates state for the Juju controller as a whole,
    34  // as opposed to model specific functionality.
    35  //
    36  // This type is primarily used in the state.Initialize function, and
    37  // in the yet to be hooked up controller worker.
    38  type Controller struct {
    39  	pool     *StatePool
    40  	ownsPool bool
    41  }
    42  
    43  // NewController returns a controller object that doesn't own
    44  // the state pool it has been given. This is for convenience
    45  // at this time to get access to controller methods.
    46  func NewController(pool *StatePool) *Controller {
    47  	return &Controller{pool: pool}
    48  }
    49  
    50  // StatePool provides access to the state pool of the controller.
    51  func (ctlr *Controller) StatePool() *StatePool {
    52  	return ctlr.pool
    53  }
    54  
    55  // SystemState returns the State object for the controller model.
    56  func (ctlr *Controller) SystemState() (*State, error) {
    57  	return ctlr.pool.SystemState()
    58  }
    59  
    60  // Close the connection to the database.
    61  func (ctlr *Controller) Close() error {
    62  	if ctlr.ownsPool {
    63  		ctlr.pool.Close()
    64  	}
    65  	return nil
    66  }
    67  
    68  // GetState returns a new State instance for the specified model. The
    69  // connection uses the same credentials and policy as the Controller.
    70  func (ctlr *Controller) GetState(modelTag names.ModelTag) (*PooledState, error) {
    71  	return ctlr.pool.Get(modelTag.Id())
    72  }
    73  
    74  // Ping probes the Controllers's database connection to ensure that it
    75  // is still alive.
    76  func (ctlr *Controller) Ping() error {
    77  	systemState, err := ctlr.pool.SystemState()
    78  	if err != nil {
    79  		return errors.Trace(err)
    80  	}
    81  	return systemState.Ping()
    82  }
    83  
    84  // ControllerConfig returns the config values for the controller.
    85  func (st *State) ControllerConfig() (jujucontroller.Config, error) {
    86  	settings, err := readSettings(st.db(), controllersC, ControllerSettingsGlobalKey)
    87  	if err != nil {
    88  		return nil, errors.Annotatef(err, "controller %q", st.ControllerUUID())
    89  	}
    90  	return settings.Map(), nil
    91  }
    92  
    93  // UpdateControllerConfig allows changing some of the configuration
    94  // for the controller. Changes passed in updateAttrs will be applied
    95  // to the current config, and keys in removeAttrs will be unset (and
    96  // so revert to their defaults). Only a subset of keys can be changed
    97  // after bootstrapping.
    98  func (st *State) UpdateControllerConfig(updateAttrs map[string]interface{}, removeAttrs []string) error {
    99  	fields, _, err := jujucontroller.ConfigSchema.ValidationSchema()
   100  	if err != nil {
   101  		return errors.Trace(err)
   102  	}
   103  	for k := range updateAttrs {
   104  		if field, ok := fields[k]; ok {
   105  			v, err := field.Coerce(updateAttrs[k], []string{k})
   106  			if err != nil {
   107  				return err
   108  			}
   109  			updateAttrs[k] = v
   110  		}
   111  	}
   112  
   113  	if err := st.checkValidControllerConfig(updateAttrs, removeAttrs); err != nil {
   114  		return errors.Trace(err)
   115  	}
   116  
   117  	settings, err := readSettings(st.db(), controllersC, ControllerSettingsGlobalKey)
   118  	if err != nil {
   119  		return errors.Annotatef(err, "controller %q", st.ControllerUUID())
   120  	}
   121  	for _, r := range removeAttrs {
   122  		settings.Delete(r)
   123  	}
   124  	settings.Update(updateAttrs)
   125  
   126  	// Ensure the resulting config is still valid.
   127  	newValues := settings.Map()
   128  	_, err = jujucontroller.NewConfig(
   129  		newValues[jujucontroller.ControllerUUIDKey].(string),
   130  		newValues[jujucontroller.CACertKey].(string),
   131  		newValues,
   132  	)
   133  	if err != nil {
   134  		return errors.Trace(err)
   135  	}
   136  
   137  	_, ops := settings.settingsUpdateOps()
   138  	return errors.Trace(settings.write(ops))
   139  }
   140  
   141  func (st *State) checkValidControllerConfig(updateAttrs map[string]interface{}, removeAttrs []string) error {
   142  	for k := range updateAttrs {
   143  		if err := checkUpdateControllerConfig(k); err != nil {
   144  			return errors.Trace(err)
   145  		}
   146  
   147  		if k == jujucontroller.JujuHASpace || k == jujucontroller.JujuManagementSpace {
   148  			cVal := updateAttrs[k].(string)
   149  			if err := st.checkSpaceIsAvailableToAllControllers(cVal); err != nil {
   150  				return errors.Annotatef(err, "invalid config %q=%q", k, cVal)
   151  			}
   152  		}
   153  	}
   154  	for _, r := range removeAttrs {
   155  		if err := checkUpdateControllerConfig(r); err != nil {
   156  			return errors.Trace(err)
   157  		}
   158  	}
   159  	return nil
   160  }
   161  
   162  func checkUpdateControllerConfig(name string) error {
   163  	if !jujucontroller.ControllerOnlyAttribute(name) {
   164  		return errors.Errorf("unknown controller config setting %q", name)
   165  	}
   166  	if !jujucontroller.AllowedUpdateConfigAttributes.Contains(name) {
   167  		return errors.Errorf("can't change %q after bootstrap", name)
   168  	}
   169  	return nil
   170  }
   171  
   172  // checkSpaceIsAvailableToAllControllers checks if each controller machine has
   173  // at least one address in the input space. If not, an error is returned.
   174  func (st *State) checkSpaceIsAvailableToAllControllers(spaceName string) error {
   175  	controllerIds, err := st.ControllerIds()
   176  	if err != nil {
   177  		return errors.Annotate(err, "cannot get controller info")
   178  	}
   179  
   180  	space, err := st.SpaceByName(spaceName)
   181  	if err != nil {
   182  		return errors.Trace(err)
   183  	}
   184  	netSpace, err := space.NetworkSpace()
   185  	if err != nil {
   186  		return errors.Annotate(err, "getting network space")
   187  	}
   188  
   189  	var missing []string
   190  	for _, id := range controllerIds {
   191  		m, err := st.Machine(id)
   192  		if err != nil {
   193  			return errors.Annotate(err, "cannot get machine")
   194  		}
   195  		if _, ok := m.Addresses().InSpaces(netSpace); !ok {
   196  			missing = append(missing, id)
   197  		}
   198  	}
   199  
   200  	if len(missing) > 0 {
   201  		return errors.Errorf("machines with no addresses in this space: %s", strings.Join(missing, ", "))
   202  	}
   203  	return nil
   204  }
   205  
   206  type controllersDoc struct {
   207  	Id            string   `bson:"_id"`
   208  	CloudName     string   `bson:"cloud"`
   209  	ModelUUID     string   `bson:"model-uuid"`
   210  	ControllerIds []string `bson:"controller-ids"`
   211  }
   212  
   213  // ControllerInfo holds information about currently
   214  // configured controller machines.
   215  type ControllerInfo struct {
   216  	// CloudName is the name of the cloud to which this controller is deployed.
   217  	CloudName string
   218  
   219  	// ModelTag identifies the initial model. Only the initial
   220  	// model is able to have machines that manage state. The initial
   221  	// model is the model that is created when bootstrapping.
   222  	ModelTag names.ModelTag
   223  
   224  	// ControllerIds holds the ids of all the controller nodes.
   225  	// It's main purpose is to allow assertions tha the set of
   226  	// controllers hasn't changed when adding/removing controller nodes.
   227  	ControllerIds []string
   228  }
   229  
   230  // ControllerInfo returns information about
   231  // the currently configured controller machines.
   232  func (st *State) ControllerInfo() (*ControllerInfo, error) {
   233  	session := st.session.Copy()
   234  	defer session.Close()
   235  	return readRawControllerInfo(session)
   236  }
   237  
   238  // readRawControllerInfo reads ControllerInfo direct from the supplied session,
   239  // falling back to the bootstrap model document to extract the UUID when
   240  // required.
   241  func readRawControllerInfo(session *mgo.Session) (*ControllerInfo, error) {
   242  	db := session.DB(jujuDB)
   243  	controllers := db.C(controllersC)
   244  
   245  	var doc controllersDoc
   246  	err := controllers.Find(bson.D{{"_id", modelGlobalKey}}).One(&doc)
   247  	if err == mgo.ErrNotFound {
   248  		return nil, errors.NotFoundf("controllers document")
   249  	}
   250  	if err != nil {
   251  		return nil, errors.Annotatef(err, "cannot get controllers document")
   252  	}
   253  	return &ControllerInfo{
   254  		CloudName:     doc.CloudName,
   255  		ModelTag:      names.NewModelTag(doc.ModelUUID),
   256  		ControllerIds: doc.ControllerIds,
   257  	}, nil
   258  }
   259  
   260  const stateServingInfoKey = "stateServingInfo"
   261  
   262  type stateServingInfo struct {
   263  	APIPort      int    `bson:"apiport"`
   264  	StatePort    int    `bson:"stateport"`
   265  	Cert         string `bson:"cert"`
   266  	PrivateKey   string `bson:"privatekey"`
   267  	CAPrivateKey string `bson:"caprivatekey"`
   268  	// this will be passed as the KeyFile argument to MongoDB
   269  	SharedSecret   string `bson:"sharedsecret"`
   270  	SystemIdentity string `bson:"systemidentity"`
   271  }
   272  
   273  // StateServingInfo returns information for running a controller machine
   274  func (st *State) StateServingInfo() (jujucontroller.StateServingInfo, error) {
   275  	controllers, closer := st.db().GetCollection(controllersC)
   276  	defer closer()
   277  
   278  	var info stateServingInfo
   279  	err := controllers.Find(bson.D{{"_id", stateServingInfoKey}}).One(&info)
   280  	if err != nil {
   281  		return jujucontroller.StateServingInfo{}, errors.Trace(err)
   282  	}
   283  	if info.StatePort == 0 {
   284  		return jujucontroller.StateServingInfo{}, errors.NotFoundf("state serving info")
   285  	}
   286  	return jujucontroller.StateServingInfo{
   287  		APIPort:        info.APIPort,
   288  		StatePort:      info.StatePort,
   289  		Cert:           info.Cert,
   290  		PrivateKey:     info.PrivateKey,
   291  		CAPrivateKey:   info.CAPrivateKey,
   292  		SharedSecret:   info.SharedSecret,
   293  		SystemIdentity: info.SystemIdentity,
   294  	}, nil
   295  }
   296  
   297  // SetStateServingInfo stores information needed for running a controller
   298  func (st *State) SetStateServingInfo(info jujucontroller.StateServingInfo) error {
   299  	if info.StatePort == 0 || info.APIPort == 0 ||
   300  		info.Cert == "" || info.PrivateKey == "" {
   301  		return errors.Errorf("incomplete state serving info set in state")
   302  	}
   303  	if info.CAPrivateKey == "" {
   304  		// No CA certificate key means we can't generate new controller
   305  		// certificates when needed to add to the certificate SANs.
   306  		// Older Juju deployments discard the key because no one realised
   307  		// the certificate was flawed, so at best we can log a warning
   308  		// until an upgrade process is written.
   309  		logger.Warningf("state serving info has no CA certificate key")
   310  	}
   311  	ops := []txn.Op{{
   312  		C:  controllersC,
   313  		Id: stateServingInfoKey,
   314  		Update: bson.D{{"$set", stateServingInfo{
   315  			APIPort:        info.APIPort,
   316  			StatePort:      info.StatePort,
   317  			Cert:           info.Cert,
   318  			PrivateKey:     info.PrivateKey,
   319  			CAPrivateKey:   info.CAPrivateKey,
   320  			SharedSecret:   info.SharedSecret,
   321  			SystemIdentity: info.SystemIdentity,
   322  		}}},
   323  	}}
   324  	if err := st.db().RunTransaction(ops); err != nil {
   325  		return errors.Annotatef(err, "cannot set state serving info")
   326  	}
   327  	return nil
   328  }