github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/modelgeneration.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  	"sort"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/juju/charm/v12"
    13  	"github.com/juju/collections/set"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/mgo/v3"
    16  	"github.com/juju/mgo/v3/bson"
    17  	"github.com/juju/mgo/v3/txn"
    18  	"github.com/juju/names/v5"
    19  	jujutxn "github.com/juju/txn/v3"
    20  
    21  	"github.com/juju/juju/core/settings"
    22  	"github.com/juju/juju/mongo/utils"
    23  	stateerrors "github.com/juju/juju/state/errors"
    24  )
    25  
    26  // itemChange is the state representation of a core settings ItemChange.
    27  type itemChange struct {
    28  	Type     int         `bson:"type"`
    29  	Key      string      `bson:"key"`
    30  	OldValue interface{} `bson:"old,omitempty"`
    31  	NewValue interface{} `bson:"new,omitempty"`
    32  }
    33  
    34  // coreChange returns the core package representation of this change.
    35  // Stored keys are unescaped.
    36  func (c *itemChange) coreChange() settings.ItemChange {
    37  	return settings.ItemChange{
    38  		Type:     c.Type,
    39  		Key:      utils.UnescapeKey(c.Key),
    40  		OldValue: c.OldValue,
    41  		NewValue: c.NewValue,
    42  	}
    43  }
    44  
    45  // generationDoc represents the state of a model generation in MongoDB.
    46  type generationDoc struct {
    47  	DocId    string `bson:"_id"`
    48  	TxnRevno int64  `bson:"txn-revno"`
    49  
    50  	// Name is the name given to this branch at creation.
    51  	// There should never be more than one branch, not applied or aborted,
    52  	// with the same name. Branch names can otherwise be re-used.
    53  	Name string `bson:"name"`
    54  
    55  	// GenerationId is a monotonically incrementing sequence,
    56  	// set when a branch is committed to the model.
    57  	// Branches that are not applied, or that have been aborted
    58  	// will not have a generation ID set.
    59  	GenerationId int `bson:"generation-id"`
    60  
    61  	// ModelUUID indicates the model to which this generation applies.
    62  	ModelUUID string `bson:"model-uuid"`
    63  
    64  	// AssignedUnits is a map of unit names that are in the generation,
    65  	// keyed by application name.
    66  	// An application ID can be present without any unit IDs,
    67  	// which indicates that it has configuration changes applied in the
    68  	// generation, but no units currently set to be in it.
    69  	AssignedUnits map[string][]string `bson:"assigned-units"`
    70  
    71  	// Config is all changes made to charm configuration under this branch.
    72  	Config map[string][]itemChange `bson:"charm-config"`
    73  
    74  	// TODO (manadart 2019-04-02): CharmURLs, Resources.
    75  
    76  	// Created is a Unix timestamp indicating when this generation was created.
    77  	Created int64 `bson:"created"`
    78  
    79  	// CreatedBy is the user who created this generation.
    80  	CreatedBy string `bson:"created-by"`
    81  
    82  	// Completed, if set, indicates when this generation was completed and
    83  	// effectively became the current model generation.
    84  	Completed int64 `bson:"completed"`
    85  
    86  	// CompletedBy is the user who committed this generation to the model.
    87  	CompletedBy string `bson:"completed-by"`
    88  }
    89  
    90  // Generation represents the state of a model generation.
    91  type Generation struct {
    92  	st  *State
    93  	doc generationDoc
    94  }
    95  
    96  func (g *Generation) BranchName() string {
    97  	return g.doc.Name
    98  }
    99  
   100  // GenerationId indicates the relative order that this branch was committed
   101  // and had its changes applied to the whole model.
   102  func (g *Generation) GenerationId() int {
   103  	return g.doc.GenerationId
   104  }
   105  
   106  // ModelUUID returns the ID of the model to which this generation applies.
   107  func (g *Generation) ModelUUID() string {
   108  	return g.doc.ModelUUID
   109  }
   110  
   111  // AssignedUnits returns the unit names, keyed by application name
   112  // that have been assigned to this generation.
   113  func (g *Generation) AssignedUnits() map[string][]string {
   114  	return g.doc.AssignedUnits
   115  }
   116  
   117  // Config returns all changed charm configuration for the generation.
   118  // The persisted objects are converted to core changes.
   119  func (g *Generation) Config() map[string]settings.ItemChanges {
   120  	changes := make(map[string]settings.ItemChanges, len(g.doc.Config))
   121  	for appName, appCfg := range g.doc.Config {
   122  		appChanges := make(settings.ItemChanges, len(appCfg))
   123  		for i, ch := range appCfg {
   124  			appChanges[i] = ch.coreChange()
   125  		}
   126  		sort.Sort(appChanges)
   127  		changes[appName] = appChanges
   128  	}
   129  	return changes
   130  }
   131  
   132  // Created returns the Unix timestamp at generation creation.
   133  func (g *Generation) Created() int64 {
   134  	return g.doc.Created
   135  }
   136  
   137  // CreatedBy returns the user who created the generation.
   138  func (g *Generation) CreatedBy() string {
   139  	return g.doc.CreatedBy
   140  }
   141  
   142  // IsCompleted returns true if the generation has been completed;
   143  // i.e it has a completion time-stamp.
   144  func (g *Generation) IsCompleted() bool {
   145  	return g.doc.Completed > 0
   146  }
   147  
   148  // Completed returns the Unix timestamp at generation completion.
   149  func (g *Generation) Completed() int64 {
   150  	return g.doc.Completed
   151  }
   152  
   153  // CompletedBy returns the user who committed the generation.
   154  func (g *Generation) CompletedBy() string {
   155  	return g.doc.CompletedBy
   156  }
   157  
   158  // AssignApplication indicates that the application with the input name has had
   159  // changes in this generation.
   160  func (g *Generation) AssignApplication(appName string) error {
   161  	buildTxn := func(attempt int) ([]txn.Op, error) {
   162  		if attempt > 0 {
   163  			if err := g.Refresh(); err != nil {
   164  				return nil, errors.Trace(err)
   165  			}
   166  		}
   167  		if _, ok := g.doc.AssignedUnits[appName]; ok {
   168  			return nil, jujutxn.ErrNoOperations
   169  		}
   170  		if err := g.CheckNotComplete(); err != nil {
   171  			return nil, err
   172  		}
   173  		return assignGenerationAppTxnOps(g.doc.DocId, appName), nil
   174  	}
   175  
   176  	return errors.Trace(g.st.db().Run(buildTxn))
   177  }
   178  
   179  func assignGenerationAppTxnOps(id, appName string) []txn.Op {
   180  	assignedField := "assigned-units"
   181  	appField := fmt.Sprintf("%s.%s", assignedField, appName)
   182  
   183  	return []txn.Op{
   184  		{
   185  			C:  generationsC,
   186  			Id: id,
   187  			Assert: bson.D{{"$and", []bson.D{
   188  				{{"completed", 0}},
   189  				{{assignedField, bson.D{{"$exists", true}}}},
   190  				{{appField, bson.D{{"$exists", false}}}},
   191  			}}},
   192  			Update: bson.D{
   193  				{"$set", bson.D{{appField, []string{}}}},
   194  			},
   195  		},
   196  	}
   197  }
   198  
   199  // AssignAllUnits ensures that all units of the input application are
   200  // designated as tracking the branch, by adding the unit names
   201  // to the generation.
   202  func (g *Generation) AssignAllUnits(appName string) error {
   203  	return g.AssignUnits(appName, 0)
   204  }
   205  
   206  func (g *Generation) AssignUnits(appName string, numUnits int) error {
   207  	buildTxn := func(attempt int) ([]txn.Op, error) {
   208  		if attempt > 0 {
   209  			if err := g.Refresh(); err != nil {
   210  				return nil, errors.Trace(err)
   211  			}
   212  		}
   213  		if err := g.CheckNotComplete(); err != nil {
   214  			return nil, errors.Trace(err)
   215  		}
   216  		unitNames, err := appUnitNames(g.st, appName)
   217  		if err != nil {
   218  			return nil, errors.Trace(err)
   219  		}
   220  		app, err := g.st.Application(appName)
   221  		if err != nil {
   222  			return nil, errors.Trace(err)
   223  		}
   224  		ops := []txn.Op{
   225  			{
   226  				C:  applicationsC,
   227  				Id: app.doc.DocID,
   228  				Assert: bson.D{
   229  					{"life", Alive},
   230  					{"unitcount", app.doc.UnitCount},
   231  				},
   232  			},
   233  		}
   234  		// Ensure we sort the unitNames so that when we ask for the numUnits
   235  		// to track, they're going to be predictable results.
   236  		sort.Strings(unitNames)
   237  
   238  		var assigned int
   239  		assignedUnits := set.NewStrings(g.doc.AssignedUnits[appName]...)
   240  		for _, name := range unitNames {
   241  			if !assignedUnits.Contains(name) {
   242  				if numUnits > 0 && numUnits == assigned {
   243  					break
   244  				}
   245  				unit, err := g.st.Unit(name)
   246  				if err != nil {
   247  					return nil, errors.Trace(err)
   248  				}
   249  				ops = append(ops, assignGenerationUnitTxnOps(g.doc.DocId, appName, unit)...)
   250  				assigned++
   251  			}
   252  		}
   253  		// If there are no units to add to the generation, quit here.
   254  		if assigned == 0 {
   255  			return nil, jujutxn.ErrNoOperations
   256  		}
   257  		return ops, nil
   258  	}
   259  	return errors.Trace(g.st.db().Run(buildTxn))
   260  }
   261  
   262  // AssignUnit indicates that the unit with the input name is tracking this
   263  // branch, by adding the name to the generation.
   264  func (g *Generation) AssignUnit(unitName string) error {
   265  	appName, err := names.UnitApplication(unitName)
   266  	if err != nil {
   267  		return errors.Trace(err)
   268  	}
   269  
   270  	buildTxn := func(attempt int) ([]txn.Op, error) {
   271  		if attempt > 0 {
   272  			if err := g.Refresh(); err != nil {
   273  				return nil, errors.Trace(err)
   274  			}
   275  		}
   276  		if err := g.CheckNotComplete(); err != nil {
   277  			return nil, errors.Trace(err)
   278  		}
   279  		if set.NewStrings(g.doc.AssignedUnits[appName]...).Contains(unitName) {
   280  			return nil, jujutxn.ErrNoOperations
   281  		}
   282  		unit, err := g.st.Unit(unitName)
   283  		if err != nil {
   284  			return nil, errors.Trace(err)
   285  		}
   286  		return assignGenerationUnitTxnOps(g.doc.DocId, appName, unit), nil
   287  	}
   288  
   289  	return errors.Trace(g.st.db().Run(buildTxn))
   290  }
   291  
   292  func assignGenerationUnitTxnOps(id, appName string, unit *Unit) []txn.Op {
   293  	assignedField := "assigned-units"
   294  	appField := fmt.Sprintf("%s.%s", assignedField, appName)
   295  
   296  	return []txn.Op{
   297  		{
   298  			C:      unitsC,
   299  			Id:     unit.doc.DocID,
   300  			Assert: bson.D{{"life", Alive}},
   301  		},
   302  		{
   303  			C:  generationsC,
   304  			Id: id,
   305  			Assert: bson.D{{"$and", []bson.D{
   306  				{{"completed", 0}},
   307  				{{assignedField, bson.D{{"$exists", true}}}},
   308  				{{appField, bson.D{{"$not", bson.D{{"$elemMatch", bson.D{{"$eq", unit.Name()}}}}}}}},
   309  			}}},
   310  			Update: bson.D{
   311  				{"$push", bson.D{{appField, unit.Name()}}},
   312  			},
   313  		},
   314  	}
   315  }
   316  
   317  // UpdateCharmConfig applies the input changes to the input application's
   318  // charm configuration under this branch.
   319  // the incoming charm settings are assumed to have been validated.
   320  func (g *Generation) UpdateCharmConfig(appName string, master *Settings, validChanges charm.Settings) error {
   321  	buildTxn := func(attempt int) ([]txn.Op, error) {
   322  		if attempt > 0 {
   323  			if err := g.Refresh(); err != nil {
   324  				return nil, errors.Trace(err)
   325  			}
   326  		}
   327  		if err := g.CheckNotComplete(); err != nil {
   328  			return nil, errors.Trace(err)
   329  		}
   330  
   331  		// Apply the current branch deltas to the master settings.
   332  		branchChanges := g.Config()
   333  		branchDelta, branchHasDelta := branchChanges[appName]
   334  		if branchHasDelta {
   335  			master.applyChanges(branchDelta)
   336  		}
   337  
   338  		// Now apply the incoming changes on top and generate a new delta.
   339  		for k, v := range validChanges {
   340  			if v == nil {
   341  				master.Delete(k)
   342  			} else {
   343  				master.Set(k, v)
   344  			}
   345  		}
   346  		newDelta := master.changes()
   347  
   348  		// Ensure that the delta represents a change from master settings
   349  		// as they were when each setting was first modified under the branch.
   350  		if branchHasDelta {
   351  			var err error
   352  			if newDelta, err = newDelta.ApplyDeltaSource(branchDelta); err != nil {
   353  				return nil, errors.Trace(err)
   354  			}
   355  		}
   356  
   357  		return []txn.Op{
   358  			{
   359  				C:  generationsC,
   360  				Id: g.doc.DocId,
   361  				Assert: bson.D{{"$and", []bson.D{
   362  					{{"completed", 0}},
   363  					{{"txn-revno", g.doc.TxnRevno}},
   364  				}}},
   365  				Update: bson.D{
   366  					{"$set", bson.D{{"charm-config." + appName, makeItemChanges(newDelta)}}},
   367  				},
   368  			},
   369  		}, nil
   370  	}
   371  
   372  	return errors.Trace(g.st.db().Run(buildTxn))
   373  }
   374  
   375  // Commit marks the generation as completed and assigns it the next value from
   376  // the generation sequence. The new generation ID is returned.
   377  func (g *Generation) Commit(userName string) (int, error) {
   378  	var newGenId int
   379  
   380  	buildTxn := func(attempt int) ([]txn.Op, error) {
   381  		if attempt > 0 {
   382  			if err := g.Refresh(); err != nil {
   383  				return nil, errors.Trace(err)
   384  			}
   385  		}
   386  
   387  		if g.IsCompleted() {
   388  			if g.GenerationId() == 0 {
   389  				return nil, errors.New("branch was already aborted")
   390  			}
   391  			return nil, jujutxn.ErrNoOperations
   392  		}
   393  
   394  		now, err := g.st.ControllerTimestamp()
   395  		if err != nil {
   396  			return nil, errors.Trace(err)
   397  		}
   398  		assigned, err := g.assignedWithAllUnits()
   399  		if err != nil {
   400  			return nil, errors.Trace(err)
   401  		}
   402  		ops, err := g.commitConfigTxnOps()
   403  		if err != nil {
   404  			return nil, errors.Trace(err)
   405  		}
   406  
   407  		// Get the new sequence as late as we can.
   408  		// If assigned is empty, indicating no changes under this branch,
   409  		// then the generation ID in not incremented.
   410  		// This effectively means the generation is aborted, not committed.
   411  		if len(assigned) > 0 {
   412  			id, err := sequenceWithMin(g.st, "generation", 1)
   413  			if err != nil {
   414  				return nil, errors.Trace(err)
   415  			}
   416  			newGenId = id
   417  		}
   418  
   419  		// As a proxy for checking that the generation has not changed,
   420  		// Assert that the txn rev-no has not changed since we materialised
   421  		// this generation object.
   422  		ops = append(ops, txn.Op{
   423  			C:      generationsC,
   424  			Id:     g.doc.DocId,
   425  			Assert: bson.D{{"txn-revno", g.doc.TxnRevno}},
   426  			Update: bson.D{
   427  				{"$set", bson.D{
   428  					{"assigned-units", assigned},
   429  					{"completed", now.Unix()},
   430  					{"completed-by", userName},
   431  					{"generation-id", newGenId},
   432  				}},
   433  			},
   434  		})
   435  		return ops, nil
   436  	}
   437  
   438  	if err := g.st.db().Run(buildTxn); err != nil {
   439  		return 0, errors.Trace(err)
   440  	}
   441  	return newGenId, nil
   442  }
   443  
   444  // assignedWithAllUnits generates a new value for the branch's
   445  // AssignedUnits field, to indicate that all units of changed applications
   446  // are tracking the branch.
   447  func (g *Generation) assignedWithAllUnits() (map[string][]string, error) {
   448  	assigned := g.AssignedUnits()
   449  	for app := range assigned {
   450  		units, err := appUnitNames(g.st, app)
   451  		if err != nil {
   452  			return nil, errors.Trace(err)
   453  		}
   454  		assigned[app] = units
   455  	}
   456  	return assigned, nil
   457  }
   458  
   459  // commitConfigTxnOps iterates over all the applications with configuration
   460  // deltas, determines their effective new settings, then gathers the
   461  // operations representing the changes so that they can all be applied in a
   462  // single transaction.
   463  func (g *Generation) commitConfigTxnOps() ([]txn.Op, error) {
   464  	var ops []txn.Op
   465  	for appName, delta := range g.Config() {
   466  		if len(delta) == 0 {
   467  			continue
   468  		}
   469  		app, err := g.st.Application(appName)
   470  		if err != nil {
   471  			return nil, errors.Trace(err)
   472  		}
   473  
   474  		// Apply the branch delta to the application's charm config settings.
   475  		cfg, err := readSettings(g.st.db(), settingsC, app.charmConfigKey())
   476  		if err != nil {
   477  			return nil, errors.Trace(err)
   478  		}
   479  		cfg.applyChanges(delta)
   480  
   481  		_, updates := cfg.settingsUpdateOps()
   482  		// Assert that the settings document has not changed underneath us
   483  		// in addition to appending the field changes.
   484  		if len(updates) > 0 {
   485  			ops = append(ops, cfg.assertUnchangedOp())
   486  			ops = append(ops, updates...)
   487  		}
   488  	}
   489  	return ops, nil
   490  }
   491  
   492  // Abort marks the generation as completed however no value is assigned from
   493  // the generation sequence.
   494  func (g *Generation) Abort(userName string) error {
   495  	buildTxn := func(attempt int) ([]txn.Op, error) {
   496  		if attempt > 0 {
   497  			if err := g.Refresh(); err != nil {
   498  				return nil, errors.Trace(err)
   499  			}
   500  		}
   501  
   502  		if g.IsCompleted() {
   503  			if g.GenerationId() > 0 {
   504  				return nil, errors.New("branch was already committed")
   505  			}
   506  			return nil, jujutxn.ErrNoOperations
   507  		}
   508  
   509  		// Must have no assigned units.
   510  		assigned := g.AssignedUnits()
   511  		for _, units := range assigned {
   512  			if len(units) > 0 {
   513  				return nil, errors.New("branch is in progress. Either reset values on tracking units and commit the branch or remove them to abort.")
   514  			}
   515  		}
   516  
   517  		// Must not have upgraded charm of tracked application.
   518  		// TODO (hml) 2019-06-26
   519  		// Implement cannot abort branch where tracked application has
   520  		// been upgraded.
   521  
   522  		now, err := g.st.ControllerTimestamp()
   523  		if err != nil {
   524  			return nil, errors.Trace(err)
   525  		}
   526  		// As a proxy for checking that the generation has not changed,
   527  		// Assert that the txn rev-no has not changed since we materialised
   528  		// this generation object.
   529  		ops := []txn.Op{{
   530  			C:      generationsC,
   531  			Id:     g.doc.DocId,
   532  			Assert: bson.D{{"txn-revno", g.doc.TxnRevno}},
   533  			Update: bson.D{
   534  				{"$set", bson.D{
   535  					{"completed", now.Unix()},
   536  					{"completed-by", userName},
   537  				}},
   538  			},
   539  		}}
   540  		return ops, nil
   541  	}
   542  
   543  	return errors.Trace(g.st.db().Run(buildTxn))
   544  }
   545  
   546  // CheckNotComplete returns an error if this
   547  // generation was committed or aborted.
   548  func (g *Generation) CheckNotComplete() error {
   549  	if g.doc.Completed == 0 {
   550  		return nil
   551  	}
   552  
   553  	msg := "committed"
   554  	if g.doc.GenerationId == 0 {
   555  		msg = "aborted"
   556  	}
   557  	return errors.New("branch was already " + msg)
   558  }
   559  
   560  // Refresh refreshes the contents of the generation from the underlying state.
   561  func (g *Generation) Refresh() error {
   562  	col, closer := g.st.db().GetCollection(generationsC)
   563  	defer closer()
   564  
   565  	var doc generationDoc
   566  	if err := col.FindId(g.doc.DocId).One(&doc); err != nil {
   567  		return errors.Trace(err)
   568  	}
   569  	g.doc = doc
   570  	return nil
   571  }
   572  
   573  // IsTracking returns true if the generation is tracking the provided unit.
   574  func (g *Generation) IsTracking(unitName string) bool {
   575  	var tracked bool
   576  	for _, v := range g.doc.AssignedUnits {
   577  		if tracked = set.NewStrings(v...).Contains(unitName); tracked {
   578  			break
   579  		}
   580  	}
   581  	return tracked
   582  }
   583  
   584  func (g *Generation) unassignUnitOps(unitName, appName string) []txn.Op {
   585  	assignedField := "assigned-units"
   586  	appField := fmt.Sprintf("%s.%s", assignedField, appName)
   587  
   588  	// As a proxy for checking that the generation has not changed,
   589  	// Assert that the txn rev-no has not changed since we materialised
   590  	// this generation object.
   591  	return []txn.Op{{
   592  		C:      generationsC,
   593  		Id:     g.doc.DocId,
   594  		Assert: bson.D{{"txn-revno", g.doc.TxnRevno}},
   595  		Update: bson.D{
   596  			{"$pull", bson.D{{appField, unitName}}},
   597  		},
   598  	}}
   599  }
   600  
   601  // HasChangesFor returns true when the generation has config changes for
   602  // the provided application.
   603  func (g *Generation) HasChangesFor(appName string) bool {
   604  	_, ok := g.doc.Config[appName]
   605  	return ok
   606  }
   607  
   608  // unassignAppOps returns operations to remove the tracking and config data
   609  // for the application from the generation.
   610  func (g *Generation) unassignAppOps(appName string) []txn.Op {
   611  	assigned := g.doc.AssignedUnits
   612  	delete(assigned, appName)
   613  	ops := []txn.Op{{
   614  		C:      generationsC,
   615  		Id:     g.doc.DocId,
   616  		Assert: bson.D{{"txn-revno", g.doc.TxnRevno}},
   617  		Update: bson.D{
   618  			{"$set", bson.D{{"assigned-units", assigned}}},
   619  		},
   620  	}}
   621  	currentCfg := g.doc.Config
   622  	if _, ok := currentCfg[appName]; ok {
   623  		newCfg := map[string][]itemChange{}
   624  		for app, cfg := range currentCfg {
   625  			if app == appName {
   626  				continue
   627  			}
   628  			newCfg[app] = cfg
   629  		}
   630  		ops = append(ops, txn.Op{
   631  			C:      generationsC,
   632  			Id:     g.doc.DocId,
   633  			Assert: bson.D{{"txn-revno", g.doc.TxnRevno}},
   634  			Update: bson.D{
   635  				{"$set", bson.D{{"charm-config", newCfg}}},
   636  			},
   637  		})
   638  	}
   639  	return ops
   640  }
   641  
   642  // AddBranch creates a new branch in the current model.
   643  func (m *Model) AddBranch(branchName, userName string) error {
   644  	return errors.Trace(m.st.AddBranch(branchName, userName))
   645  }
   646  
   647  // AddBranch creates a new branch in the current model.
   648  // A branch cannot be created with the same name as another "in-flight" branch.
   649  // The input user indicates the operator who invoked the creation.
   650  func (st *State) AddBranch(branchName, userName string) error {
   651  	id, err := sequence(st, "branch")
   652  	if err != nil {
   653  		return errors.Trace(err)
   654  	}
   655  
   656  	buildTxn := func(attempt int) ([]txn.Op, error) {
   657  		if _, err := st.Branch(branchName); err != nil {
   658  			if !errors.IsNotFound(err) {
   659  				return nil, errors.Annotatef(err, "checking for existing branch")
   660  			}
   661  		} else {
   662  			return nil, errors.Errorf("model already has branch %q", branchName)
   663  		}
   664  
   665  		now, err := st.ControllerTimestamp()
   666  		if err != nil {
   667  			return nil, errors.Trace(err)
   668  		}
   669  		return insertGenerationTxnOps(strconv.Itoa(id), branchName, userName, now), nil
   670  	}
   671  	err = st.db().Run(buildTxn)
   672  	if err != nil {
   673  		err = onAbort(err, stateerrors.ErrDead)
   674  		logger.Errorf("cannot add branch to the model: %v", err)
   675  	}
   676  	return err
   677  }
   678  
   679  func insertGenerationTxnOps(id, branchName, userName string, now *time.Time) []txn.Op {
   680  	doc := &generationDoc{
   681  		Name:          branchName,
   682  		AssignedUnits: map[string][]string{},
   683  		Created:       now.Unix(),
   684  		CreatedBy:     userName,
   685  	}
   686  
   687  	return []txn.Op{
   688  		{
   689  			C:      generationsC,
   690  			Id:     id,
   691  			Insert: doc,
   692  		},
   693  	}
   694  }
   695  
   696  // Generations returns all committed  branches.
   697  func (m *Model) Generations() ([]*Generation, error) {
   698  	b, err := m.st.CommittedBranches()
   699  	return b, errors.Trace(err)
   700  }
   701  
   702  // Branches returns all "in-flight" branches for the model.
   703  func (m *Model) Branches() ([]*Generation, error) {
   704  	b, err := m.st.Branches()
   705  	return b, errors.Trace(err)
   706  }
   707  
   708  // Branches returns all "in-flight" branches.
   709  func (st *State) Branches() ([]*Generation, error) {
   710  	col, closer := st.db().GetCollection(generationsC)
   711  	defer closer()
   712  
   713  	var docs []generationDoc
   714  	if err := col.Find(bson.M{"completed": 0}).All(&docs); err != nil {
   715  		return nil, errors.Trace(err)
   716  	}
   717  
   718  	branches := make([]*Generation, len(docs))
   719  	for i, d := range docs {
   720  		branches[i] = newGeneration(st, &d)
   721  	}
   722  	return branches, nil
   723  }
   724  
   725  // CommittedBranches returns all committed branches.
   726  func (st *State) CommittedBranches() ([]*Generation, error) {
   727  	col, closer := st.db().GetCollection(generationsC)
   728  	defer closer()
   729  
   730  	var docs []generationDoc
   731  	query := bson.M{"generation-id": bson.M{"$gte": 1}}
   732  	if err := col.Find(query).All(&docs); err != nil {
   733  		return nil, errors.Trace(err)
   734  	}
   735  
   736  	branches := make([]*Generation, len(docs))
   737  	for i, d := range docs {
   738  		branches[i] = newGeneration(st, &d)
   739  	}
   740  	return branches, nil
   741  }
   742  
   743  // Branch retrieves the generation with the the input branch name from the
   744  // collection of not-yet-completed generations.
   745  func (m *Model) Branch(name string) (*Generation, error) {
   746  	gen, err := m.st.Branch(name)
   747  	return gen, errors.Trace(err)
   748  }
   749  
   750  // Generation retrieves the generation with the the input generation_id from the
   751  // collection of completed generations.
   752  func (m *Model) Generation(id int) (*Generation, error) {
   753  	gen, err := m.st.CommittedBranch(id)
   754  	return gen, errors.Trace(err)
   755  }
   756  
   757  func (m *Model) applicationBranches(appName string) ([]*Generation, error) {
   758  	branches, err := m.Branches()
   759  	if err != nil {
   760  		return nil, errors.Trace(err)
   761  	}
   762  	foundBranches := make([]*Generation, 0)
   763  	for _, branch := range branches {
   764  		if branch.HasChangesFor(appName) {
   765  			foundBranches = append(foundBranches, branch)
   766  			continue
   767  		}
   768  		if _, ok := branch.doc.AssignedUnits[appName]; ok {
   769  			foundBranches = append(foundBranches, branch)
   770  		}
   771  	}
   772  	return foundBranches, nil
   773  }
   774  
   775  // Branch retrieves the generation with the the input branch name from the
   776  // collection of not-yet-completed generations.
   777  func (st *State) Branch(name string) (*Generation, error) {
   778  	doc, err := st.getBranchDoc(name)
   779  	if err != nil {
   780  		return nil, errors.Trace(err)
   781  	}
   782  	return newGeneration(st, doc), nil
   783  }
   784  
   785  // Generation retrieves the generation with the the input id from the
   786  // collection of completed generations.
   787  func (st *State) CommittedBranch(id int) (*Generation, error) {
   788  	doc, err := st.getCommittedBranchDoc(id)
   789  	if err != nil {
   790  		return nil, errors.Trace(err)
   791  	}
   792  	return newGeneration(st, doc), nil
   793  }
   794  
   795  func (st *State) getBranchDoc(name string) (*generationDoc, error) {
   796  	col, closer := st.db().GetCollection(generationsC)
   797  	defer closer()
   798  
   799  	doc := &generationDoc{}
   800  	err := col.Find(bson.M{
   801  		"name":      name,
   802  		"completed": 0,
   803  	}).One(doc)
   804  
   805  	switch err {
   806  	case nil:
   807  		return doc, nil
   808  	case mgo.ErrNotFound:
   809  		mod, _ := st.modelName()
   810  		return nil, errors.NotFoundf("branch %q in model %q", name, mod)
   811  	default:
   812  		mod, _ := st.modelName()
   813  		return nil, errors.Annotatef(err, "retrieving branch %q in model %q", name, mod)
   814  	}
   815  }
   816  
   817  func (st *State) getCommittedBranchDoc(id int) (*generationDoc, error) {
   818  	col, closer := st.db().GetCollection(generationsC)
   819  	defer closer()
   820  
   821  	doc := &generationDoc{}
   822  	err := col.Find(bson.M{
   823  		"generation-id": id,
   824  	}).One(doc)
   825  
   826  	switch err {
   827  	case nil:
   828  		return doc, nil
   829  	case mgo.ErrNotFound:
   830  		mod, _ := st.modelName()
   831  		return nil, errors.NotFoundf("generation_id %d in model %q", id, mod)
   832  	default:
   833  		mod, _ := st.modelName()
   834  		return nil, errors.Annotatef(err, "retrieving generation_id %q in model %q", id, mod)
   835  	}
   836  }
   837  
   838  func (m *Model) unitBranch(unitName string) (*Generation, error) {
   839  	// NOTE (hml) 2019-07-02
   840  	// Currently a unit may only be tracked in a single generation.
   841  	// The branches spec indicates that may change in the future.  If
   842  	// it does, this method and caller will need to be updated accordingly.
   843  	branches, err := m.Branches()
   844  	if err != nil {
   845  		return nil, errors.Trace(err)
   846  	}
   847  	for _, b := range branches {
   848  		if b.IsTracking(unitName) {
   849  			return b, nil
   850  		}
   851  	}
   852  	return nil, nil
   853  }
   854  
   855  func newGeneration(st *State, doc *generationDoc) *Generation {
   856  	return &Generation{
   857  		st:  st,
   858  		doc: *doc,
   859  	}
   860  }
   861  
   862  // makeItemChanges generates a persistable collection of changes from a core
   863  // settings representation, with keys escaped for Mongo.
   864  func makeItemChanges(coreChanges settings.ItemChanges) []itemChange {
   865  	changes := make([]itemChange, len(coreChanges))
   866  	for i, c := range coreChanges {
   867  		changes[i] = itemChange{
   868  			Type:     c.Type,
   869  			Key:      utils.EscapeKey(c.Key),
   870  			OldValue: c.OldValue,
   871  			NewValue: c.NewValue,
   872  		}
   873  	}
   874  	return changes
   875  }
   876  
   877  // branchesCleanupChange removes the generation doc.
   878  type branchesCleanupChange struct{}
   879  
   880  // Prepare is part of the Change interface.
   881  func (change branchesCleanupChange) Prepare(db Database) ([]txn.Op, error) {
   882  	generations, closer := db.GetCollection(generationsC)
   883  	defer closer()
   884  
   885  	var docs []struct {
   886  		DocID string `bson:"_id"`
   887  	}
   888  	err := generations.Find(nil).Select(bson.D{{"_id", 1}}).All(&docs)
   889  	if err != nil {
   890  		return nil, errors.Trace(err)
   891  	}
   892  	if len(docs) == 0 {
   893  		return nil, ErrChangeComplete
   894  	}
   895  
   896  	ops := make([]txn.Op, len(docs))
   897  	for i, doc := range docs {
   898  		ops[i] = txn.Op{
   899  			C:      generationsC,
   900  			Id:     doc.DocID,
   901  			Remove: true,
   902  		}
   903  	}
   904  	return ops, nil
   905  
   906  }