github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/addmachine.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names"
    13  	"github.com/juju/replicaset"
    14  	jujutxn "github.com/juju/txn"
    15  	"gopkg.in/mgo.v2/bson"
    16  	"gopkg.in/mgo.v2/txn"
    17  
    18  	"github.com/juju/juju/constraints"
    19  	"github.com/juju/juju/instance"
    20  	"github.com/juju/juju/network"
    21  	"github.com/juju/juju/status"
    22  )
    23  
    24  // MachineTemplate holds attributes that are to be associated
    25  // with a newly created machine.
    26  type MachineTemplate struct {
    27  	// Series is the series to be associated with the new machine.
    28  	Series string
    29  
    30  	// Constraints are the constraints to be used when finding
    31  	// an instance for the machine.
    32  	Constraints constraints.Value
    33  
    34  	// Jobs holds the jobs to run on the machine's instance.
    35  	// A machine must have at least one job to do.
    36  	// JobManageModel can only be part of the jobs
    37  	// when the first (bootstrap) machine is added.
    38  	Jobs []MachineJob
    39  
    40  	// NoVote holds whether a machine running
    41  	// a controller should abstain from peer voting.
    42  	// It is ignored if Jobs does not contain JobManageModel.
    43  	NoVote bool
    44  
    45  	// Addresses holds the addresses to be associated with the
    46  	// new machine.
    47  	//
    48  	// TODO(dimitern): This should be removed once all addresses
    49  	// come from link-layer device addresses.
    50  	Addresses []network.Address
    51  
    52  	// InstanceId holds the instance id to associate with the machine.
    53  	// If this is empty, the provisioner will try to provision the machine.
    54  	// If this is non-empty, the HardwareCharacteristics and Nonce
    55  	// fields must be set appropriately.
    56  	InstanceId instance.Id
    57  
    58  	// HardwareCharacteristics holds the h/w characteristics to
    59  	// be associated with the machine.
    60  	HardwareCharacteristics instance.HardwareCharacteristics
    61  
    62  	// LinkLayerDevices holds a list of arguments for setting link-layer devices
    63  	// on the machine.
    64  	LinkLayerDevices []LinkLayerDeviceArgs
    65  
    66  	// Volumes holds the parameters for volumes that are to be created
    67  	// and attached to the machine.
    68  	Volumes []MachineVolumeParams
    69  
    70  	// VolumeAttachments holds the parameters for attaching existing
    71  	// volumes to the machine.
    72  	VolumeAttachments map[names.VolumeTag]VolumeAttachmentParams
    73  
    74  	// Filesystems holds the parameters for filesystems that are to be
    75  	// created and attached to the machine.
    76  	Filesystems []MachineFilesystemParams
    77  
    78  	// FilesystemAttachments holds the parameters for attaching existing
    79  	// filesystems to the machine.
    80  	FilesystemAttachments map[names.FilesystemTag]FilesystemAttachmentParams
    81  
    82  	// Nonce holds a unique value that can be used to check
    83  	// if a new instance was really started for this machine.
    84  	// See Machine.SetProvisioned. This must be set if InstanceId is set.
    85  	Nonce string
    86  
    87  	// Dirty signifies whether the new machine will be treated
    88  	// as unclean for unit-assignment purposes.
    89  	Dirty bool
    90  
    91  	// Placement holds the placement directive that will be associated
    92  	// with the machine.
    93  	Placement string
    94  
    95  	// principals holds the principal units that will
    96  	// associated with the machine.
    97  	principals []string
    98  }
    99  
   100  // MachineVolumeParams holds the parameters for creating a volume and
   101  // attaching it to a new machine.
   102  type MachineVolumeParams struct {
   103  	Volume     VolumeParams
   104  	Attachment VolumeAttachmentParams
   105  }
   106  
   107  // MachineFilesystemParams holds the parameters for creating a filesystem
   108  // and attaching it to a new machine.
   109  type MachineFilesystemParams struct {
   110  	Filesystem FilesystemParams
   111  	Attachment FilesystemAttachmentParams
   112  }
   113  
   114  // AddMachineInsideNewMachine creates a new machine within a container
   115  // of the given type inside another new machine. The two given templates
   116  // specify the form of the child and parent respectively.
   117  func (st *State) AddMachineInsideNewMachine(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*Machine, error) {
   118  	mdoc, ops, err := st.addMachineInsideNewMachineOps(template, parentTemplate, containerType)
   119  	if err != nil {
   120  		return nil, errors.Annotate(err, "cannot add a new machine")
   121  	}
   122  	return st.addMachine(mdoc, ops)
   123  }
   124  
   125  // AddMachineInsideMachine adds a machine inside a container of the
   126  // given type on the existing machine with id=parentId.
   127  func (st *State) AddMachineInsideMachine(template MachineTemplate, parentId string, containerType instance.ContainerType) (*Machine, error) {
   128  	mdoc, ops, err := st.addMachineInsideMachineOps(template, parentId, containerType)
   129  	if err != nil {
   130  		return nil, errors.Annotate(err, "cannot add a new machine")
   131  	}
   132  	return st.addMachine(mdoc, ops)
   133  }
   134  
   135  // AddMachine adds a machine with the given series and jobs.
   136  // It is deprecated and around for testing purposes only.
   137  func (st *State) AddMachine(series string, jobs ...MachineJob) (*Machine, error) {
   138  	ms, err := st.AddMachines(MachineTemplate{
   139  		Series: series,
   140  		Jobs:   jobs,
   141  	})
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	return ms[0], nil
   146  }
   147  
   148  // AddOneMachine machine adds a new machine configured according to the
   149  // given template.
   150  func (st *State) AddOneMachine(template MachineTemplate) (*Machine, error) {
   151  	ms, err := st.AddMachines(template)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	return ms[0], nil
   156  }
   157  
   158  // AddMachines adds new machines configured according to the
   159  // given templates.
   160  func (st *State) AddMachines(templates ...MachineTemplate) (_ []*Machine, err error) {
   161  	defer errors.DeferredAnnotatef(&err, "cannot add a new machine")
   162  	var ms []*Machine
   163  	var ops []txn.Op
   164  	var mdocs []*machineDoc
   165  	for _, template := range templates {
   166  		// Adding a machine without any principals is
   167  		// only permitted if unit placement is supported.
   168  		if len(template.principals) == 0 && template.InstanceId == "" {
   169  			if err := st.supportsUnitPlacement(); err != nil {
   170  				return nil, errors.Trace(err)
   171  			}
   172  		}
   173  		mdoc, addOps, err := st.addMachineOps(template)
   174  		if err != nil {
   175  			return nil, errors.Trace(err)
   176  		}
   177  		mdocs = append(mdocs, mdoc)
   178  		ms = append(ms, newMachine(st, mdoc))
   179  		ops = append(ops, addOps...)
   180  	}
   181  	ssOps, err := st.maintainControllersOps(mdocs, nil)
   182  	if err != nil {
   183  		return nil, errors.Trace(err)
   184  	}
   185  	ops = append(ops, ssOps...)
   186  	ops = append(ops, assertModelActiveOp(st.ModelUUID()))
   187  	if err := st.runTransaction(ops); err != nil {
   188  		if errors.Cause(err) == txn.ErrAborted {
   189  			if err := checkModelActive(st); err != nil {
   190  				return nil, errors.Trace(err)
   191  			}
   192  		}
   193  		return nil, errors.Trace(err)
   194  	}
   195  	return ms, nil
   196  }
   197  
   198  func (st *State) addMachine(mdoc *machineDoc, ops []txn.Op) (*Machine, error) {
   199  	ops = append([]txn.Op{assertModelActiveOp(st.ModelUUID())}, ops...)
   200  	if err := st.runTransaction(ops); err != nil {
   201  		if errors.Cause(err) == txn.ErrAborted {
   202  			if err := checkModelActive(st); err != nil {
   203  				return nil, errors.Trace(err)
   204  			}
   205  		}
   206  		return nil, errors.Trace(err)
   207  	}
   208  	return newMachine(st, mdoc), nil
   209  }
   210  
   211  func (st *State) resolveMachineConstraints(cons constraints.Value) (constraints.Value, error) {
   212  	mcons, err := st.resolveConstraints(cons)
   213  	if err != nil {
   214  		return constraints.Value{}, err
   215  	}
   216  	// Machine constraints do not use a container constraint value.
   217  	// Both provisioning and deployment constraints use the same
   218  	// constraints.Value struct so here we clear the container
   219  	// value. Provisioning ignores the container value but clearing
   220  	// it avoids potential confusion.
   221  	mcons.Container = nil
   222  	return mcons, nil
   223  }
   224  
   225  // effectiveMachineTemplate verifies that the given template is
   226  // valid and combines it with values from the state
   227  // to produce a resulting template that more accurately
   228  // represents the data that will be inserted into the state.
   229  func (st *State) effectiveMachineTemplate(p MachineTemplate, allowController bool) (tmpl MachineTemplate, err error) {
   230  	// First check for obvious errors.
   231  	if p.Series == "" {
   232  		return tmpl, errors.New("no series specified")
   233  	}
   234  	if p.InstanceId != "" {
   235  		if p.Nonce == "" {
   236  			return tmpl, errors.New("cannot add a machine with an instance id and no nonce")
   237  		}
   238  	} else if p.Nonce != "" {
   239  		return tmpl, errors.New("cannot specify a nonce without an instance id")
   240  	}
   241  
   242  	p.Constraints, err = st.resolveMachineConstraints(p.Constraints)
   243  	if err != nil {
   244  		return tmpl, err
   245  	}
   246  
   247  	if len(p.Jobs) == 0 {
   248  		return tmpl, errors.New("no jobs specified")
   249  	}
   250  	jset := make(map[MachineJob]bool)
   251  	for _, j := range p.Jobs {
   252  		if jset[j] {
   253  			return MachineTemplate{}, errors.Errorf("duplicate job: %s", j)
   254  		}
   255  		jset[j] = true
   256  	}
   257  	if jset[JobManageModel] {
   258  		if !allowController {
   259  			return tmpl, errControllerNotAllowed
   260  		}
   261  	}
   262  	return p, nil
   263  }
   264  
   265  // addMachineOps returns operations to add a new top level machine
   266  // based on the given template. It also returns the machine document
   267  // that will be inserted.
   268  func (st *State) addMachineOps(template MachineTemplate) (*machineDoc, []txn.Op, error) {
   269  	template, err := st.effectiveMachineTemplate(template, st.IsController())
   270  	if err != nil {
   271  		return nil, nil, err
   272  	}
   273  	if template.InstanceId == "" {
   274  		if err := st.precheckInstance(template.Series, template.Constraints, template.Placement); err != nil {
   275  			return nil, nil, err
   276  		}
   277  	}
   278  	seq, err := st.sequence("machine")
   279  	if err != nil {
   280  		return nil, nil, err
   281  	}
   282  	mdoc := st.machineDocForTemplate(template, strconv.Itoa(seq))
   283  	prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template)
   284  	if err != nil {
   285  		return nil, nil, errors.Trace(err)
   286  	}
   287  	prereqOps = append(prereqOps, assertModelActiveOp(st.ModelUUID()))
   288  	prereqOps = append(prereqOps, st.insertNewContainerRefOp(mdoc.Id))
   289  	if template.InstanceId != "" {
   290  		prereqOps = append(prereqOps, txn.Op{
   291  			C:      instanceDataC,
   292  			Id:     mdoc.DocID,
   293  			Assert: txn.DocMissing,
   294  			Insert: &instanceData{
   295  				DocID:      mdoc.DocID,
   296  				MachineId:  mdoc.Id,
   297  				InstanceId: template.InstanceId,
   298  				ModelUUID:  mdoc.ModelUUID,
   299  				Arch:       template.HardwareCharacteristics.Arch,
   300  				Mem:        template.HardwareCharacteristics.Mem,
   301  				RootDisk:   template.HardwareCharacteristics.RootDisk,
   302  				CpuCores:   template.HardwareCharacteristics.CpuCores,
   303  				CpuPower:   template.HardwareCharacteristics.CpuPower,
   304  				Tags:       template.HardwareCharacteristics.Tags,
   305  				AvailZone:  template.HardwareCharacteristics.AvailabilityZone,
   306  			},
   307  		})
   308  	}
   309  
   310  	return mdoc, append(prereqOps, machineOp), nil
   311  }
   312  
   313  // supportsContainerType reports whether the machine supports the given
   314  // container type. If the machine's supportedContainers attribute is
   315  // set, this decision can be made right here, otherwise we assume that
   316  // everything will be ok and later on put the container into an error
   317  // state if necessary.
   318  func (m *Machine) supportsContainerType(ctype instance.ContainerType) bool {
   319  	supportedContainers, ok := m.SupportedContainers()
   320  	if !ok {
   321  		// We don't know yet, so we report that we support the container.
   322  		return true
   323  	}
   324  	for _, ct := range supportedContainers {
   325  		if ct == ctype {
   326  			return true
   327  		}
   328  	}
   329  	return false
   330  }
   331  
   332  // addMachineInsideMachineOps returns operations to add a machine inside
   333  // a container of the given type on an existing machine.
   334  func (st *State) addMachineInsideMachineOps(template MachineTemplate, parentId string, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) {
   335  	if template.InstanceId != "" {
   336  		return nil, nil, errors.New("cannot specify instance id for a new container")
   337  	}
   338  	template, err := st.effectiveMachineTemplate(template, false)
   339  	if err != nil {
   340  		return nil, nil, err
   341  	}
   342  	if containerType == "" {
   343  		return nil, nil, errors.New("no container type specified")
   344  	}
   345  	// Adding a machine within a machine implies add-machine or placement.
   346  	if err := st.supportsUnitPlacement(); err != nil {
   347  		return nil, nil, err
   348  	}
   349  
   350  	// If a parent machine is specified, make sure it exists
   351  	// and can support the requested container type.
   352  	parent, err := st.Machine(parentId)
   353  	if err != nil {
   354  		return nil, nil, err
   355  	}
   356  	if !parent.supportsContainerType(containerType) {
   357  		return nil, nil, errors.Errorf("machine %s cannot host %s containers", parentId, containerType)
   358  	}
   359  
   360  	newId, err := st.newContainerId(parentId, containerType)
   361  	if err != nil {
   362  		return nil, nil, err
   363  	}
   364  	mdoc := st.machineDocForTemplate(template, newId)
   365  	mdoc.ContainerType = string(containerType)
   366  	prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template)
   367  	if err != nil {
   368  		return nil, nil, errors.Trace(err)
   369  	}
   370  	prereqOps = append(prereqOps,
   371  		// Update containers record for host machine.
   372  		st.addChildToContainerRefOp(parentId, mdoc.Id),
   373  		// Create a containers reference document for the container itself.
   374  		st.insertNewContainerRefOp(mdoc.Id),
   375  	)
   376  	return mdoc, append(prereqOps, machineOp), nil
   377  }
   378  
   379  // newContainerId returns a new id for a machine within the machine
   380  // with id parentId and the given container type.
   381  func (st *State) newContainerId(parentId string, containerType instance.ContainerType) (string, error) {
   382  	seq, err := st.sequence(fmt.Sprintf("machine%s%sContainer", parentId, containerType))
   383  	if err != nil {
   384  		return "", err
   385  	}
   386  	return fmt.Sprintf("%s/%s/%d", parentId, containerType, seq), nil
   387  }
   388  
   389  // addMachineInsideNewMachineOps returns operations to create a new
   390  // machine within a container of the given type inside another
   391  // new machine. The two given templates specify the form
   392  // of the child and parent respectively.
   393  func (st *State) addMachineInsideNewMachineOps(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) {
   394  	if template.InstanceId != "" || parentTemplate.InstanceId != "" {
   395  		return nil, nil, errors.New("cannot specify instance id for a new container")
   396  	}
   397  	seq, err := st.sequence("machine")
   398  	if err != nil {
   399  		return nil, nil, err
   400  	}
   401  	parentTemplate, err = st.effectiveMachineTemplate(parentTemplate, false)
   402  	if err != nil {
   403  		return nil, nil, err
   404  	}
   405  	if containerType == "" {
   406  		return nil, nil, errors.New("no container type specified")
   407  	}
   408  	if parentTemplate.InstanceId == "" {
   409  		// Adding a machine within a machine implies add-machine or placement.
   410  		if err := st.supportsUnitPlacement(); err != nil {
   411  			return nil, nil, err
   412  		}
   413  		if err := st.precheckInstance(parentTemplate.Series, parentTemplate.Constraints, parentTemplate.Placement); err != nil {
   414  			return nil, nil, err
   415  		}
   416  	}
   417  
   418  	parentDoc := st.machineDocForTemplate(parentTemplate, strconv.Itoa(seq))
   419  	newId, err := st.newContainerId(parentDoc.Id, containerType)
   420  	if err != nil {
   421  		return nil, nil, err
   422  	}
   423  	template, err = st.effectiveMachineTemplate(template, false)
   424  	if err != nil {
   425  		return nil, nil, err
   426  	}
   427  	mdoc := st.machineDocForTemplate(template, newId)
   428  	mdoc.ContainerType = string(containerType)
   429  	parentPrereqOps, parentOp, err := st.insertNewMachineOps(parentDoc, parentTemplate)
   430  	if err != nil {
   431  		return nil, nil, errors.Trace(err)
   432  	}
   433  	prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template)
   434  	if err != nil {
   435  		return nil, nil, errors.Trace(err)
   436  	}
   437  	prereqOps = append(prereqOps, parentPrereqOps...)
   438  	prereqOps = append(prereqOps,
   439  		// The host machine doesn't exist yet, create a new containers record.
   440  		st.insertNewContainerRefOp(mdoc.Id),
   441  		// Create a containers reference document for the container itself.
   442  		st.insertNewContainerRefOp(parentDoc.Id, mdoc.Id),
   443  	)
   444  	return mdoc, append(prereqOps, parentOp, machineOp), nil
   445  }
   446  
   447  func (st *State) machineDocForTemplate(template MachineTemplate, id string) *machineDoc {
   448  	// We ignore the error from Select*Address as an error indicates
   449  	// no address is available, in which case the empty address is returned
   450  	// and setting the preferred address to an empty one is the correct
   451  	// thing to do when none is available.
   452  	privateAddr, _ := network.SelectInternalAddress(template.Addresses, false)
   453  	publicAddr, _ := network.SelectPublicAddress(template.Addresses)
   454  	return &machineDoc{
   455  		DocID:                   st.docID(id),
   456  		Id:                      id,
   457  		ModelUUID:               st.ModelUUID(),
   458  		Series:                  template.Series,
   459  		Jobs:                    template.Jobs,
   460  		Clean:                   !template.Dirty,
   461  		Principals:              template.principals,
   462  		Life:                    Alive,
   463  		Nonce:                   template.Nonce,
   464  		Addresses:               fromNetworkAddresses(template.Addresses, OriginMachine),
   465  		PreferredPrivateAddress: fromNetworkAddress(privateAddr, OriginMachine),
   466  		PreferredPublicAddress:  fromNetworkAddress(publicAddr, OriginMachine),
   467  		NoVote:                  template.NoVote,
   468  		Placement:               template.Placement,
   469  	}
   470  }
   471  
   472  // insertNewMachineOps returns operations to insert the given machine document
   473  // into the database, based on the given template. Only the constraints are
   474  // taken from the template.
   475  func (st *State) insertNewMachineOps(mdoc *machineDoc, template MachineTemplate) (prereqOps []txn.Op, machineOp txn.Op, err error) {
   476  	machineStatusDoc := statusDoc{
   477  		Status:    status.StatusPending,
   478  		ModelUUID: st.ModelUUID(),
   479  		// TODO(fwereade): 2016-03-17 lp:1558657
   480  		Updated: time.Now().UnixNano(),
   481  	}
   482  	instanceStatusDoc := statusDoc{
   483  		Status:    status.StatusPending,
   484  		ModelUUID: st.ModelUUID(),
   485  		Updated:   time.Now().UnixNano(),
   486  	}
   487  
   488  	prereqOps, machineOp = st.baseNewMachineOps(
   489  		mdoc,
   490  		machineStatusDoc,
   491  		instanceStatusDoc,
   492  		template.Constraints,
   493  	)
   494  
   495  	storageOps, volumeAttachments, filesystemAttachments, err := st.machineStorageOps(
   496  		mdoc, &machineStorageParams{
   497  			filesystems:           template.Filesystems,
   498  			filesystemAttachments: template.FilesystemAttachments,
   499  			volumes:               template.Volumes,
   500  			volumeAttachments:     template.VolumeAttachments,
   501  		},
   502  	)
   503  	if err != nil {
   504  		return nil, txn.Op{}, errors.Trace(err)
   505  	}
   506  	for _, a := range volumeAttachments {
   507  		mdoc.Volumes = append(mdoc.Volumes, a.tag.Id())
   508  	}
   509  	for _, a := range filesystemAttachments {
   510  		mdoc.Filesystems = append(mdoc.Filesystems, a.tag.Id())
   511  	}
   512  	prereqOps = append(prereqOps, storageOps...)
   513  
   514  	// At the last moment we still have statusDoc in scope, set the initial
   515  	// history entry. This is risky, and may lead to extra entries, but that's
   516  	// an intrinsic problem with mixing txn and non-txn ops -- we can't sync
   517  	// them cleanly.
   518  	probablyUpdateStatusHistory(st, machineGlobalKey(mdoc.Id), machineStatusDoc)
   519  	probablyUpdateStatusHistory(st, machineGlobalInstanceKey(mdoc.Id), instanceStatusDoc)
   520  	return prereqOps, machineOp, nil
   521  }
   522  
   523  func (st *State) baseNewMachineOps(mdoc *machineDoc, machineStatusDoc, instanceStatusDoc statusDoc, cons constraints.Value) (prereqOps []txn.Op, machineOp txn.Op) {
   524  	machineOp = txn.Op{
   525  		C:      machinesC,
   526  		Id:     mdoc.DocID,
   527  		Assert: txn.DocMissing,
   528  		Insert: mdoc,
   529  	}
   530  
   531  	globalKey := machineGlobalKey(mdoc.Id)
   532  	globalInstanceKey := machineGlobalInstanceKey(mdoc.Id)
   533  
   534  	prereqOps = []txn.Op{
   535  		createConstraintsOp(st, globalKey, cons),
   536  		createStatusOp(st, globalKey, machineStatusDoc),
   537  		createStatusOp(st, globalInstanceKey, instanceStatusDoc),
   538  		createMachineBlockDevicesOp(mdoc.Id),
   539  		addModelMachineRefOp(st, mdoc.Id),
   540  	}
   541  	return prereqOps, machineOp
   542  }
   543  
   544  type machineStorageParams struct {
   545  	volumes               []MachineVolumeParams
   546  	volumeAttachments     map[names.VolumeTag]VolumeAttachmentParams
   547  	filesystems           []MachineFilesystemParams
   548  	filesystemAttachments map[names.FilesystemTag]FilesystemAttachmentParams
   549  }
   550  
   551  // machineStorageOps creates txn.Ops for creating volumes, filesystems,
   552  // and attachments to the specified machine. The results are the txn.Ops,
   553  // and the tags of volumes and filesystems newly attached to the machine.
   554  func (st *State) machineStorageOps(
   555  	mdoc *machineDoc, args *machineStorageParams,
   556  ) ([]txn.Op, []volumeAttachmentTemplate, []filesystemAttachmentTemplate, error) {
   557  	var filesystemOps, volumeOps []txn.Op
   558  	var fsAttachments []filesystemAttachmentTemplate
   559  	var volumeAttachments []volumeAttachmentTemplate
   560  
   561  	// Create filesystems and filesystem attachments.
   562  	for _, f := range args.filesystems {
   563  		ops, filesystemTag, volumeTag, err := st.addFilesystemOps(f.Filesystem, mdoc.Id)
   564  		if err != nil {
   565  			return nil, nil, nil, errors.Trace(err)
   566  		}
   567  		filesystemOps = append(filesystemOps, ops...)
   568  		fsAttachments = append(fsAttachments, filesystemAttachmentTemplate{
   569  			filesystemTag, f.Filesystem.storage, f.Attachment,
   570  		})
   571  		if volumeTag != (names.VolumeTag{}) {
   572  			// The filesystem requires a volume, so create a volume attachment too.
   573  			volumeAttachments = append(volumeAttachments, volumeAttachmentTemplate{
   574  				volumeTag, VolumeAttachmentParams{},
   575  			})
   576  		}
   577  	}
   578  
   579  	// Create volumes and volume attachments.
   580  	for _, v := range args.volumes {
   581  		ops, tag, err := st.addVolumeOps(v.Volume, mdoc.Id)
   582  		if err != nil {
   583  			return nil, nil, nil, errors.Trace(err)
   584  		}
   585  		volumeOps = append(volumeOps, ops...)
   586  		volumeAttachments = append(volumeAttachments, volumeAttachmentTemplate{
   587  			tag, v.Attachment,
   588  		})
   589  	}
   590  
   591  	// TODO(axw) handle args.filesystemAttachments, args.volumeAttachments
   592  	// when we handle attaching to existing (e.g. shared) storage.
   593  
   594  	ops := make([]txn.Op, 0, len(filesystemOps)+len(volumeOps)+len(fsAttachments)+len(volumeAttachments))
   595  	if len(fsAttachments) > 0 {
   596  		attachmentOps := createMachineFilesystemAttachmentsOps(mdoc.Id, fsAttachments)
   597  		ops = append(ops, filesystemOps...)
   598  		ops = append(ops, attachmentOps...)
   599  	}
   600  	if len(volumeAttachments) > 0 {
   601  		attachmentOps := createMachineVolumeAttachmentsOps(mdoc.Id, volumeAttachments)
   602  		ops = append(ops, volumeOps...)
   603  		ops = append(ops, attachmentOps...)
   604  	}
   605  	return ops, volumeAttachments, fsAttachments, nil
   606  }
   607  
   608  // addMachineStorageAttachmentsOps returns txn.Ops for adding the IDs of
   609  // attached volumes and filesystems to an existing machine. Filesystem
   610  // mount points are checked against existing filesystem attachments for
   611  // conflicts, with a txn.Op added to prevent concurrent additions as
   612  // necessary.
   613  func addMachineStorageAttachmentsOps(
   614  	machine *Machine,
   615  	volumes []volumeAttachmentTemplate,
   616  	filesystems []filesystemAttachmentTemplate,
   617  ) ([]txn.Op, error) {
   618  	var updates bson.D
   619  	assert := isAliveDoc
   620  	if len(volumes) > 0 {
   621  		volumeIds := make([]string, len(volumes))
   622  		for i, v := range volumes {
   623  			volumeIds[i] = v.tag.Id()
   624  		}
   625  		updates = append(updates, bson.DocElem{"$addToSet", bson.D{{
   626  			"volumes", bson.D{{"$each", volumeIds}}}},
   627  		})
   628  	}
   629  	if len(filesystems) > 0 {
   630  		filesystemIds := make([]string, len(filesystems))
   631  		var withLocation []filesystemAttachmentTemplate
   632  		for i, f := range filesystems {
   633  			filesystemIds[i] = f.tag.Id()
   634  			if !f.params.locationAutoGenerated {
   635  				// If the location was not automatically
   636  				// generated, we must ensure it does not
   637  				// conflict with any existing storage.
   638  				// Generated paths are guaranteed to be
   639  				// unique.
   640  				withLocation = append(withLocation, f)
   641  			}
   642  		}
   643  		updates = append(updates, bson.DocElem{"$addToSet", bson.D{{
   644  			"filesystems", bson.D{{"$each", filesystemIds}}}},
   645  		})
   646  		if len(withLocation) > 0 {
   647  			if err := validateFilesystemMountPoints(machine, withLocation); err != nil {
   648  				return nil, errors.Annotate(err, "validating filesystem mount points")
   649  			}
   650  			// Make sure no filesystems are added concurrently.
   651  			assert = append(assert, bson.DocElem{
   652  				"filesystems", bson.D{{"$not", bson.D{{
   653  					"$elemMatch", bson.D{{
   654  						"$nin", machine.doc.Filesystems,
   655  					}},
   656  				}}}},
   657  			})
   658  		}
   659  	}
   660  	return []txn.Op{{
   661  		C:      machinesC,
   662  		Id:     machine.doc.Id,
   663  		Assert: assert,
   664  		Update: updates,
   665  	}}, nil
   666  }
   667  
   668  func hasJob(jobs []MachineJob, job MachineJob) bool {
   669  	for _, j := range jobs {
   670  		if j == job {
   671  			return true
   672  		}
   673  	}
   674  	return false
   675  }
   676  
   677  var errControllerNotAllowed = errors.New("controller jobs specified but not allowed")
   678  
   679  // maintainControllersOps returns a set of operations that will maintain
   680  // the controller information when the given machine documents
   681  // are added to the machines collection. If currentInfo is nil,
   682  // there can be only one machine document and it must have
   683  // id 0 (this is a special case to allow adding the bootstrap machine)
   684  func (st *State) maintainControllersOps(mdocs []*machineDoc, currentInfo *ControllerInfo) ([]txn.Op, error) {
   685  	var newIds, newVotingIds []string
   686  	for _, doc := range mdocs {
   687  		if !hasJob(doc.Jobs, JobManageModel) {
   688  			continue
   689  		}
   690  		newIds = append(newIds, doc.Id)
   691  		if !doc.NoVote {
   692  			newVotingIds = append(newVotingIds, doc.Id)
   693  		}
   694  	}
   695  	if len(newIds) == 0 {
   696  		return nil, nil
   697  	}
   698  	if currentInfo == nil {
   699  		// Allow bootstrap machine only.
   700  		if len(mdocs) != 1 || mdocs[0].Id != "0" {
   701  			return nil, errControllerNotAllowed
   702  		}
   703  		var err error
   704  		currentInfo, err = st.ControllerInfo()
   705  		if err != nil {
   706  			return nil, errors.Annotate(err, "cannot get controller info")
   707  		}
   708  		if len(currentInfo.MachineIds) > 0 || len(currentInfo.VotingMachineIds) > 0 {
   709  			return nil, errors.New("controllers already exist")
   710  		}
   711  	}
   712  	ops := []txn.Op{{
   713  		C:  controllersC,
   714  		Id: modelGlobalKey,
   715  		Assert: bson.D{{
   716  			"$and", []bson.D{
   717  				{{"machineids", bson.D{{"$size", len(currentInfo.MachineIds)}}}},
   718  				{{"votingmachineids", bson.D{{"$size", len(currentInfo.VotingMachineIds)}}}},
   719  			},
   720  		}},
   721  		Update: bson.D{
   722  			{"$addToSet", bson.D{{"machineids", bson.D{{"$each", newIds}}}}},
   723  			{"$addToSet", bson.D{{"votingmachineids", bson.D{{"$each", newVotingIds}}}}},
   724  		},
   725  	}}
   726  	return ops, nil
   727  }
   728  
   729  // EnableHA adds controller machines as necessary to make
   730  // the number of live controllers equal to numControllers. The given
   731  // constraints and series will be attached to any new machines.
   732  // If placement is not empty, any new machines which may be required are started
   733  // according to the specified placement directives until the placement list is
   734  // exhausted; thereafter any new machines are started according to the constraints and series.
   735  func (st *State) EnableHA(
   736  	numControllers int, cons constraints.Value, series string, placement []string,
   737  ) (ControllersChanges, error) {
   738  
   739  	if numControllers < 0 || (numControllers != 0 && numControllers%2 != 1) {
   740  		return ControllersChanges{}, errors.New("number of controllers must be odd and non-negative")
   741  	}
   742  	if numControllers > replicaset.MaxPeers {
   743  		return ControllersChanges{}, errors.Errorf("controller count is too large (allowed %d)", replicaset.MaxPeers)
   744  	}
   745  	var change ControllersChanges
   746  	buildTxn := func(attempt int) ([]txn.Op, error) {
   747  		currentInfo, err := st.ControllerInfo()
   748  		if err != nil {
   749  			return nil, err
   750  		}
   751  		desiredControllerCount := numControllers
   752  		if desiredControllerCount == 0 {
   753  			desiredControllerCount = len(currentInfo.VotingMachineIds)
   754  			if desiredControllerCount <= 1 {
   755  				desiredControllerCount = 3
   756  			}
   757  		}
   758  		if len(currentInfo.VotingMachineIds) > desiredControllerCount {
   759  			return nil, errors.New("cannot reduce controller count")
   760  		}
   761  
   762  		intent, err := st.enableHAIntentions(currentInfo, placement)
   763  		if err != nil {
   764  			return nil, err
   765  		}
   766  		voteCount := 0
   767  		for _, m := range intent.maintain {
   768  			if m.WantsVote() {
   769  				voteCount++
   770  			}
   771  		}
   772  		if voteCount == desiredControllerCount && len(intent.remove) == 0 {
   773  			return nil, jujutxn.ErrNoOperations
   774  		}
   775  		// Promote as many machines as we can to fulfil the shortfall.
   776  		if n := desiredControllerCount - voteCount; n < len(intent.promote) {
   777  			intent.promote = intent.promote[:n]
   778  		}
   779  		voteCount += len(intent.promote)
   780  
   781  		if n := desiredControllerCount - voteCount; n < len(intent.convert) {
   782  			intent.convert = intent.convert[:n]
   783  		}
   784  		voteCount += len(intent.convert)
   785  
   786  		intent.newCount = desiredControllerCount - voteCount
   787  
   788  		logger.Infof("%d new machines; promoting %v; converting %v", intent.newCount, intent.promote, intent.convert)
   789  
   790  		var ops []txn.Op
   791  		ops, change, err = st.enableHAIntentionOps(intent, currentInfo, cons, series)
   792  		return ops, err
   793  	}
   794  	if err := st.run(buildTxn); err != nil {
   795  		err = errors.Annotate(err, "failed to create new controller machines")
   796  		return ControllersChanges{}, err
   797  	}
   798  	return change, nil
   799  }
   800  
   801  // Change in controllers after the ensure availability txn has committed.
   802  type ControllersChanges struct {
   803  	Added      []string
   804  	Removed    []string
   805  	Maintained []string
   806  	Promoted   []string
   807  	Demoted    []string
   808  	Converted  []string
   809  }
   810  
   811  // enableHAIntentionOps returns operations to fulfil the desired intent.
   812  func (st *State) enableHAIntentionOps(
   813  	intent *enableHAIntent,
   814  	currentInfo *ControllerInfo,
   815  	cons constraints.Value,
   816  	series string,
   817  ) ([]txn.Op, ControllersChanges, error) {
   818  	var ops []txn.Op
   819  	var change ControllersChanges
   820  	for _, m := range intent.promote {
   821  		ops = append(ops, promoteControllerOps(m)...)
   822  		change.Promoted = append(change.Promoted, m.doc.Id)
   823  	}
   824  	for _, m := range intent.demote {
   825  		ops = append(ops, demoteControllerOps(m)...)
   826  		change.Demoted = append(change.Demoted, m.doc.Id)
   827  	}
   828  	for _, m := range intent.convert {
   829  		ops = append(ops, convertControllerOps(m)...)
   830  		change.Converted = append(change.Converted, m.doc.Id)
   831  	}
   832  	// Use any placement directives that have been provided
   833  	// when adding new machines, until the directives have
   834  	// been all used up. Set up a helper function to do the
   835  	// work required.
   836  	placementCount := 0
   837  	getPlacement := func() string {
   838  		if placementCount >= len(intent.placement) {
   839  			return ""
   840  		}
   841  		result := intent.placement[placementCount]
   842  		placementCount++
   843  		return result
   844  	}
   845  	mdocs := make([]*machineDoc, intent.newCount)
   846  	for i := range mdocs {
   847  		template := MachineTemplate{
   848  			Series: series,
   849  			Jobs: []MachineJob{
   850  				JobHostUnits,
   851  				JobManageModel,
   852  			},
   853  			Constraints: cons,
   854  			Placement:   getPlacement(),
   855  		}
   856  		mdoc, addOps, err := st.addMachineOps(template)
   857  		if err != nil {
   858  			return nil, ControllersChanges{}, err
   859  		}
   860  		mdocs[i] = mdoc
   861  		ops = append(ops, addOps...)
   862  		change.Added = append(change.Added, mdoc.Id)
   863  
   864  	}
   865  	for _, m := range intent.remove {
   866  		ops = append(ops, removeControllerOps(m)...)
   867  		change.Removed = append(change.Removed, m.doc.Id)
   868  
   869  	}
   870  
   871  	for _, m := range intent.maintain {
   872  		tag, err := names.ParseTag(m.Tag().String())
   873  		if err != nil {
   874  			return nil, ControllersChanges{}, errors.Annotate(err, "could not parse machine tag")
   875  		}
   876  		if tag.Kind() != names.MachineTagKind {
   877  			return nil, ControllersChanges{}, errors.Errorf("expected machine tag kind, got %s", tag.Kind())
   878  		}
   879  		change.Maintained = append(change.Maintained, tag.Id())
   880  	}
   881  	ssOps, err := st.maintainControllersOps(mdocs, currentInfo)
   882  	if err != nil {
   883  		return nil, ControllersChanges{}, errors.Annotate(err, "cannot prepare machine add operations")
   884  	}
   885  	ops = append(ops, ssOps...)
   886  	return ops, change, nil
   887  }
   888  
   889  // controllerAvailable returns true if the specified controller machine is
   890  // available.
   891  var controllerAvailable = func(m *Machine) (bool, error) {
   892  	// TODO(axw) #1271504 2014-01-22
   893  	// Check the controller's associated mongo health;
   894  	// requires coordination with worker/peergrouper.
   895  	return m.AgentPresence()
   896  }
   897  
   898  type enableHAIntent struct {
   899  	newCount  int
   900  	placement []string
   901  
   902  	promote, maintain, demote, remove, convert []*Machine
   903  }
   904  
   905  // enableHAIntentions returns what we would like
   906  // to do to maintain the availability of the existing servers
   907  // mentioned in the given info, including:
   908  //   demoting unavailable, voting machines;
   909  //   removing unavailable, non-voting, non-vote-holding machines;
   910  //   gathering available, non-voting machines that may be promoted;
   911  func (st *State) enableHAIntentions(info *ControllerInfo, placement []string) (*enableHAIntent, error) {
   912  	var intent enableHAIntent
   913  	for _, s := range placement {
   914  		// TODO(natefinch): unscoped placements shouldn't ever get here (though
   915  		// they do currently).  We should fix up the CLI to always add a scope
   916  		// to placements and then we can remove the need to deal with unscoped
   917  		// placements.
   918  		p, err := instance.ParsePlacement(s)
   919  		if err == instance.ErrPlacementScopeMissing {
   920  			intent.placement = append(intent.placement, s)
   921  			continue
   922  		}
   923  		if err == nil && p.Scope == instance.MachineScope {
   924  			// TODO(natefinch) add env provider policy to check if conversion is
   925  			// possible (e.g. cannot be supported by Azure in HA mode).
   926  
   927  			if names.IsContainerMachine(p.Directive) {
   928  				return nil, errors.New("container placement directives not supported")
   929  			}
   930  
   931  			m, err := st.Machine(p.Directive)
   932  			if err != nil {
   933  				return nil, errors.Annotatef(err, "can't find machine for placement directive %q", s)
   934  			}
   935  			if m.IsManager() {
   936  				return nil, errors.Errorf("machine for placement directive %q is already a controller", s)
   937  			}
   938  			intent.convert = append(intent.convert, m)
   939  			intent.placement = append(intent.placement, s)
   940  			continue
   941  		}
   942  		return nil, errors.Errorf("unsupported placement directive %q", s)
   943  	}
   944  
   945  	for _, mid := range info.MachineIds {
   946  		m, err := st.Machine(mid)
   947  		if err != nil {
   948  			return nil, err
   949  		}
   950  		available, err := controllerAvailable(m)
   951  		if err != nil {
   952  			return nil, err
   953  		}
   954  		logger.Infof("machine %q, available %v, wants vote %v, has vote %v", m, available, m.WantsVote(), m.HasVote())
   955  		if available {
   956  			if m.WantsVote() {
   957  				intent.maintain = append(intent.maintain, m)
   958  			} else {
   959  				intent.promote = append(intent.promote, m)
   960  			}
   961  			continue
   962  		}
   963  		if m.WantsVote() {
   964  			// The machine wants to vote, so we simply set novote and allow it
   965  			// to run its course to have its vote removed by the worker that
   966  			// maintains the replicaset. We will replace it with an existing
   967  			// non-voting controller if there is one, starting a new one if
   968  			// not.
   969  			intent.demote = append(intent.demote, m)
   970  		} else if m.HasVote() {
   971  			// The machine still has a vote, so keep it around for now.
   972  			intent.maintain = append(intent.maintain, m)
   973  		} else {
   974  			// The machine neither wants to nor has a vote, so remove its
   975  			// JobManageModel job immediately.
   976  			intent.remove = append(intent.remove, m)
   977  		}
   978  	}
   979  	logger.Infof("initial intentions: promote %v; maintain %v; demote %v; remove %v; convert: %v",
   980  		intent.promote, intent.maintain, intent.demote, intent.remove, intent.convert)
   981  	return &intent, nil
   982  }
   983  
   984  func convertControllerOps(m *Machine) []txn.Op {
   985  	return []txn.Op{{
   986  		C:  machinesC,
   987  		Id: m.doc.DocID,
   988  		Update: bson.D{
   989  			{"$addToSet", bson.D{{"jobs", JobManageModel}}},
   990  			{"$set", bson.D{{"novote", false}}},
   991  		},
   992  		Assert: bson.D{{"jobs", bson.D{{"$nin", []MachineJob{JobManageModel}}}}},
   993  	}, {
   994  		C:  controllersC,
   995  		Id: modelGlobalKey,
   996  		Update: bson.D{
   997  			{"$addToSet", bson.D{{"votingmachineids", m.doc.Id}}},
   998  			{"$addToSet", bson.D{{"machineids", m.doc.Id}}},
   999  		},
  1000  	}}
  1001  }
  1002  
  1003  func promoteControllerOps(m *Machine) []txn.Op {
  1004  	return []txn.Op{{
  1005  		C:      machinesC,
  1006  		Id:     m.doc.DocID,
  1007  		Assert: bson.D{{"novote", true}},
  1008  		Update: bson.D{{"$set", bson.D{{"novote", false}}}},
  1009  	}, {
  1010  		C:      controllersC,
  1011  		Id:     modelGlobalKey,
  1012  		Update: bson.D{{"$addToSet", bson.D{{"votingmachineids", m.doc.Id}}}},
  1013  	}}
  1014  }
  1015  
  1016  func demoteControllerOps(m *Machine) []txn.Op {
  1017  	return []txn.Op{{
  1018  		C:      machinesC,
  1019  		Id:     m.doc.DocID,
  1020  		Assert: bson.D{{"novote", false}},
  1021  		Update: bson.D{{"$set", bson.D{{"novote", true}}}},
  1022  	}, {
  1023  		C:      controllersC,
  1024  		Id:     modelGlobalKey,
  1025  		Update: bson.D{{"$pull", bson.D{{"votingmachineids", m.doc.Id}}}},
  1026  	}}
  1027  }
  1028  
  1029  func removeControllerOps(m *Machine) []txn.Op {
  1030  	return []txn.Op{{
  1031  		C:      machinesC,
  1032  		Id:     m.doc.DocID,
  1033  		Assert: bson.D{{"novote", true}, {"hasvote", false}},
  1034  		Update: bson.D{
  1035  			{"$pull", bson.D{{"jobs", JobManageModel}}},
  1036  			{"$set", bson.D{{"novote", false}}},
  1037  		},
  1038  	}, {
  1039  		C:      controllersC,
  1040  		Id:     modelGlobalKey,
  1041  		Update: bson.D{{"$pull", bson.D{{"machineids", m.doc.Id}}}},
  1042  	}}
  1043  }