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, ¤t) 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 }