github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	"github.com/juju/errors"
    11  	"github.com/juju/mgo/v3/txn"
    12  	"github.com/juju/names/v5"
    13  
    14  	"github.com/juju/juju/core/constraints"
    15  	"github.com/juju/juju/core/instance"
    16  	"github.com/juju/juju/core/network"
    17  	"github.com/juju/juju/core/status"
    18  	"github.com/juju/juju/storage"
    19  )
    20  
    21  // MachineTemplate holds attributes that are to be associated
    22  // with a newly created machine.
    23  type MachineTemplate struct {
    24  	// Base is the base to be associated with the new machine.
    25  	Base Base
    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  	// JobManageModel can only be part of the jobs
    34  	// when the first (bootstrap) machine is added.
    35  	Jobs []MachineJob
    36  
    37  	// Addresses holds the addresses to be associated with the
    38  	// new machine.
    39  	//
    40  	// TODO(dimitern): This should be removed once all addresses
    41  	// come from link-layer device addresses.
    42  	Addresses network.SpaceAddresses
    43  
    44  	// InstanceId holds the instance id to associate with the machine.
    45  	// If this is empty, the provisioner will try to provision the machine.
    46  	// If this is non-empty, the HardwareCharacteristics and Nonce
    47  	// fields must be set appropriately.
    48  	InstanceId instance.Id
    49  
    50  	// DisplayName holds the human readable name for the instance
    51  	// associated with the machine.
    52  	DisplayName string
    53  
    54  	// HardwareCharacteristics holds the h/w characteristics to
    55  	// be associated with the machine.
    56  	HardwareCharacteristics instance.HardwareCharacteristics
    57  
    58  	// LinkLayerDevices holds a list of arguments for setting link-layer devices
    59  	// on the machine.
    60  	LinkLayerDevices []LinkLayerDeviceArgs
    61  
    62  	// Volumes holds the parameters for volumes that are to be created
    63  	// and attached to the machine.
    64  	Volumes []HostVolumeParams
    65  
    66  	// VolumeAttachments holds the parameters for attaching existing
    67  	// volumes to the machine.
    68  	VolumeAttachments map[names.VolumeTag]VolumeAttachmentParams
    69  
    70  	// Filesystems holds the parameters for filesystems that are to be
    71  	// created and attached to the machine.
    72  	Filesystems []HostFilesystemParams
    73  
    74  	// FilesystemAttachments holds the parameters for attaching existing
    75  	// filesystems to the machine.
    76  	FilesystemAttachments map[names.FilesystemTag]FilesystemAttachmentParams
    77  
    78  	// Nonce holds a unique value that can be used to check
    79  	// if a new instance was really started for this machine.
    80  	// See Machine.SetProvisioned. This must be set if InstanceId is set.
    81  	Nonce string
    82  
    83  	// Dirty signifies whether the new machine will be treated
    84  	// as unclean for unit-assignment purposes.
    85  	Dirty bool
    86  
    87  	// Placement holds the placement directive that will be associated
    88  	// with the machine.
    89  	Placement string
    90  
    91  	// principals holds the principal units that will
    92  	// associated with the machine.
    93  	principals []string
    94  }
    95  
    96  // HostVolumeParams holds the parameters for creating a volume and
    97  // attaching it to a new host.
    98  type HostVolumeParams struct {
    99  	Volume     VolumeParams
   100  	Attachment VolumeAttachmentParams
   101  }
   102  
   103  // HostFilesystemParams holds the parameters for creating a filesystem
   104  // and attaching it to a new host.
   105  type HostFilesystemParams struct {
   106  	Filesystem FilesystemParams
   107  	Attachment FilesystemAttachmentParams
   108  }
   109  
   110  // AddMachineInsideNewMachine creates a new machine within a container
   111  // of the given type inside another new machine. The two given templates
   112  // specify the form of the child and parent respectively.
   113  func (st *State) AddMachineInsideNewMachine(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*Machine, error) {
   114  	mdoc, ops, err := st.addMachineInsideNewMachineOps(template, parentTemplate, containerType)
   115  	if err != nil {
   116  		return nil, errors.Annotate(err, "cannot add a new machine")
   117  	}
   118  	return st.addMachine(mdoc, ops)
   119  }
   120  
   121  // AddMachineInsideMachine adds a machine inside a container of the
   122  // given type on the existing machine with id=parentId.
   123  func (st *State) AddMachineInsideMachine(template MachineTemplate, parentId string, containerType instance.ContainerType) (*Machine, error) {
   124  	mdoc, ops, err := st.addMachineInsideMachineOps(template, parentId, containerType)
   125  	if err != nil {
   126  		return nil, errors.Annotate(err, "cannot add a new machine")
   127  	}
   128  	return st.addMachine(mdoc, ops)
   129  }
   130  
   131  // AddMachine adds a machine with the given series and jobs.
   132  // It is deprecated and around for testing purposes only.
   133  func (st *State) AddMachine(base Base, jobs ...MachineJob) (*Machine, error) {
   134  	ms, err := st.AddMachines(MachineTemplate{
   135  		Base: base,
   136  		Jobs: jobs,
   137  	})
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	return ms[0], nil
   142  }
   143  
   144  // AddOneMachine machine adds a new machine configured according to the
   145  // given template.
   146  func (st *State) AddOneMachine(template MachineTemplate) (*Machine, error) {
   147  	ms, err := st.AddMachines(template)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	return ms[0], nil
   152  }
   153  
   154  // AddMachines adds new machines configured according to the
   155  // given templates.
   156  func (st *State) AddMachines(templates ...MachineTemplate) (_ []*Machine, err error) {
   157  	defer errors.DeferredAnnotatef(&err, "cannot add a new machine")
   158  	var ms []*Machine
   159  	var ops []txn.Op
   160  	var controllerIds []string
   161  	for _, template := range templates {
   162  		mdoc, addOps, err := st.addMachineOps(template)
   163  		if err != nil {
   164  			return nil, errors.Trace(err)
   165  		}
   166  		if isController(mdoc) {
   167  			controllerIds = append(controllerIds, mdoc.Id)
   168  		}
   169  		ms = append(ms, newMachine(st, mdoc))
   170  		ops = append(ops, addOps...)
   171  	}
   172  	ssOps, err := st.maintainControllersOps(controllerIds, true)
   173  	if err != nil {
   174  		return nil, errors.Trace(err)
   175  	}
   176  	ops = append(ops, ssOps...)
   177  	ops = append(ops, assertModelActiveOp(st.ModelUUID()))
   178  	if err := st.db().RunTransaction(ops); err != nil {
   179  		if errors.Cause(err) == txn.ErrAborted {
   180  			if err := checkModelActive(st); err != nil {
   181  				return nil, errors.Trace(err)
   182  			}
   183  		}
   184  		return nil, errors.Trace(err)
   185  	}
   186  	return ms, nil
   187  }
   188  
   189  func (st *State) addMachine(mdoc *machineDoc, ops []txn.Op) (*Machine, error) {
   190  	ops = append([]txn.Op{assertModelActiveOp(st.ModelUUID())}, ops...)
   191  	if err := st.db().RunTransaction(ops); err != nil {
   192  		if errors.Cause(err) == txn.ErrAborted {
   193  			if err := checkModelActive(st); err != nil {
   194  				return nil, errors.Trace(err)
   195  			}
   196  		}
   197  		return nil, errors.Trace(err)
   198  	}
   199  	return newMachine(st, mdoc), nil
   200  }
   201  
   202  func (st *State) resolveMachineConstraints(cons constraints.Value) (constraints.Value, error) {
   203  	mcons, err := st.ResolveConstraints(cons)
   204  	if err != nil {
   205  		return constraints.Value{}, err
   206  	}
   207  	// Machine constraints do not use a container constraint value.
   208  	// Both provisioning and deployment constraints use the same
   209  	// constraints.Value struct so here we clear the container
   210  	// value. Provisioning ignores the container value but clearing
   211  	// it avoids potential confusion.
   212  	mcons.Container = nil
   213  	return mcons, nil
   214  }
   215  
   216  // effectiveMachineTemplate verifies that the given template is
   217  // valid and combines it with values from the state
   218  // to produce a resulting template that more accurately
   219  // represents the data that will be inserted into the state.
   220  func (st *State) effectiveMachineTemplate(p MachineTemplate, allowController bool) (tmpl MachineTemplate, err error) {
   221  	// First check for obvious errors.
   222  	if p.Base.String() == "" {
   223  		return tmpl, errors.New("no base specified")
   224  	}
   225  	if p.InstanceId != "" {
   226  		if p.Nonce == "" {
   227  			return tmpl, errors.New("cannot add a machine with an instance id and no nonce")
   228  		}
   229  	} else if p.Nonce != "" {
   230  		return tmpl, errors.New("cannot specify a nonce without an instance id")
   231  	}
   232  
   233  	// We ignore all constraints if there's a placement directive.
   234  	if p.Placement == "" {
   235  		p.Constraints, err = st.resolveMachineConstraints(p.Constraints)
   236  		if err != nil {
   237  			return tmpl, err
   238  		}
   239  	}
   240  
   241  	if len(p.Jobs) == 0 {
   242  		return tmpl, errors.New("no jobs specified")
   243  	}
   244  	jset := make(map[MachineJob]bool)
   245  	for _, j := range p.Jobs {
   246  		if jset[j] {
   247  			return MachineTemplate{}, errors.Errorf("duplicate job: %s", j)
   248  		}
   249  		jset[j] = true
   250  	}
   251  	if jset[JobManageModel] {
   252  		if !allowController {
   253  			return tmpl, errControllerNotAllowed
   254  		}
   255  	}
   256  	return p, nil
   257  }
   258  
   259  // addMachineOps returns operations to add a new top level machine
   260  // based on the given template. It also returns the machine document
   261  // that will be inserted.
   262  func (st *State) addMachineOps(template MachineTemplate) (*machineDoc, []txn.Op, error) {
   263  	template, err := st.effectiveMachineTemplate(template, st.IsController())
   264  	if err != nil {
   265  		return nil, nil, err
   266  	}
   267  	if template.InstanceId == "" {
   268  		volumeAttachments, err := st.machineTemplateVolumeAttachmentParams(template)
   269  		if err != nil {
   270  			return nil, nil, err
   271  		}
   272  		if err := st.precheckInstance(
   273  			template.Base,
   274  			template.Constraints,
   275  			template.Placement,
   276  			volumeAttachments,
   277  		); err != nil {
   278  			return nil, nil, err
   279  		}
   280  	}
   281  	seq, err := sequence(st, "machine")
   282  	if err != nil {
   283  		return nil, nil, err
   284  	}
   285  	mdoc := st.machineDocForTemplate(template, strconv.Itoa(seq))
   286  	prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template)
   287  	if err != nil {
   288  		return nil, nil, errors.Trace(err)
   289  	}
   290  
   291  	prereqOps = append(prereqOps, assertModelActiveOp(st.ModelUUID()))
   292  	prereqOps = append(prereqOps, insertNewContainerRefOp(st, mdoc.Id))
   293  	if template.InstanceId != "" {
   294  		prereqOps = append(prereqOps, txn.Op{
   295  			C:      instanceDataC,
   296  			Id:     mdoc.DocID,
   297  			Assert: txn.DocMissing,
   298  			Insert: &instanceData{
   299  				DocID:          mdoc.DocID,
   300  				MachineId:      mdoc.Id,
   301  				InstanceId:     template.InstanceId,
   302  				DisplayName:    template.DisplayName,
   303  				ModelUUID:      mdoc.ModelUUID,
   304  				Arch:           template.HardwareCharacteristics.Arch,
   305  				Mem:            template.HardwareCharacteristics.Mem,
   306  				RootDisk:       template.HardwareCharacteristics.RootDisk,
   307  				RootDiskSource: template.HardwareCharacteristics.RootDiskSource,
   308  				CpuCores:       template.HardwareCharacteristics.CpuCores,
   309  				CpuPower:       template.HardwareCharacteristics.CpuPower,
   310  				Tags:           template.HardwareCharacteristics.Tags,
   311  				AvailZone:      template.HardwareCharacteristics.AvailabilityZone,
   312  				VirtType:       template.HardwareCharacteristics.VirtType,
   313  			},
   314  		})
   315  	}
   316  	if isController(mdoc) {
   317  		prereqOps = append(prereqOps, addControllerNodeOp(st, mdoc.Id, false))
   318  	}
   319  
   320  	return mdoc, append(prereqOps, machineOp), nil
   321  }
   322  
   323  // supportsContainerType reports whether the machine supports the given
   324  // container type. If the machine's supportedContainers attribute is
   325  // set, this decision can be made right here, otherwise we assume that
   326  // everything will be ok and later on put the container into an error
   327  // state if necessary.
   328  func (m *Machine) supportsContainerType(ctype instance.ContainerType) bool {
   329  	supportedContainers, ok := m.SupportedContainers()
   330  	if !ok {
   331  		// We don't know yet, so we report that we support the container.
   332  		return true
   333  	}
   334  	for _, ct := range supportedContainers {
   335  		if ct == ctype {
   336  			return true
   337  		}
   338  	}
   339  	return false
   340  }
   341  
   342  // addMachineInsideMachineOps returns operations to add a machine inside
   343  // a container of the given type on an existing machine.
   344  func (st *State) addMachineInsideMachineOps(template MachineTemplate, parentId string, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) {
   345  	if template.InstanceId != "" {
   346  		return nil, nil, errors.New("cannot specify instance id for a new container")
   347  	}
   348  	template, err := st.effectiveMachineTemplate(template, false)
   349  	if err != nil {
   350  		return nil, nil, err
   351  	}
   352  	if containerType == "" {
   353  		return nil, nil, errors.New("no container type specified")
   354  	}
   355  
   356  	// If a parent machine is specified, make sure it exists
   357  	// and can support the requested container type.
   358  	parent, err := st.Machine(parentId)
   359  	if err != nil {
   360  		return nil, nil, err
   361  	}
   362  	if !parent.supportsContainerType(containerType) {
   363  		return nil, nil, errors.Errorf("machine %s cannot host %s containers", parentId, containerType)
   364  	}
   365  
   366  	// Ensure that the machine is not locked for series-upgrade.
   367  	locked, err := parent.IsLockedForSeriesUpgrade()
   368  	if err != nil {
   369  		return nil, nil, err
   370  	}
   371  	if locked {
   372  		return nil, nil, errors.Errorf("machine %s is locked for series upgrade", parentId)
   373  	}
   374  
   375  	newId, err := st.newContainerId(parentId, containerType)
   376  	if err != nil {
   377  		return nil, nil, err
   378  	}
   379  	mdoc := st.machineDocForTemplate(template, newId)
   380  	mdoc.ContainerType = string(containerType)
   381  	prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template)
   382  	if err != nil {
   383  		return nil, nil, errors.Trace(err)
   384  	}
   385  	prereqOps = append(prereqOps,
   386  		// Update containers record for host machine.
   387  		addChildToContainerRefOp(st, parentId, mdoc.Id),
   388  		// Create a containers reference document for the container itself.
   389  		insertNewContainerRefOp(st, mdoc.Id),
   390  	)
   391  	return mdoc, append(prereqOps, machineOp), nil
   392  }
   393  
   394  // newContainerId returns a new id for a machine within the machine
   395  // with id parentId and the given container type.
   396  func (st *State) newContainerId(parentId string, containerType instance.ContainerType) (string, error) {
   397  	name := fmt.Sprintf("machine%s%sContainer", parentId, containerType)
   398  	seq, err := sequence(st, name)
   399  	if err != nil {
   400  		return "", err
   401  	}
   402  	return fmt.Sprintf("%s/%s/%d", parentId, containerType, seq), nil
   403  }
   404  
   405  // addMachineInsideNewMachineOps returns operations to create a new
   406  // machine within a container of the given type inside another
   407  // new machine. The two given templates specify the form
   408  // of the child and parent respectively.
   409  func (st *State) addMachineInsideNewMachineOps(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) {
   410  	if template.InstanceId != "" || parentTemplate.InstanceId != "" {
   411  		return nil, nil, errors.New("cannot specify instance id for a new container")
   412  	}
   413  	seq, err := sequence(st, "machine")
   414  	if err != nil {
   415  		return nil, nil, err
   416  	}
   417  	parentTemplate, err = st.effectiveMachineTemplate(parentTemplate, false)
   418  	if err != nil {
   419  		return nil, nil, err
   420  	}
   421  	if containerType == "" {
   422  		return nil, nil, errors.New("no container type specified")
   423  	}
   424  	if parentTemplate.InstanceId == "" {
   425  		volumeAttachments, err := st.machineTemplateVolumeAttachmentParams(parentTemplate)
   426  		if err != nil {
   427  			return nil, nil, err
   428  		}
   429  		if err := st.precheckInstance(
   430  			parentTemplate.Base,
   431  			parentTemplate.Constraints,
   432  			parentTemplate.Placement,
   433  			volumeAttachments,
   434  		); err != nil {
   435  			return nil, nil, err
   436  		}
   437  	}
   438  
   439  	parentDoc := st.machineDocForTemplate(parentTemplate, strconv.Itoa(seq))
   440  	newId, err := st.newContainerId(parentDoc.Id, containerType)
   441  	if err != nil {
   442  		return nil, nil, err
   443  	}
   444  	template, err = st.effectiveMachineTemplate(template, false)
   445  	if err != nil {
   446  		return nil, nil, err
   447  	}
   448  	mdoc := st.machineDocForTemplate(template, newId)
   449  	mdoc.ContainerType = string(containerType)
   450  	parentPrereqOps, parentOp, err := st.insertNewMachineOps(parentDoc, parentTemplate)
   451  	if err != nil {
   452  		return nil, nil, errors.Trace(err)
   453  	}
   454  	prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template)
   455  	if err != nil {
   456  		return nil, nil, errors.Trace(err)
   457  	}
   458  	prereqOps = append(prereqOps, parentPrereqOps...)
   459  	prereqOps = append(prereqOps,
   460  		// The host machine doesn't exist yet, create a new containers record.
   461  		insertNewContainerRefOp(st, mdoc.Id),
   462  		// Create a containers reference document for the container itself.
   463  		insertNewContainerRefOp(st, parentDoc.Id, mdoc.Id),
   464  	)
   465  	return mdoc, append(prereqOps, parentOp, machineOp), nil
   466  }
   467  
   468  func (st *State) machineTemplateVolumeAttachmentParams(t MachineTemplate) ([]storage.VolumeAttachmentParams, error) {
   469  	sb, err := NewStorageBackend(st)
   470  	if err != nil {
   471  		return nil, errors.Trace(err)
   472  	}
   473  	out := make([]storage.VolumeAttachmentParams, 0, len(t.VolumeAttachments))
   474  	for volumeTag, a := range t.VolumeAttachments {
   475  		v, err := sb.Volume(volumeTag)
   476  		if err != nil {
   477  			return nil, errors.Trace(err)
   478  		}
   479  		volumeInfo, err := v.Info()
   480  		if err != nil {
   481  			return nil, errors.Trace(err)
   482  		}
   483  		providerType, _, _, err := poolStorageProvider(sb, volumeInfo.Pool)
   484  		if err != nil {
   485  			return nil, errors.Trace(err)
   486  		}
   487  		out = append(out, storage.VolumeAttachmentParams{
   488  			AttachmentParams: storage.AttachmentParams{
   489  				Provider: providerType,
   490  				ReadOnly: a.ReadOnly,
   491  			},
   492  			Volume:   volumeTag,
   493  			VolumeId: volumeInfo.VolumeId,
   494  		})
   495  	}
   496  	return out, nil
   497  }
   498  
   499  func (st *State) machineDocForTemplate(template MachineTemplate, id string) *machineDoc {
   500  	// We ignore the error from Select*Address as an error indicates
   501  	// no address is available, in which case the empty address is returned
   502  	// and setting the preferred address to an empty one is the correct
   503  	// thing to do when none is available.
   504  	privateAddr, _ := template.Addresses.OneMatchingScope(network.ScopeMatchCloudLocal)
   505  	publicAddr, _ := template.Addresses.OneMatchingScope(network.ScopeMatchPublic)
   506  	logger.Infof(
   507  		"new machine %q has preferred addresses: private %q, public %q",
   508  		id, privateAddr, publicAddr,
   509  	)
   510  	base := template.Base.Normalise()
   511  	return &machineDoc{
   512  		DocID:                   st.docID(id),
   513  		Id:                      id,
   514  		ModelUUID:               st.ModelUUID(),
   515  		Base:                    base,
   516  		Jobs:                    template.Jobs,
   517  		Clean:                   !template.Dirty,
   518  		Principals:              template.principals,
   519  		Life:                    Alive,
   520  		Nonce:                   template.Nonce,
   521  		Addresses:               fromNetworkAddresses(template.Addresses, network.OriginMachine),
   522  		PreferredPrivateAddress: fromNetworkAddress(privateAddr, network.OriginMachine),
   523  		PreferredPublicAddress:  fromNetworkAddress(publicAddr, network.OriginMachine),
   524  		Placement:               template.Placement,
   525  	}
   526  }
   527  
   528  // insertNewMachineOps returns operations to insert the given machine document
   529  // into the database, based on the given template. Only the constraints are
   530  // taken from the template.
   531  func (st *State) insertNewMachineOps(mdoc *machineDoc, template MachineTemplate) (prereqOps []txn.Op, machineOp txn.Op, err error) {
   532  	now := st.clock().Now()
   533  	machineStatusDoc := statusDoc{
   534  		Status:    status.Pending,
   535  		ModelUUID: st.ModelUUID(),
   536  		Updated:   now.UnixNano(),
   537  	}
   538  	instanceStatusDoc := statusDoc{
   539  		Status:    status.Pending,
   540  		ModelUUID: st.ModelUUID(),
   541  		Updated:   now.UnixNano(),
   542  	}
   543  	modificationStatusDoc := statusDoc{
   544  		Status:    status.Idle,
   545  		ModelUUID: st.ModelUUID(),
   546  		Updated:   now.UnixNano(),
   547  	}
   548  
   549  	prereqOps, machineOp = st.baseNewMachineOps(
   550  		mdoc,
   551  		machineStatusDoc,
   552  		instanceStatusDoc,
   553  		modificationStatusDoc,
   554  		template.Constraints,
   555  	)
   556  
   557  	sb, err := NewStorageBackend(st)
   558  	if err != nil {
   559  		return nil, txn.Op{}, errors.Trace(err)
   560  	}
   561  	storageOps, volumeAttachments, filesystemAttachments, err := sb.hostStorageOps(
   562  		mdoc.Id, &storageParams{
   563  			filesystems:           template.Filesystems,
   564  			filesystemAttachments: template.FilesystemAttachments,
   565  			volumes:               template.Volumes,
   566  			volumeAttachments:     template.VolumeAttachments,
   567  		},
   568  	)
   569  	if err != nil {
   570  		return nil, txn.Op{}, errors.Trace(err)
   571  	}
   572  	for _, a := range volumeAttachments {
   573  		mdoc.Volumes = append(mdoc.Volumes, a.tag.Id())
   574  	}
   575  	for _, a := range filesystemAttachments {
   576  		mdoc.Filesystems = append(mdoc.Filesystems, a.tag.Id())
   577  	}
   578  	prereqOps = append(prereqOps, storageOps...)
   579  
   580  	// At the last moment we still have statusDoc in scope, set the initial
   581  	// history entry. This is risky, and may lead to extra entries, but that's
   582  	// an intrinsic problem with mixing txn and non-txn ops -- we can't sync
   583  	// them cleanly.
   584  	_, _ = probablyUpdateStatusHistory(st.db(), machineGlobalKey(mdoc.Id), machineStatusDoc)
   585  	_, _ = probablyUpdateStatusHistory(st.db(), machineGlobalInstanceKey(mdoc.Id), instanceStatusDoc)
   586  	_, _ = probablyUpdateStatusHistory(st.db(), machineGlobalModificationKey(mdoc.Id), modificationStatusDoc)
   587  	return prereqOps, machineOp, nil
   588  }
   589  
   590  func (st *State) baseNewMachineOps(mdoc *machineDoc,
   591  	machineStatusDoc, instanceStatusDoc, modificationStatusDoc statusDoc,
   592  	cons constraints.Value,
   593  ) (prereqOps []txn.Op, machineOp txn.Op) {
   594  	machineOp = txn.Op{
   595  		C:      machinesC,
   596  		Id:     mdoc.DocID,
   597  		Assert: txn.DocMissing,
   598  		Insert: mdoc,
   599  	}
   600  
   601  	globalKey := machineGlobalKey(mdoc.Id)
   602  	globalInstanceKey := machineGlobalInstanceKey(mdoc.Id)
   603  	globalModificationKey := machineGlobalModificationKey(mdoc.Id)
   604  
   605  	prereqOps = []txn.Op{
   606  		createConstraintsOp(globalKey, cons),
   607  		createStatusOp(st, globalKey, machineStatusDoc),
   608  		createStatusOp(st, globalInstanceKey, instanceStatusDoc),
   609  		createStatusOp(st, globalModificationKey, modificationStatusDoc),
   610  		createMachineBlockDevicesOp(mdoc.Id),
   611  		addModelMachineRefOp(st, mdoc.Id),
   612  	}
   613  	return prereqOps, machineOp
   614  }