github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/storage/resolver.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storage 5 6 import ( 7 "github.com/juju/errors" 8 "gopkg.in/juju/charm.v6-unstable/hooks" 9 "gopkg.in/juju/names.v2" 10 11 "github.com/juju/juju/apiserver/params" 12 "github.com/juju/juju/storage" 13 "github.com/juju/juju/worker/uniter/hook" 14 "github.com/juju/juju/worker/uniter/operation" 15 "github.com/juju/juju/worker/uniter/remotestate" 16 "github.com/juju/juju/worker/uniter/resolver" 17 ) 18 19 // StorageResolverOperations instances know how to make operations 20 // required by the resolver. 21 type StorageResolverOperations interface { 22 NewUpdateStorage(tags []names.StorageTag) (operation.Operation, error) 23 NewRunHook(hookInfo hook.Info) (operation.Operation, error) 24 } 25 26 type storageResolver struct { 27 storage *Attachments 28 dying bool 29 life map[names.StorageTag]params.Life 30 } 31 32 // NewResolver returns a new storage resolver. 33 func NewResolver(storage *Attachments) resolver.Resolver { 34 return &storageResolver{ 35 storage: storage, 36 life: make(map[names.StorageTag]params.Life), 37 } 38 } 39 40 // NextOp is defined on the Resolver interface. 41 func (s *storageResolver) NextOp( 42 localState resolver.LocalState, 43 remoteState remotestate.Snapshot, 44 opFactory operation.Factory, 45 ) (operation.Operation, error) { 46 47 if remoteState.Life == params.Dying { 48 // The unit is dying, so destroy all of its storage. 49 if !s.dying { 50 if err := s.storage.SetDying(); err != nil { 51 return nil, errors.Trace(err) 52 } 53 s.dying = true 54 } 55 for tag, snap := range remoteState.Storage { 56 snap.Life = params.Dying 57 remoteState.Storage[tag] = snap 58 } 59 } 60 61 if err := s.maybeShortCircuitRemoval(remoteState.Storage); err != nil { 62 return nil, errors.Trace(err) 63 } 64 65 var runStorageHooks bool 66 switch { 67 case localState.Kind == operation.Continue: 68 // There's nothing in progress. 69 runStorageHooks = true 70 case !localState.Installed && localState.Kind == operation.RunHook && localState.Step == operation.Queued: 71 // The install operation completed, and there's an install 72 // hook queued. Run storage-attached hooks first. 73 runStorageHooks = true 74 } 75 if !runStorageHooks { 76 return nil, resolver.ErrNoOperation 77 } 78 79 if !localState.Installed && s.storage.Pending() == 0 { 80 logger.Infof("initial storage attachments ready") 81 } 82 83 for tag, snap := range remoteState.Storage { 84 op, err := s.nextHookOp(tag, snap, opFactory) 85 if errors.Cause(err) == resolver.ErrNoOperation { 86 continue 87 } 88 return op, err 89 } 90 if s.storage.Pending() > 0 { 91 logger.Debugf("still pending %v", s.storage.pending.SortedValues()) 92 if !localState.Installed { 93 // We only wait for pending storage before 94 // the install hook runs; we should not block 95 // other hooks from running while storage is 96 // being provisioned. 97 return nil, resolver.ErrWaiting 98 } 99 } 100 return nil, resolver.ErrNoOperation 101 } 102 103 // maybeShortCircuitRemoval removes any storage that is not alive, 104 // and has not had a storage-attached hook committed. 105 func (s *storageResolver) maybeShortCircuitRemoval(remote map[names.StorageTag]remotestate.StorageSnapshot) error { 106 for tag, snap := range remote { 107 local, ok := s.storage.storageAttachments[tag] 108 if (ok && local.attached) || snap.Life == params.Alive { 109 continue 110 } 111 if err := s.storage.removeStorageAttachment(tag); err != nil { 112 return errors.Trace(err) 113 } 114 delete(remote, tag) 115 } 116 return nil 117 } 118 119 func (s *storageResolver) nextHookOp( 120 tag names.StorageTag, 121 snap remotestate.StorageSnapshot, 122 opFactory operation.Factory, 123 ) (operation.Operation, error) { 124 125 logger.Debugf("next hook op for %v: %+v", tag, snap) 126 127 if snap.Life == params.Dead { 128 // Storage must have been Dying to become Dead; 129 // no further action is required. 130 return nil, resolver.ErrNoOperation 131 } 132 133 hookInfo := hook.Info{StorageId: tag.Id()} 134 switch snap.Life { 135 case params.Alive: 136 storageAttachment, ok := s.storage.storageAttachments[tag] 137 if ok && storageAttachment.attached { 138 // Once the storage is attached, we only care about 139 // lifecycle state changes. 140 return nil, resolver.ErrNoOperation 141 } 142 // The storage-attached hook has not been committed, so add the 143 // storage to the pending set. 144 s.storage.pending.Add(tag) 145 if !snap.Attached { 146 // The storage attachment has not been provisioned yet, 147 // so just ignore it for now. We'll be notified again 148 // when it has been provisioned. 149 return nil, resolver.ErrNoOperation 150 } 151 // The storage is alive, but we haven't previously run the 152 // "storage-attached" hook. Do so now. 153 hookInfo.Kind = hooks.StorageAttached 154 case params.Dying: 155 storageAttachment, ok := s.storage.storageAttachments[tag] 156 if !ok || !storageAttachment.attached { 157 // Nothing to do: attachment is dying, but 158 // the storage-attached hook has not been 159 // issued. 160 return nil, resolver.ErrNoOperation 161 } 162 // The storage is dying, but we haven't previously run the 163 // "storage-detached" hook. Do so now. 164 hookInfo.Kind = hooks.StorageDetaching 165 } 166 167 // Update the local state to reflect what we're about to report 168 // to a hook. 169 stateFile, err := readStateFile(s.storage.storageStateDir, tag) 170 if err != nil { 171 return nil, errors.Trace(err) 172 } 173 s.storage.storageAttachments[tag] = storageAttachment{ 174 stateFile, &contextStorage{ 175 tag: tag, 176 kind: storage.StorageKind(snap.Kind), 177 location: snap.Location, 178 }, 179 } 180 181 return opFactory.NewRunHook(hookInfo) 182 }