launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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  
    10  	"labix.org/v2/mgo/txn"
    11  
    12  	errgo "launchpad.net/errgo/errors"
    13  	"launchpad.net/juju-core/constraints"
    14  	"launchpad.net/juju-core/errors"
    15  	"launchpad.net/juju-core/instance"
    16  	"launchpad.net/juju-core/replicaset"
    17  	"launchpad.net/juju-core/state/api/params"
    18  	"launchpad.net/juju-core/utils"
    19  )
    20  
    21  // MachineTemplate holds attributes that are to be associated
    22  // with a newly created machine.
    23  type MachineTemplate struct {
    24  	// Series is the series to be associated with the new machine.
    25  	Series string
    26  
    27  	// Constraints are the constraints to be used when finding
    28  	// an instance for the machine.
    29  	Constraints constraints.Value
    30  
    31  	// Jobs holds the jobs to run on the machine's instance.
    32  	// A machine must have at least one job to do.
    33  	// JobManageEnviron can only be part of the jobs
    34  	// when the first (bootstrap) machine is added.
    35  	Jobs []MachineJob
    36  
    37  	// NoVote holds whether a machine running
    38  	// a state server should abstain from peer voting.
    39  	// It is ignored if Jobs does not contain JobManageEnviron.
    40  	NoVote bool
    41  
    42  	// Addresses holds the addresses to be associated with the
    43  	// new machine.
    44  	Addresses []instance.Address
    45  
    46  	// InstanceId holds the instance id to associate with the machine.
    47  	// If this is empty, the provisioner will try to provision the machine.
    48  	// If this is non-empty, the HardwareCharacteristics and Nonce
    49  	// fields must be set appropriately.
    50  	InstanceId instance.Id
    51  
    52  	// HardwareCharacteristics holds the h/w characteristics to
    53  	// be associated with the machine.
    54  	HardwareCharacteristics instance.HardwareCharacteristics
    55  
    56  	// Nonce holds a unique value that can be used to check
    57  	// if a new instance was really started for this machine.
    58  	// See Machine.SetProvisioned. This must be set if InstanceId is set.
    59  	Nonce string
    60  
    61  	// Dirty signifies whether the new machine will be treated
    62  	// as unclean for unit-assignment purposes.
    63  	Dirty bool
    64  
    65  	// principals holds the principal units that will
    66  	// associated with the machine.
    67  	principals []string
    68  }
    69  
    70  // AddMachineInsideNewMachine creates a new machine within a container
    71  // of the given type inside another new machine. The two given templates
    72  // specify the form of the child and parent respectively.
    73  func (st *State) AddMachineInsideNewMachine(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*Machine, error) {
    74  	mdoc, ops, err := st.addMachineInsideNewMachineOps(template, parentTemplate, containerType)
    75  	if err != nil {
    76  		return nil, errgo.Notef(err, "cannot add a new machine")
    77  	}
    78  	return st.addMachine(mdoc, ops)
    79  }
    80  
    81  // AddMachineInsideMachine adds a machine inside a container of the
    82  // given type on the existing machine with id=parentId.
    83  func (st *State) AddMachineInsideMachine(template MachineTemplate, parentId string, containerType instance.ContainerType) (*Machine, error) {
    84  	mdoc, ops, err := st.addMachineInsideMachineOps(template, parentId, containerType)
    85  	if err != nil {
    86  		return nil, errgo.Notef(err, "cannot add a new machine")
    87  	}
    88  	return st.addMachine(mdoc, ops)
    89  }
    90  
    91  // AddMachine adds a machine with the given series and jobs.
    92  // It is deprecated and around for testing purposes only.
    93  func (st *State) AddMachine(series string, jobs ...MachineJob) (*Machine, error) {
    94  	ms, err := st.AddMachines(MachineTemplate{
    95  		Series: series,
    96  		Jobs:   jobs,
    97  	})
    98  	if err != nil {
    99  		return nil, mask(err)
   100  	}
   101  	return ms[0], nil
   102  }
   103  
   104  // AddOne machine adds a new machine configured according to the
   105  // given template.
   106  func (st *State) AddOneMachine(template MachineTemplate) (*Machine, error) {
   107  	ms, err := st.AddMachines(template)
   108  	if err != nil {
   109  		return nil, mask(err)
   110  	}
   111  	return ms[0], nil
   112  }
   113  
   114  // AddMachines adds new machines configured according to the
   115  // given templates.
   116  func (st *State) AddMachines(templates ...MachineTemplate) (_ []*Machine, err error) {
   117  	defer utils.ErrorContextf(&err, "cannot add a new machine")
   118  	var ms []*Machine
   119  	env, err := st.Environment()
   120  	if err != nil {
   121  		return nil, mask(err)
   122  	} else if env.Life() != Alive {
   123  		return nil, errgo.Newf("environment is no longer alive")
   124  	}
   125  	var ops []txn.Op
   126  	var mdocs []*machineDoc
   127  	for _, template := range templates {
   128  		mdoc, addOps, err := st.addMachineOps(template)
   129  		if err != nil {
   130  			return nil, mask(err)
   131  		}
   132  		mdocs = append(mdocs, mdoc)
   133  		ms = append(ms, newMachine(st, mdoc))
   134  		ops = append(ops, addOps...)
   135  	}
   136  	ssOps, err := st.maintainStateServersOps(mdocs, nil)
   137  	if err != nil {
   138  		return nil, mask(err)
   139  	}
   140  	ops = append(ops, ssOps...)
   141  	ops = append(ops, env.assertAliveOp())
   142  	if err := st.runTransaction(ops); err != nil {
   143  		return nil, onAbort(err, errgo.Newf("environment is no longer alive"))
   144  	}
   145  	return ms, nil
   146  }
   147  
   148  func (st *State) addMachine(mdoc *machineDoc, ops []txn.Op) (*Machine, error) {
   149  	env, err := st.Environment()
   150  	if err != nil {
   151  		return nil, mask(err)
   152  	} else if env.Life() != Alive {
   153  		return nil, errgo.Newf("environment is no longer alive")
   154  	}
   155  	ops = append([]txn.Op{env.assertAliveOp()}, ops...)
   156  	if err := st.runTransaction(ops); err != nil {
   157  		enverr := env.Refresh()
   158  		if (enverr == nil && env.Life() != Alive) || errors.IsNotFoundError(enverr) {
   159  			return nil, errgo.Newf("environment is no longer alive")
   160  		} else if enverr != nil {
   161  			err = enverr
   162  		}
   163  		return nil, err
   164  	}
   165  	return newMachine(st, mdoc), nil
   166  }
   167  
   168  // effectiveMachineTemplate verifies that the given template is
   169  // valid and combines it with values from the state
   170  // to produce a resulting template that more accurately
   171  // represents the data that will be inserted into the state.
   172  func (st *State) effectiveMachineTemplate(p MachineTemplate, allowStateServer bool) (MachineTemplate, error) {
   173  	if p.Series == "" {
   174  		return MachineTemplate{}, errgo.Newf("no series specified")
   175  	}
   176  	cons, err := st.EnvironConstraints()
   177  	if err != nil {
   178  		return MachineTemplate{}, mask(err)
   179  	}
   180  	p.Constraints = p.Constraints.WithFallbacks(cons)
   181  	// Machine constraints do not use a container constraint value.
   182  	// Both provisioning and deployment constraints use the same
   183  	// constraints.Value struct so here we clear the container
   184  	// value. Provisioning ignores the container value but clearing
   185  	// it avoids potential confusion.
   186  	p.Constraints.Container = nil
   187  
   188  	if len(p.Jobs) == 0 {
   189  		return MachineTemplate{}, errgo.Newf("no jobs specified")
   190  	}
   191  	jset := make(map[MachineJob]bool)
   192  	for _, j := range p.Jobs {
   193  		if jset[j] {
   194  			return MachineTemplate{}, errgo.Newf("duplicate job: %s", j)
   195  		}
   196  		jset[j] = true
   197  	}
   198  	if jset[JobManageEnviron] {
   199  		if !allowStateServer {
   200  			return MachineTemplate{}, errStateServerNotAllowed
   201  		}
   202  	}
   203  
   204  	if p.InstanceId != "" {
   205  		if p.Nonce == "" {
   206  			return MachineTemplate{}, errgo.Newf("cannot add a machine with an instance id and no nonce")
   207  		}
   208  	} else if p.Nonce != "" {
   209  		return MachineTemplate{}, errgo.Newf("cannot specify a nonce without an instance id")
   210  	}
   211  	return p, nil
   212  }
   213  
   214  // addMachineOps returns operations to add a new top level machine
   215  // based on the given template. It also returns the machine document
   216  // that will be inserted.
   217  func (st *State) addMachineOps(template MachineTemplate) (*machineDoc, []txn.Op, error) {
   218  	template, err := st.effectiveMachineTemplate(template, true)
   219  	if err != nil {
   220  		return nil, nil, mask(err)
   221  	}
   222  	seq, err := st.sequence("machine")
   223  	if err != nil {
   224  		return nil, nil, mask(err)
   225  	}
   226  	mdoc := machineDocForTemplate(template, strconv.Itoa(seq))
   227  	var ops []txn.Op
   228  	ops = append(ops, st.insertNewMachineOps(mdoc, template.Constraints)...)
   229  	ops = append(ops, st.insertNewContainerRefOp(mdoc.Id))
   230  	if template.InstanceId != "" {
   231  		ops = append(ops, txn.Op{
   232  			C:      st.instanceData.Name,
   233  			Id:     mdoc.Id,
   234  			Assert: txn.DocMissing,
   235  			Insert: &instanceData{
   236  				Id:         mdoc.Id,
   237  				InstanceId: template.InstanceId,
   238  				Arch:       template.HardwareCharacteristics.Arch,
   239  				Mem:        template.HardwareCharacteristics.Mem,
   240  				RootDisk:   template.HardwareCharacteristics.RootDisk,
   241  				CpuCores:   template.HardwareCharacteristics.CpuCores,
   242  				CpuPower:   template.HardwareCharacteristics.CpuPower,
   243  				Tags:       template.HardwareCharacteristics.Tags,
   244  			},
   245  		})
   246  	}
   247  	return mdoc, ops, nil
   248  }
   249  
   250  // supportsContainerType reports whether the machine supports the given
   251  // container type. If the machine's supportedContainers attribute is
   252  // set, this decision can be made right here, otherwise we assume that
   253  // everything will be ok and later on put the container into an error
   254  // state if necessary.
   255  func (m *Machine) supportsContainerType(ctype instance.ContainerType) bool {
   256  	supportedContainers, ok := m.SupportedContainers()
   257  	if !ok {
   258  		// We don't know yet, so we report that we support the container.
   259  		return true
   260  	}
   261  	for _, ct := range supportedContainers {
   262  		if ct == ctype {
   263  			return true
   264  		}
   265  	}
   266  	return false
   267  }
   268  
   269  // addMachineInsideMachineOps returns operations to add a machine inside
   270  // a container of the given type on an existing machine.
   271  func (st *State) addMachineInsideMachineOps(template MachineTemplate, parentId string, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) {
   272  	if template.InstanceId != "" {
   273  		return nil, nil, errgo.Newf("cannot specify instance id for a new container")
   274  	}
   275  	template, err := st.effectiveMachineTemplate(template, false)
   276  	if err != nil {
   277  		return nil, nil, mask(err)
   278  	}
   279  	if containerType == "" {
   280  		return nil, nil, errgo.Newf("no container type specified")
   281  	}
   282  
   283  	// If a parent machine is specified, make sure it exists
   284  	// and can support the requested container type.
   285  	parent, err := st.Machine(parentId)
   286  	if err != nil {
   287  		return nil, nil, mask(err)
   288  	}
   289  	if !parent.supportsContainerType(containerType) {
   290  		return nil, nil, errgo.Newf("machine %s cannot host %s containers", parentId, containerType)
   291  	}
   292  	newId, err := st.newContainerId(parentId, containerType)
   293  	if err != nil {
   294  		return nil, nil, mask(err)
   295  	}
   296  	mdoc := machineDocForTemplate(template, newId)
   297  	mdoc.ContainerType = string(containerType)
   298  	var ops []txn.Op
   299  	ops = append(ops, st.insertNewMachineOps(mdoc, template.Constraints)...)
   300  	ops = append(ops,
   301  		// Update containers record for host machine.
   302  		st.addChildToContainerRefOp(parentId, mdoc.Id),
   303  		// Create a containers reference document for the container itself.
   304  		st.insertNewContainerRefOp(mdoc.Id),
   305  	)
   306  	return mdoc, ops, nil
   307  }
   308  
   309  // newContainerId returns a new id for a machine within the machine
   310  // with id parentId and the given container type.
   311  func (st *State) newContainerId(parentId string, containerType instance.ContainerType) (string, error) {
   312  	seq, err := st.sequence(fmt.Sprintf("machine%s%sContainer", parentId, containerType))
   313  	if err != nil {
   314  		return "", mask(err)
   315  	}
   316  	return fmt.Sprintf("%s/%s/%d", parentId, containerType, seq), nil
   317  }
   318  
   319  // addMachineInsideNewMachineOps returns operations to create a new
   320  // machine within a container of the given type inside another
   321  // new machine. The two given templates specify the form
   322  // of the child and parent respectively.
   323  func (st *State) addMachineInsideNewMachineOps(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) {
   324  	if template.InstanceId != "" || parentTemplate.InstanceId != "" {
   325  		return nil, nil, errgo.Newf("cannot specify instance id for a new container")
   326  	}
   327  	seq, err := st.sequence("machine")
   328  	if err != nil {
   329  		return nil, nil, mask(err)
   330  	}
   331  	parentTemplate, err = st.effectiveMachineTemplate(parentTemplate, false)
   332  	if err != nil {
   333  		return nil, nil, mask(err)
   334  	}
   335  	parentDoc := machineDocForTemplate(parentTemplate, strconv.Itoa(seq))
   336  	newId, err := st.newContainerId(parentDoc.Id, containerType)
   337  	if err != nil {
   338  		return nil, nil, mask(err)
   339  	}
   340  	template, err = st.effectiveMachineTemplate(template, false)
   341  	if err != nil {
   342  		return nil, nil, mask(err)
   343  	}
   344  	mdoc := machineDocForTemplate(template, newId)
   345  	mdoc.ContainerType = string(containerType)
   346  	var ops []txn.Op
   347  	ops = append(ops, st.insertNewMachineOps(parentDoc, parentTemplate.Constraints)...)
   348  	ops = append(ops, st.insertNewMachineOps(mdoc, template.Constraints)...)
   349  	ops = append(ops,
   350  		// The host machine doesn't exist yet, create a new containers record.
   351  		st.insertNewContainerRefOp(mdoc.Id),
   352  		// Create a containers reference document for the container itself.
   353  		st.insertNewContainerRefOp(parentDoc.Id, mdoc.Id),
   354  	)
   355  	return mdoc, ops, nil
   356  }
   357  
   358  func machineDocForTemplate(template MachineTemplate, id string) *machineDoc {
   359  	return &machineDoc{
   360  		Id:         id,
   361  		Series:     template.Series,
   362  		Jobs:       template.Jobs,
   363  		Clean:      !template.Dirty,
   364  		Principals: template.principals,
   365  		Life:       Alive,
   366  		InstanceId: template.InstanceId,
   367  		Nonce:      template.Nonce,
   368  		Addresses:  instanceAddressesToAddresses(template.Addresses),
   369  		NoVote:     template.NoVote,
   370  	}
   371  }
   372  
   373  // insertNewMachineOps returns operations to insert the given machine
   374  // document and its associated constraints into the database.
   375  func (st *State) insertNewMachineOps(mdoc *machineDoc, cons constraints.Value) []txn.Op {
   376  	return []txn.Op{
   377  		{
   378  			C:      st.machines.Name,
   379  			Id:     mdoc.Id,
   380  			Assert: txn.DocMissing,
   381  			Insert: mdoc,
   382  		},
   383  		createConstraintsOp(st, machineGlobalKey(mdoc.Id), cons),
   384  		createStatusOp(st, machineGlobalKey(mdoc.Id), statusDoc{
   385  			Status: params.StatusPending,
   386  		}),
   387  	}
   388  }
   389  
   390  func hasJob(jobs []MachineJob, job MachineJob) bool {
   391  	for _, j := range jobs {
   392  		if j == job {
   393  			return true
   394  		}
   395  	}
   396  	return false
   397  }
   398  
   399  var errStateServerNotAllowed = errgo.Newf("state server jobs specified without calling EnsureAvailability")
   400  
   401  // maintainStateServersOps returns a set of operations that will maintain
   402  // the state server information when the given machine documents
   403  // are added to the machines collection. If currentInfo is nil,
   404  // there can be only one machine document and it must have
   405  // id 0 (this is a special case to allow adding the bootstrap machine)
   406  func (st *State) maintainStateServersOps(mdocs []*machineDoc, currentInfo *StateServerInfo) ([]txn.Op, error) {
   407  	var newIds, newVotingIds []string
   408  	for _, doc := range mdocs {
   409  		if !hasJob(doc.Jobs, JobManageEnviron) {
   410  			continue
   411  		}
   412  		newIds = append(newIds, doc.Id)
   413  		if !doc.NoVote {
   414  			newVotingIds = append(newVotingIds, doc.Id)
   415  		}
   416  	}
   417  	if len(newIds) == 0 {
   418  		return nil, nil
   419  	}
   420  	if currentInfo == nil {
   421  		// Allow bootstrap machine only.
   422  		if len(mdocs) != 1 || mdocs[0].Id != "0" {
   423  			return nil, errStateServerNotAllowed
   424  		}
   425  		var err error
   426  		currentInfo, err = st.StateServerInfo()
   427  		if err != nil {
   428  			return nil, errgo.Notef(err, "cannot get state server info")
   429  		}
   430  		if len(currentInfo.MachineIds) > 0 || len(currentInfo.VotingMachineIds) > 0 {
   431  			return nil, errgo.Newf("state servers already exist")
   432  		}
   433  	}
   434  	ops := []txn.Op{{
   435  		C:  st.stateServers.Name,
   436  		Id: environGlobalKey,
   437  		Assert: D{{
   438  			"$and", []D{
   439  				{{"machineids", D{{"$size", len(currentInfo.MachineIds)}}}},
   440  				{{"votingmachineids", D{{"$size", len(currentInfo.VotingMachineIds)}}}},
   441  			},
   442  		}},
   443  		Update: D{
   444  			{"$addToSet", D{{"machineids", D{{"$each", newIds}}}}},
   445  			{"$addToSet", D{{"votingmachineids", D{{"$each", newVotingIds}}}}},
   446  		},
   447  	}}
   448  	return ops, nil
   449  }
   450  
   451  // EnsureAvailability adds state server machines as necessary to make
   452  // the number of live state servers equal to numStateServers. The given
   453  // constraints and series will be attached to any new machines.
   454  //
   455  // TODO(rog):
   456  // If any current state servers are down, they will be
   457  // removed from the current set of voting replica set
   458  // peers (although the machines themselves will remain
   459  // and they will still remain part of the replica set).
   460  // Once a machine's voting status has been removed,
   461  // the machine itself may be removed.
   462  func (st *State) EnsureAvailability(numStateServers int, cons constraints.Value, series string) error {
   463  	if numStateServers%2 != 1 || numStateServers <= 0 {
   464  		return errgo.Newf("number of state servers must be odd and greater than zero")
   465  	}
   466  	if numStateServers > replicaset.MaxPeers {
   467  		return errgo.Newf("state server count is too large (allowed %d)", replicaset.MaxPeers)
   468  	}
   469  	info, err := st.StateServerInfo()
   470  	if err != nil {
   471  		return mask(err)
   472  	}
   473  	if len(info.VotingMachineIds) == numStateServers {
   474  		// TODO(rog) #1271504 2014-01-22
   475  		// Find machines which are down, set
   476  		// their NoVote flag and add new machines to
   477  		// replace them.
   478  		return nil
   479  	}
   480  	if len(info.VotingMachineIds) > numStateServers {
   481  		return errgo.Newf("cannot reduce state server count")
   482  	}
   483  	mdocs := make([]*machineDoc, 0, numStateServers-len(info.MachineIds))
   484  	var ops []txn.Op
   485  	for i := len(info.MachineIds); i < numStateServers; i++ {
   486  		template := MachineTemplate{
   487  			Series: series,
   488  			Jobs: []MachineJob{
   489  				JobHostUnits,
   490  				JobManageEnviron,
   491  			},
   492  			Constraints: cons,
   493  		}
   494  		mdoc, addOps, err := st.addMachineOps(template)
   495  		if err != nil {
   496  			return mask(err)
   497  		}
   498  		mdocs = append(mdocs, mdoc)
   499  		ops = append(ops, addOps...)
   500  	}
   501  	ssOps, err := st.maintainStateServersOps(mdocs, info)
   502  	if err != nil {
   503  		return errgo.Notef(err, "cannot prepare machine add operations")
   504  	}
   505  	ops = append(ops, ssOps...)
   506  	err = st.runTransaction(ops)
   507  	if err != nil {
   508  		return errgo.Notef(err, "failed to create new state server machines")
   509  	}
   510  	return nil
   511  }