github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/state/resources_persistence_staged.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"bytes"
     8  
     9  	"github.com/juju/errors"
    10  	"gopkg.in/mgo.v2/txn"
    11  )
    12  
    13  // StagedResource represents resource info that has been added to the
    14  // "staging" area of the underlying data store. It remains unavailable
    15  // until finalized, at which point it moves out of the staging area and
    16  // replaces the current active resource info.
    17  type StagedResource struct {
    18  	base   ResourcePersistenceBase
    19  	id     string
    20  	stored storedResource
    21  }
    22  
    23  func (staged StagedResource) stage() error {
    24  	buildTxn := func(attempt int) ([]txn.Op, error) {
    25  		var ops []txn.Op
    26  		switch attempt {
    27  		case 0:
    28  			ops = newInsertStagedResourceOps(staged.stored)
    29  		case 1:
    30  			ops = newEnsureStagedResourceSameOps(staged.stored)
    31  		default:
    32  			return nil, errors.NewAlreadyExists(nil, "already staged")
    33  		}
    34  		if staged.stored.PendingID == "" {
    35  			// Only non-pending resources must have an existing service.
    36  			ops = append(ops, staged.base.ApplicationExistsOps(staged.stored.ApplicationID)...)
    37  		}
    38  
    39  		return ops, nil
    40  	}
    41  	if err := staged.base.Run(buildTxn); err != nil {
    42  		return errors.Trace(err)
    43  	}
    44  	return nil
    45  }
    46  
    47  // Unstage ensures that the resource is removed
    48  // from the staging area. If it isn't in the staging area
    49  // then this is a noop.
    50  func (staged StagedResource) Unstage() error {
    51  	buildTxn := func(attempt int) ([]txn.Op, error) {
    52  		if attempt > 0 {
    53  			// The op has no assert so we should not get here.
    54  			return nil, errors.New("unstaging the resource failed")
    55  		}
    56  
    57  		ops := newRemoveStagedResourceOps(staged.id)
    58  		return ops, nil
    59  	}
    60  	if err := staged.base.Run(buildTxn); err != nil {
    61  		return errors.Trace(err)
    62  	}
    63  	return nil
    64  }
    65  
    66  // Activate makes the staged resource the active resource.
    67  func (staged StagedResource) Activate() error {
    68  	buildTxn := func(attempt int) ([]txn.Op, error) {
    69  		// This is an "upsert".
    70  		var ops []txn.Op
    71  		switch attempt {
    72  		case 0:
    73  			ops = newInsertResourceOps(staged.stored)
    74  		case 1:
    75  			ops = newUpdateResourceOps(staged.stored)
    76  		default:
    77  			return nil, errors.New("setting the resource failed")
    78  		}
    79  		if staged.stored.PendingID == "" {
    80  			// Only non-pending resources must have an existing service.
    81  			ops = append(ops, staged.base.ApplicationExistsOps(staged.stored.ApplicationID)...)
    82  		}
    83  		// No matter what, we always remove any staging.
    84  		ops = append(ops, newRemoveStagedResourceOps(staged.id)...)
    85  
    86  		// If we are changing the bytes for a resource, we increment the
    87  		// CharmModifiedVersion on the service, since resources are integral to
    88  		// the high level "version" of the charm.
    89  		if staged.stored.PendingID == "" {
    90  			hasNewBytes, err := staged.hasNewBytes()
    91  			if err != nil {
    92  				logger.Errorf("can't read existing resource during activate: %v", errors.Details(err))
    93  				return nil, errors.Trace(err)
    94  			}
    95  			if hasNewBytes {
    96  				incOps := staged.base.IncCharmModifiedVersionOps(staged.stored.ApplicationID)
    97  				ops = append(ops, incOps...)
    98  			}
    99  		}
   100  		logger.Debugf("activate ops: %#v", ops)
   101  		return ops, nil
   102  	}
   103  	if err := staged.base.Run(buildTxn); err != nil {
   104  		return errors.Trace(err)
   105  	}
   106  	return nil
   107  }
   108  
   109  func (staged StagedResource) hasNewBytes() (bool, error) {
   110  	var current resourceDoc
   111  	err := staged.base.One(resourcesC, staged.stored.ID, &current)
   112  	switch {
   113  	case errors.IsNotFound(err):
   114  		// if there's no current resource stored, then any non-zero bytes will
   115  		// be new.
   116  		return !staged.stored.Fingerprint.IsZero(), nil
   117  	case err != nil:
   118  		return false, errors.Annotate(err, "couldn't read existing resource")
   119  	default:
   120  		diff := !bytes.Equal(staged.stored.Fingerprint.Bytes(), current.Fingerprint)
   121  		return diff, nil
   122  	}
   123  }