github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/plans/changes_sync.go (about) 1 package plans 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/iaas-resource-provision/iaas-rpc/internal/addrs" 8 "github.com/iaas-resource-provision/iaas-rpc/internal/states" 9 ) 10 11 // ChangesSync is a wrapper around a Changes that provides a concurrency-safe 12 // interface to insert new changes and retrieve copies of existing changes. 13 // 14 // Each ChangesSync is independent of all others, so all concurrent writers 15 // to a particular Changes must share a single ChangesSync. Behavior is 16 // undefined if any other caller makes changes to the underlying Changes 17 // object or its nested objects concurrently with any of the methods of a 18 // particular ChangesSync. 19 type ChangesSync struct { 20 lock sync.Mutex 21 changes *Changes 22 } 23 24 // IsFullDestroy returns true if the set of changes indicates we are doing a 25 // destroy of all resources. 26 func (cs *ChangesSync) IsFullDestroy() bool { 27 if cs == nil { 28 panic("FullDestroy on nil ChangesSync") 29 } 30 cs.lock.Lock() 31 defer cs.lock.Unlock() 32 33 for _, c := range cs.changes.Resources { 34 if c.Action != Delete { 35 return false 36 } 37 } 38 39 return true 40 } 41 42 // AppendResourceInstanceChange records the given resource instance change in 43 // the set of planned resource changes. 44 // 45 // The caller must ensure that there are no concurrent writes to the given 46 // change while this method is running, but it is safe to resume mutating 47 // it after this method returns without affecting the saved change. 48 func (cs *ChangesSync) AppendResourceInstanceChange(changeSrc *ResourceInstanceChangeSrc) { 49 if cs == nil { 50 panic("AppendResourceInstanceChange on nil ChangesSync") 51 } 52 cs.lock.Lock() 53 defer cs.lock.Unlock() 54 55 s := changeSrc.DeepCopy() 56 cs.changes.Resources = append(cs.changes.Resources, s) 57 } 58 59 // GetResourceInstanceChange searches the set of resource instance changes for 60 // one matching the given address and generation, returning it if it exists. 61 // 62 // If no such change exists, nil is returned. 63 // 64 // The returned object is a deep copy of the change recorded in the plan, so 65 // callers may mutate it although it's generally better (less confusing) to 66 // treat planned changes as immutable after they've been initially constructed. 67 func (cs *ChangesSync) GetResourceInstanceChange(addr addrs.AbsResourceInstance, gen states.Generation) *ResourceInstanceChangeSrc { 68 if cs == nil { 69 panic("GetResourceInstanceChange on nil ChangesSync") 70 } 71 cs.lock.Lock() 72 defer cs.lock.Unlock() 73 74 if gen == states.CurrentGen { 75 return cs.changes.ResourceInstance(addr).DeepCopy() 76 } 77 if dk, ok := gen.(states.DeposedKey); ok { 78 return cs.changes.ResourceInstanceDeposed(addr, dk).DeepCopy() 79 } 80 panic(fmt.Sprintf("unsupported generation value %#v", gen)) 81 } 82 83 // GetChangesForConfigResource searched the set of resource instance 84 // changes and returns all changes related to a given configuration address. 85 // This is be used to find possible changes related to a configuration 86 // reference. 87 // 88 // If no such changes exist, nil is returned. 89 // 90 // The returned objects are a deep copy of the change recorded in the plan, so 91 // callers may mutate them although it's generally better (less confusing) to 92 // treat planned changes as immutable after they've been initially constructed. 93 func (cs *ChangesSync) GetChangesForConfigResource(addr addrs.ConfigResource) []*ResourceInstanceChangeSrc { 94 if cs == nil { 95 panic("GetChangesForConfigResource on nil ChangesSync") 96 } 97 cs.lock.Lock() 98 defer cs.lock.Unlock() 99 var changes []*ResourceInstanceChangeSrc 100 for _, c := range cs.changes.InstancesForConfigResource(addr) { 101 changes = append(changes, c.DeepCopy()) 102 } 103 return changes 104 } 105 106 // RemoveResourceInstanceChange searches the set of resource instance changes 107 // for one matching the given address and generation, and removes it from the 108 // set if it exists. 109 func (cs *ChangesSync) RemoveResourceInstanceChange(addr addrs.AbsResourceInstance, gen states.Generation) { 110 if cs == nil { 111 panic("RemoveResourceInstanceChange on nil ChangesSync") 112 } 113 cs.lock.Lock() 114 defer cs.lock.Unlock() 115 116 dk := states.NotDeposed 117 if realDK, ok := gen.(states.DeposedKey); ok { 118 dk = realDK 119 } 120 121 addrStr := addr.String() 122 for i, r := range cs.changes.Resources { 123 if r.Addr.String() != addrStr || r.DeposedKey != dk { 124 continue 125 } 126 copy(cs.changes.Resources[i:], cs.changes.Resources[i+1:]) 127 cs.changes.Resources = cs.changes.Resources[:len(cs.changes.Resources)-1] 128 return 129 } 130 } 131 132 // AppendOutputChange records the given output value change in the set of 133 // planned value changes. 134 // 135 // The caller must ensure that there are no concurrent writes to the given 136 // change while this method is running, but it is safe to resume mutating 137 // it after this method returns without affecting the saved change. 138 func (cs *ChangesSync) AppendOutputChange(changeSrc *OutputChangeSrc) { 139 if cs == nil { 140 panic("AppendOutputChange on nil ChangesSync") 141 } 142 cs.lock.Lock() 143 defer cs.lock.Unlock() 144 145 s := changeSrc.DeepCopy() 146 cs.changes.Outputs = append(cs.changes.Outputs, s) 147 } 148 149 // GetOutputChange searches the set of output value changes for one matching 150 // the given address, returning it if it exists. 151 // 152 // If no such change exists, nil is returned. 153 // 154 // The returned object is a deep copy of the change recorded in the plan, so 155 // callers may mutate it although it's generally better (less confusing) to 156 // treat planned changes as immutable after they've been initially constructed. 157 func (cs *ChangesSync) GetOutputChange(addr addrs.AbsOutputValue) *OutputChangeSrc { 158 if cs == nil { 159 panic("GetOutputChange on nil ChangesSync") 160 } 161 cs.lock.Lock() 162 defer cs.lock.Unlock() 163 164 return cs.changes.OutputValue(addr) 165 } 166 167 // GetOutputChanges searches the set of output changes for any that reside in 168 // module instances beneath the given module. If no changes exist, nil 169 // is returned. 170 // 171 // The returned objects are a deep copy of the change recorded in the plan, so 172 // callers may mutate them although it's generally better (less confusing) to 173 // treat planned changes as immutable after they've been initially constructed. 174 func (cs *ChangesSync) GetOutputChanges(parent addrs.ModuleInstance, module addrs.ModuleCall) []*OutputChangeSrc { 175 if cs == nil { 176 panic("GetOutputChange on nil ChangesSync") 177 } 178 cs.lock.Lock() 179 defer cs.lock.Unlock() 180 181 return cs.changes.OutputValues(parent, module) 182 } 183 184 // RemoveOutputChange searches the set of output value changes for one matching 185 // the given address, and removes it from the set if it exists. 186 func (cs *ChangesSync) RemoveOutputChange(addr addrs.AbsOutputValue) { 187 if cs == nil { 188 panic("RemoveOutputChange on nil ChangesSync") 189 } 190 cs.lock.Lock() 191 defer cs.lock.Unlock() 192 193 addrStr := addr.String() 194 for i, o := range cs.changes.Outputs { 195 if o.Addr.String() != addrStr { 196 continue 197 } 198 copy(cs.changes.Outputs[i:], cs.changes.Outputs[i+1:]) 199 cs.changes.Outputs = cs.changes.Outputs[:len(cs.changes.Outputs)-1] 200 return 201 } 202 }