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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     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  	"github.com/juju/replicaset/v3"
    16  	jujutxn "github.com/juju/txn/v3"
    17  	"github.com/juju/utils/v3"
    18  	"github.com/juju/version/v2"
    19  
    20  	"github.com/juju/juju/core/constraints"
    21  	"github.com/juju/juju/core/controller"
    22  	"github.com/juju/juju/core/instance"
    23  	"github.com/juju/juju/environs/bootstrap"
    24  	"github.com/juju/juju/mongo"
    25  	stateerrors "github.com/juju/juju/state/errors"
    26  	"github.com/juju/juju/tools"
    27  )
    28  
    29  func isController(mdoc *machineDoc) bool {
    30  	for _, j := range mdoc.Jobs {
    31  		if j == JobManageModel {
    32  			return true
    33  		}
    34  	}
    35  	return false
    36  }
    37  
    38  var errControllerNotAllowed = errors.New("controller jobs specified but not allowed")
    39  
    40  func (st *State) getVotingControllerCount() (int, error) {
    41  	controllerNodesColl, closer := st.db().GetCollection(controllerNodesC)
    42  	defer closer()
    43  
    44  	return controllerNodesColl.Find(bson.M{"wants-vote": true}).Count()
    45  }
    46  
    47  // maintainControllersOps returns a set of operations that will maintain
    48  // the controller information when controllers with the given ids
    49  // are added. If bootstrapOnly is true, there can be only one id = 0;
    50  // (this is a special case to allow adding the bootstrap node).
    51  func (st *State) maintainControllersOps(newIds []string, bootstrapOnly bool) ([]txn.Op, error) {
    52  	if len(newIds) == 0 {
    53  		return nil, nil
    54  	}
    55  	currentControllerIds, err := st.ControllerIds()
    56  	if err != nil {
    57  		return nil, errors.Annotate(err, "cannot get controller info")
    58  	}
    59  	if bootstrapOnly {
    60  		// Allow bootstrap machine only.
    61  		if len(newIds) != 1 || newIds[0] != "0" {
    62  			return nil, errControllerNotAllowed
    63  		}
    64  		if len(currentControllerIds) > 0 {
    65  			return nil, errors.New("controllers already exist")
    66  		}
    67  	}
    68  	ops := []txn.Op{{
    69  		C:  controllersC,
    70  		Id: modelGlobalKey,
    71  		Assert: bson.D{
    72  			{"controller-ids", bson.D{{"$size", len(currentControllerIds)}}},
    73  		},
    74  		Update: bson.D{
    75  			{"$addToSet",
    76  				bson.D{
    77  					{"controller-ids", bson.D{{"$each", newIds}}},
    78  				},
    79  			},
    80  		},
    81  	}}
    82  	return ops, nil
    83  }
    84  
    85  // EnableHA adds controller machines as necessary to make
    86  // the number of live controllers equal to numControllers. The given
    87  // constraints and series will be attached to any new machines.
    88  // If placement is not empty, any new machines which may be required are started
    89  // according to the specified placement directives until the placement list is
    90  // exhausted; thereafter any new machines are started according to the constraints and series.
    91  // MachineID is the id of the machine where the apiserver is running.
    92  func (st *State) EnableHA(
    93  	numControllers int, cons constraints.Value, base Base, placement []string,
    94  ) (ControllersChanges, error) {
    95  
    96  	if numControllers < 0 || (numControllers != 0 && numControllers%2 != 1) {
    97  		return ControllersChanges{}, errors.New("number of controllers must be odd and non-negative")
    98  	}
    99  	if numControllers > controller.MaxPeers {
   100  		return ControllersChanges{}, errors.Errorf("controller count is too large (allowed %d)", controller.MaxPeers)
   101  	}
   102  	var change ControllersChanges
   103  	buildTxn := func(attempt int) ([]txn.Op, error) {
   104  		desiredControllerCount := numControllers
   105  		votingCount, err := st.getVotingControllerCount()
   106  		if err != nil {
   107  			return nil, errors.Trace(err)
   108  		}
   109  		if desiredControllerCount == 0 {
   110  			// Make sure we go to add odd number of desired voters. Even if HA was currently at 2 desired voters
   111  			desiredControllerCount = votingCount + (votingCount+1)%2
   112  			if desiredControllerCount <= 1 {
   113  				desiredControllerCount = 3
   114  			}
   115  		}
   116  		if votingCount > desiredControllerCount {
   117  			return nil, errors.New("cannot remove controllers with enable-ha, use remove-machine and chose the controller(s) to remove")
   118  		}
   119  
   120  		controllerIds, err := st.ControllerIds()
   121  		if err != nil {
   122  			return nil, errors.Trace(err)
   123  		}
   124  		intent, err := st.enableHAIntentions(controllerIds, placement)
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  		voteCount := 0
   129  		for _, m := range intent.maintain {
   130  			if m.WantsVote() {
   131  				voteCount++
   132  			}
   133  		}
   134  		if voteCount == desiredControllerCount {
   135  			return nil, jujutxn.ErrNoOperations
   136  		}
   137  
   138  		if n := desiredControllerCount - voteCount; n < len(intent.convert) {
   139  			intent.convert = intent.convert[:n]
   140  		}
   141  		voteCount += len(intent.convert)
   142  
   143  		intent.newCount = desiredControllerCount - voteCount
   144  
   145  		logger.Infof("%d new machines; converting %v", intent.newCount, intent.convert)
   146  
   147  		var ops []txn.Op
   148  		ops, change, err = st.enableHAIntentionOps(intent, cons, base)
   149  		return ops, err
   150  	}
   151  	if err := st.db().Run(buildTxn); err != nil {
   152  		err = errors.Annotatef(err, "failed to enable HA with %d controllers", numControllers)
   153  		return ControllersChanges{}, err
   154  	}
   155  	return change, nil
   156  }
   157  
   158  // Change in controllers after the ensure availability txn has committed.
   159  type ControllersChanges struct {
   160  	Added      []string
   161  	Removed    []string
   162  	Maintained []string
   163  	Converted  []string
   164  }
   165  
   166  // enableHAIntentionOps returns operations to fulfil the desired intent.
   167  func (st *State) enableHAIntentionOps(
   168  	intent *enableHAIntent,
   169  	cons constraints.Value,
   170  	base Base,
   171  ) ([]txn.Op, ControllersChanges, error) {
   172  	var ops []txn.Op
   173  	var change ControllersChanges
   174  
   175  	// TODO(wallyworld) - only need until we transition away from enable-ha
   176  	controllerApp, err := st.Application(bootstrap.ControllerApplicationName)
   177  	if err != nil && !errors.IsNotFound(err) {
   178  		return nil, ControllersChanges{}, errors.Trace(err)
   179  	}
   180  
   181  	for _, m := range intent.convert {
   182  		ops = append(ops, convertControllerOps(m)...)
   183  		change.Converted = append(change.Converted, m.Id())
   184  		// Add a controller charm unit to the promoted machine.
   185  		if controllerApp != nil {
   186  			unitName, unitOps, err := controllerApp.addUnitOps("", AddUnitParams{machineID: m.Id()}, nil)
   187  			if err != nil {
   188  				return nil, ControllersChanges{}, errors.Trace(err)
   189  			}
   190  			ops = append(ops, unitOps...)
   191  			addToMachineOp := txn.Op{
   192  				C:      machinesC,
   193  				Id:     m.doc.DocID,
   194  				Assert: txn.DocExists,
   195  				Update: bson.D{{"$addToSet", bson.D{{"principals", unitName}}}, {"$set", bson.D{{"clean", false}}}},
   196  			}
   197  			ops = append(ops, addToMachineOp)
   198  		}
   199  	}
   200  
   201  	// Use any placement directives that have been provided when adding new
   202  	// machines, until the directives have been all used up.
   203  	// Ignore constraints for provided machines.
   204  	placementCount := 0
   205  	getPlacementConstraints := func() (string, constraints.Value) {
   206  		if placementCount >= len(intent.placement) {
   207  			return "", cons
   208  		}
   209  		result := intent.placement[placementCount]
   210  		placementCount++
   211  		return result, constraints.Value{}
   212  	}
   213  
   214  	var controllerIds []string
   215  	for i := 0; i < intent.newCount; i++ {
   216  		placement, cons := getPlacementConstraints()
   217  		template := MachineTemplate{
   218  			Base: base,
   219  			Jobs: []MachineJob{
   220  				JobHostUnits,
   221  				JobManageModel,
   222  			},
   223  			Constraints: cons,
   224  			Placement:   placement,
   225  		}
   226  		// Set up the new controller to have a controller charm unit.
   227  		// The unit itself is created below.
   228  		var controllerUnitName string
   229  		if controllerApp != nil {
   230  			controllerUnitName, err = controllerApp.newUnitName()
   231  			if err != nil {
   232  				return nil, ControllersChanges{}, errors.Trace(err)
   233  			}
   234  			template.Dirty = true
   235  			template.principals = []string{controllerUnitName}
   236  		}
   237  		mdoc, addOps, err := st.addMachineOps(template)
   238  		if err != nil {
   239  			return nil, ControllersChanges{}, errors.Trace(err)
   240  		}
   241  		if isController(mdoc) {
   242  			controllerIds = append(controllerIds, mdoc.Id)
   243  		}
   244  		ops = append(ops, addOps...)
   245  		change.Added = append(change.Added, mdoc.Id)
   246  		if controllerApp != nil {
   247  			_, unitOps, err := controllerApp.addUnitOps("", AddUnitParams{
   248  				UnitName:  &controllerUnitName,
   249  				machineID: mdoc.Id,
   250  			}, nil)
   251  			if err != nil {
   252  				return nil, ControllersChanges{}, errors.Trace(err)
   253  			}
   254  			ops = append(ops, unitOps...)
   255  		}
   256  	}
   257  
   258  	for _, m := range intent.maintain {
   259  		change.Maintained = append(change.Maintained, m.Id())
   260  	}
   261  	ssOps, err := st.maintainControllersOps(controllerIds, false)
   262  	if err != nil {
   263  		return nil, ControllersChanges{}, errors.Annotate(err, "cannot prepare machine add operations")
   264  	}
   265  	ops = append(ops, ssOps...)
   266  	return ops, change, nil
   267  }
   268  
   269  type enableHAIntent struct {
   270  	newCount  int
   271  	placement []string
   272  
   273  	maintain []ControllerNode
   274  	convert  []*Machine
   275  }
   276  
   277  // enableHAIntentions returns what we would like
   278  // to do to maintain the availability of the existing servers
   279  // mentioned in the given info, including:
   280  //
   281  //	gathering available, non-voting machines that may be promoted;
   282  func (st *State) enableHAIntentions(controllerIds []string, placement []string) (*enableHAIntent, error) {
   283  	var intent enableHAIntent
   284  	for _, s := range placement {
   285  		// TODO(natefinch): Unscoped placements can end up here, though they
   286  		// should not. We should fix up the CLI to always add a scope,
   287  		// then we can remove the need to deal with unscoped placements.
   288  
   289  		// Append unscoped placements to the intentions.
   290  		// These will be used if/when adding new controllers is required.
   291  		// These placements will be interpreted as availability zones.
   292  		p, err := instance.ParsePlacement(s)
   293  		if err == instance.ErrPlacementScopeMissing {
   294  			intent.placement = append(intent.placement, s)
   295  			continue
   296  		}
   297  
   298  		// Placements for machines are "consumed" by appending such machines as
   299  		// candidates for promotion to controllers.
   300  		if err == nil && p.Scope == instance.MachineScope {
   301  			if names.IsContainerMachine(p.Directive) {
   302  				return nil, errors.New("container placement directives not supported")
   303  			}
   304  
   305  			m, err := st.Machine(p.Directive)
   306  			if err != nil {
   307  				return nil, errors.Annotatef(err, "can't find machine for placement directive %q", s)
   308  			}
   309  			if m.IsManager() {
   310  				return nil, errors.Errorf("machine for placement directive %q is already a controller", s)
   311  			}
   312  			intent.convert = append(intent.convert, m)
   313  			continue
   314  		}
   315  		return nil, errors.Errorf("unsupported placement directive %q", s)
   316  	}
   317  
   318  	for _, id := range controllerIds {
   319  		node, err := st.ControllerNode(id)
   320  		if err != nil {
   321  			return nil, err
   322  		}
   323  		logger.Infof("controller %q, wants vote %v, has vote %v", id, node.WantsVote(), node.HasVote())
   324  		if node.WantsVote() {
   325  			intent.maintain = append(intent.maintain, node)
   326  		}
   327  	}
   328  	logger.Infof("initial intentions: maintain %v; convert: %v",
   329  		intent.maintain, intent.convert)
   330  	return &intent, nil
   331  }
   332  
   333  func convertControllerOps(m *Machine) []txn.Op {
   334  	return []txn.Op{{
   335  		C:  machinesC,
   336  		Id: m.doc.DocID,
   337  		Update: bson.D{
   338  			{"$addToSet", bson.D{{"jobs", JobManageModel}}},
   339  		},
   340  		Assert: bson.D{{"jobs", bson.D{{"$nin", []MachineJob{JobManageModel}}}}},
   341  	}, {
   342  		C:  controllersC,
   343  		Id: modelGlobalKey,
   344  		Update: bson.D{
   345  			{"$addToSet", bson.D{
   346  				{"controller-ids", m.doc.Id},
   347  			}},
   348  		},
   349  	},
   350  		addControllerNodeOp(m.st, m.doc.Id, false),
   351  	}
   352  }
   353  
   354  func (st *State) getControllerNodeDoc(id string) (*controllerNodeDoc, error) {
   355  	controllerNodesColl, closer := st.db().GetCollection(controllerNodesC)
   356  	defer closer()
   357  
   358  	cdoc := &controllerNodeDoc{}
   359  	docId := st.docID(id)
   360  	err := controllerNodesColl.FindId(docId).One(cdoc)
   361  
   362  	switch err {
   363  	case nil:
   364  		return cdoc, nil
   365  	case mgo.ErrNotFound:
   366  		return nil, errors.NotFoundf("controller node %s", id)
   367  	default:
   368  		return nil, errors.Annotatef(err, "cannot get controller node %s", id)
   369  	}
   370  }
   371  
   372  func (st *State) removeControllerReferenceOps(cid string, controllerIds []string) []txn.Op {
   373  	return []txn.Op{{
   374  		C:  machinesC,
   375  		Id: st.docID(cid),
   376  		Update: bson.D{
   377  			{"$pull", bson.D{{"jobs", JobManageModel}}},
   378  		},
   379  	}, {
   380  		C:      controllersC,
   381  		Id:     modelGlobalKey,
   382  		Assert: bson.D{{"controller-ids", controllerIds}},
   383  		Update: bson.D{{"$pull", bson.D{{"controller-ids", cid}}}},
   384  	}, {
   385  		C:  controllerNodesC,
   386  		Id: st.docID(cid),
   387  		Assert: bson.D{
   388  			{"wants-vote", false},
   389  			{"has-vote", false},
   390  		},
   391  	}}
   392  }
   393  
   394  // ControllerNode represents an instance of a HA controller.
   395  type ControllerNode interface {
   396  	Id() string
   397  	Tag() names.Tag
   398  	Refresh() error
   399  	WantsVote() bool
   400  	HasVote() bool
   401  	SetHasVote(hasVote bool) error
   402  	Watch() NotifyWatcher
   403  	SetMongoPassword(password string) error
   404  }
   405  
   406  // ControllerIds returns the ids of the controller nodes.
   407  func (st *State) ControllerIds() ([]string, error) {
   408  	controllerInfo, err := st.ControllerInfo()
   409  	if err != nil {
   410  		return nil, errors.Annotatef(err, "reading controller info")
   411  	}
   412  	return controllerInfo.ControllerIds, nil
   413  }
   414  
   415  // SafeControllerIds returns the ids of the controller nodes
   416  // by looking for the newer "controller-ids" attribute but falling
   417  // back to the legacy "machineids" if needed.
   418  // This method is only used when preparing for upgrades since 2.6
   419  // or earlier used "machineids".
   420  func (st *State) SafeControllerIds() ([]string, error) {
   421  	session := st.session.Copy()
   422  	defer session.Close()
   423  
   424  	db := session.DB(jujuDB)
   425  	controllers := db.C(controllersC)
   426  
   427  	var info bson.M
   428  	err := controllers.Find(bson.D{{"_id", modelGlobalKey}}).One(&info)
   429  	if err == mgo.ErrNotFound {
   430  		return nil, errors.NotFoundf("controllers document")
   431  	}
   432  	if err != nil {
   433  		return nil, errors.Annotatef(err, "cannot get controllers document")
   434  	}
   435  	var ids []interface{}
   436  	var ok bool
   437  	if ids, ok = info["controller-ids"].([]interface{}); !ok {
   438  		ids, ok = info["machineids"].([]interface{})
   439  	}
   440  	if !ok {
   441  		return nil, nil
   442  	}
   443  	result := make([]string, len(ids))
   444  	for i, id := range ids {
   445  		result[i] = id.(string)
   446  	}
   447  	return result, nil
   448  }
   449  
   450  // ControllerNode returns the controller node with the given id.
   451  func (st *State) ControllerNode(id string) (ControllerNode, error) {
   452  	cdoc, err := st.getControllerNodeDoc(id)
   453  	if err != nil {
   454  		return nil, errors.Trace(err)
   455  	}
   456  	return &controllerNode{*cdoc, st}, nil
   457  }
   458  
   459  // AddControllerNode creates a new controller node.
   460  func (st *State) AddControllerNode() (*controllerNode, error) {
   461  	seq, err := sequence(st, "controller")
   462  	if err != nil {
   463  		return nil, err
   464  	}
   465  	id := strconv.Itoa(seq)
   466  	doc := controllerNodeDoc{
   467  		DocID:     st.docID(id),
   468  		WantsVote: true,
   469  	}
   470  
   471  	currentInfo, err := st.ControllerInfo()
   472  	if err != nil && !errors.IsNotFound(err) {
   473  		return nil, errors.Trace(err)
   474  	}
   475  	ops := []txn.Op{addControllerNodeOp(st, id, false)}
   476  	ssOps, err := st.maintainControllersOps([]string{id}, currentInfo == nil)
   477  	if err != nil {
   478  		return nil, errors.Trace(err)
   479  	}
   480  	ops = append(ops, ssOps...)
   481  	if err := st.db().RunTransaction(ops); err != nil {
   482  		return nil, errors.Annotate(err, "cannot add controller node")
   483  	}
   484  	return &controllerNode{doc: doc, st: st}, nil
   485  }
   486  
   487  // HAPrimaryMachine returns machine tag for a controller machine
   488  // that has a mongo instance that is primary in replicaset.
   489  func (st *State) HAPrimaryMachine() (names.MachineTag, error) {
   490  	nodeID := -1
   491  	// Current status of replicaset contains node state.
   492  	// Here we determine node id of the primary node.
   493  	replicaStatus, err := replicaset.CurrentStatus(st.MongoSession())
   494  	if err != nil {
   495  		return names.MachineTag{}, errors.Trace(err)
   496  	}
   497  	for _, m := range replicaStatus.Members {
   498  		if m.State == replicaset.PrimaryState {
   499  			nodeID = m.Id
   500  		}
   501  	}
   502  	if nodeID == -1 {
   503  		return names.MachineTag{}, errors.NotFoundf("HA primary machine")
   504  	}
   505  
   506  	// Current members collection of replicaset contains additional
   507  	// information for the nodes, including machine IDs.
   508  	ms, err := replicaset.CurrentMembers(st.MongoSession())
   509  	if err != nil {
   510  		return names.MachineTag{}, errors.Trace(err)
   511  	}
   512  	for _, m := range ms {
   513  		if m.Id == nodeID {
   514  			if machineID, ok := m.Tags["juju-machine-id"]; ok {
   515  				return names.NewMachineTag(machineID), nil
   516  			}
   517  		}
   518  	}
   519  	return names.MachineTag{}, errors.NotFoundf("HA primary machine")
   520  }
   521  
   522  // ControllerNodes returns all the controller nodes.
   523  func (st *State) ControllerNodes() ([]*controllerNode, error) {
   524  	controllerNodesColl, closer := st.db().GetCollection(controllerNodesC)
   525  	defer closer()
   526  
   527  	var docs []controllerNodeDoc
   528  	err := controllerNodesColl.Find(nil).All(&docs)
   529  	if err != nil {
   530  		return nil, errors.Trace(err)
   531  	}
   532  	result := make([]*controllerNode, len(docs))
   533  	for i, doc := range docs {
   534  		result[i] = &controllerNode{doc, st}
   535  	}
   536  	return result, nil
   537  }
   538  
   539  type controllerNode struct {
   540  	doc controllerNodeDoc
   541  	st  *State
   542  }
   543  
   544  type controllerNodeDoc struct {
   545  	DocID        string       `bson:"_id"`
   546  	HasVote      bool         `bson:"has-vote"`
   547  	WantsVote    bool         `bson:"wants-vote"`
   548  	PasswordHash string       `bson:"password-hash"`
   549  	AgentVersion *tools.Tools `bson:"agent-version,omitempty"`
   550  }
   551  
   552  // Id returns the controller id.
   553  func (c *controllerNode) Id() string {
   554  	return c.st.localID(c.doc.DocID)
   555  }
   556  
   557  // Life is always alive for controller nodes.
   558  // This API is used when a controller agent attempts
   559  // to connect to a controller, currently only for CAAS.
   560  // IAAS models still connect to machines as controllers.
   561  // TODO(controlleragent) - model life on controller nodes
   562  func (c *controllerNode) Life() Life {
   563  	return Alive
   564  }
   565  
   566  // IsManager is always true for controller nodes.
   567  func (c *controllerNode) IsManager() bool {
   568  	return true
   569  }
   570  
   571  // Tag returns the controller tag.
   572  func (c *controllerNode) Tag() names.Tag {
   573  	return names.NewControllerAgentTag(c.Id())
   574  }
   575  
   576  func (c *controllerNode) SetMongoPassword(password string) error {
   577  	return mongo.SetAdminMongoPassword(c.st.session, c.Tag().String(), password)
   578  }
   579  
   580  // SetPassword implements Authenticator.
   581  func (c *controllerNode) SetPassword(password string) error {
   582  	if len(password) < utils.MinAgentPasswordLength {
   583  		return errors.Errorf("password is only %d bytes long, and is not a valid Agent password", len(password))
   584  	}
   585  	passwordHash := utils.AgentPasswordHash(password)
   586  	ops := []txn.Op{{
   587  		C:      controllerNodesC,
   588  		Id:     c.doc.DocID,
   589  		Update: bson.D{{"$set", bson.D{{"password-hash", passwordHash}}}},
   590  	}}
   591  	err := c.st.db().RunTransaction(ops)
   592  	if err != nil {
   593  		return fmt.Errorf("cannot set password of controller node %q: %v", c.Id(), err)
   594  	}
   595  	c.doc.PasswordHash = passwordHash
   596  	return nil
   597  }
   598  
   599  // PasswordValid implements Authenticator.
   600  func (c *controllerNode) PasswordValid(password string) bool {
   601  	agentHash := utils.AgentPasswordHash(password)
   602  	return agentHash == c.doc.PasswordHash
   603  }
   604  
   605  func (c *controllerNode) AgentTools() (*tools.Tools, error) {
   606  	if c.doc.AgentVersion == nil {
   607  		return nil, errors.NotFoundf("agent binaries for controller %v", c)
   608  	}
   609  	agentVersion := *c.doc.AgentVersion
   610  	return &agentVersion, nil
   611  }
   612  
   613  func (c *controllerNode) SetAgentVersion(v version.Binary) (err error) {
   614  	defer errors.DeferredAnnotatef(&err, "cannot set agent version for controller %s", c.Id())
   615  	if err := checkVersionValidity(v); err != nil {
   616  		return err
   617  	}
   618  	binaryVersion := &tools.Tools{Version: v}
   619  	ops := []txn.Op{{
   620  		C:      controllerNodesC,
   621  		Id:     c.doc.DocID,
   622  		Assert: notDeadDoc,
   623  		Update: bson.D{{"$set", bson.D{{"agent-version", binaryVersion}}}},
   624  	}}
   625  	// A "raw" transaction is needed here because this function gets
   626  	// called before database migrations have run so we don't
   627  	// necessarily want the model UUID added to the id.
   628  	if err := c.st.runRawTransaction(ops); err != nil {
   629  		return errors.Trace(err)
   630  	}
   631  	c.doc.AgentVersion = binaryVersion
   632  	return nil
   633  }
   634  
   635  // Refresh reloads the controller state.
   636  func (c *controllerNode) Refresh() error {
   637  	id := c.st.localID(c.doc.DocID)
   638  	cdoc, err := c.st.getControllerNodeDoc(id)
   639  	if err != nil {
   640  		if errors.IsNotFound(err) {
   641  			return err
   642  		}
   643  		return errors.Annotatef(err, "cannot refresh controller node %v", c)
   644  	}
   645  	c.doc = *cdoc
   646  	return nil
   647  }
   648  
   649  // Watch returns a watcher for observing changes to a node.
   650  func (c *controllerNode) Watch() NotifyWatcher {
   651  	return newEntityWatcher(c.st, controllerNodesC, c.doc.DocID)
   652  }
   653  
   654  // WantsVote reports whether the controller
   655  // that wants to take part in peer voting.
   656  func (c *controllerNode) WantsVote() bool {
   657  	return c.doc.WantsVote
   658  }
   659  
   660  // HasVote reports whether that controller is currently a voting
   661  // member of the replica set.
   662  func (c *controllerNode) HasVote() bool {
   663  	return c.doc.HasVote
   664  }
   665  
   666  // SetHasVote sets whether the controller is currently a voting
   667  // member of the replica set. It should only be called
   668  // from the worker that maintains the replica set.
   669  func (c *controllerNode) SetHasVote(hasVote bool) error {
   670  	buildTxn := func(attempt int) ([]txn.Op, error) {
   671  		if attempt > 0 {
   672  			if err := c.Refresh(); err != nil {
   673  				return nil, err
   674  			}
   675  		}
   676  
   677  		var ops []txn.Op
   678  		// Check the host entity life (machine on IAAS models).
   679  		host, err := c.st.Machine(c.Id())
   680  		if err != nil && !errors.IsNotFound(err) {
   681  			return nil, errors.Trace(err)
   682  		}
   683  		if err == nil {
   684  			if host.Life() == Dead {
   685  				return nil, stateerrors.ErrDead
   686  			}
   687  			ops = []txn.Op{{
   688  				C:      machinesC,
   689  				Id:     host.doc.DocID,
   690  				Assert: notDeadDoc,
   691  			}}
   692  		}
   693  		ops = append(ops, c.setHasVoteOps(hasVote)...)
   694  		return ops, nil
   695  	}
   696  	if err := c.st.db().Run(buildTxn); err != nil {
   697  		return errors.Trace(err)
   698  	}
   699  	return nil
   700  }
   701  
   702  func (c *controllerNode) setHasVoteOps(hasVote bool) []txn.Op {
   703  	return []txn.Op{{
   704  		C:      controllerNodesC,
   705  		Id:     c.doc.DocID,
   706  		Assert: txn.DocExists,
   707  		Update: bson.D{{"$set", bson.D{{"has-vote", hasVote}}}},
   708  	}}
   709  }
   710  
   711  func setControllerWantsVoteOp(st *State, id string, wantsVote bool) txn.Op {
   712  	return txn.Op{
   713  		C:      controllerNodesC,
   714  		Id:     st.docID(id),
   715  		Assert: txn.DocExists,
   716  		Update: bson.D{{"$set", bson.D{{"wants-vote", wantsVote}}}},
   717  	}
   718  }
   719  
   720  type controllerReference interface {
   721  	Id() string
   722  	Refresh() error
   723  	WantsVote() bool
   724  	HasVote() bool
   725  }
   726  
   727  // RemoveControllerReference will unregister Controller from being part of the set of Controllers.
   728  // It must not have or want to vote, and it must not be the last controller.
   729  func (st *State) RemoveControllerReference(c controllerReference) error {
   730  	logger.Infof("removing controller machine %q", c.Id())
   731  	buildTxn := func(attempt int) ([]txn.Op, error) {
   732  		if attempt != 0 {
   733  			// Something changed, make sure we're still up to date
   734  			if err := c.Refresh(); err != nil {
   735  				return nil, errors.Trace(err)
   736  			}
   737  		}
   738  		if c.WantsVote() {
   739  			return nil, errors.Errorf("controller %s cannot be removed as it still wants to vote", c.Id())
   740  		}
   741  		if c.HasVote() {
   742  			return nil, errors.Errorf("controller %s cannot be removed as it still has a vote", c.Id())
   743  		}
   744  		controllerIds, err := st.ControllerIds()
   745  		if err != nil {
   746  			return nil, errors.Trace(err)
   747  		}
   748  		if len(controllerIds) <= 1 {
   749  			return nil, errors.Errorf("controller %s cannot be removed as it is the last controller", c.Id())
   750  		}
   751  		return st.removeControllerReferenceOps(c.Id(), controllerIds), nil
   752  	}
   753  	if err := st.db().Run(buildTxn); err != nil {
   754  		return errors.Trace(err)
   755  	}
   756  	return nil
   757  }
   758  
   759  func addControllerNodeOp(mb modelBackend, id string, hasVote bool) txn.Op {
   760  	doc := &controllerNodeDoc{
   761  		DocID:     mb.docID(id),
   762  		HasVote:   hasVote,
   763  		WantsVote: true,
   764  	}
   765  	return txn.Op{
   766  		C:      controllerNodesC,
   767  		Id:     doc.DocID,
   768  		Assert: txn.DocMissing,
   769  		Insert: doc,
   770  	}
   771  }
   772  
   773  func removeControllerNodeOp(mb modelBackend, id string) txn.Op {
   774  	return txn.Op{
   775  		C:      controllerNodesC,
   776  		Id:     mb.docID(id),
   777  		Remove: true,
   778  	}
   779  }