github.com/alibaba/sealer@v0.8.6-0.20220430115802-37a2bdaa8173/pkg/image/save/distributionpkg/proxy/scheduler/scheduler.go (about) 1 // Copyright © 2021 https://github.com/distribution/distribution 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package scheduler 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "sync" 22 "time" 23 24 dcontext "github.com/distribution/distribution/v3/context" 25 "github.com/distribution/distribution/v3/reference" 26 "github.com/distribution/distribution/v3/registry/storage/driver" 27 ) 28 29 // onTTLExpiryFunc is called when a repository's TTL expires 30 type expiryFunc func(reference.Reference) error 31 32 const ( 33 entryTypeBlob = iota 34 entryTypeManifest 35 indexSaveFrequency = 5 * time.Second 36 ) 37 38 // schedulerEntry represents an entry in the scheduler 39 // fields are exported for serialization 40 type schedulerEntry struct { 41 Key string `json:"Key"` 42 Expiry time.Time `json:"ExpiryData"` 43 EntryType int `json:"EntryType"` 44 45 timer *time.Timer 46 } 47 48 // New returns a new instance of the scheduler 49 func New(ctx context.Context, driver driver.StorageDriver, path string) *TTLExpirationScheduler { 50 return &TTLExpirationScheduler{ 51 entries: make(map[string]*schedulerEntry), 52 driver: driver, 53 pathToStateFile: path, 54 ctx: ctx, 55 stopped: true, 56 doneChan: make(chan struct{}), 57 saveTimer: time.NewTicker(indexSaveFrequency), 58 } 59 } 60 61 // TTLExpirationScheduler is a scheduler used to perform actions 62 // when TTLs expire 63 type TTLExpirationScheduler struct { 64 sync.Mutex 65 66 entries map[string]*schedulerEntry 67 68 driver driver.StorageDriver 69 ctx context.Context 70 pathToStateFile string 71 72 stopped bool 73 74 onBlobExpire expiryFunc 75 onManifestExpire expiryFunc 76 77 indexDirty bool 78 saveTimer *time.Ticker 79 doneChan chan struct{} 80 } 81 82 // OnBlobExpire is called when a scheduled blob's TTL expires 83 func (ttles *TTLExpirationScheduler) OnBlobExpire(f expiryFunc) { 84 ttles.Lock() 85 defer ttles.Unlock() 86 87 ttles.onBlobExpire = f 88 } 89 90 // OnManifestExpire is called when a scheduled manifest's TTL expires 91 func (ttles *TTLExpirationScheduler) OnManifestExpire(f expiryFunc) { 92 ttles.Lock() 93 defer ttles.Unlock() 94 95 ttles.onManifestExpire = f 96 } 97 98 // AddBlob schedules a blob cleanup after ttl expires 99 func (ttles *TTLExpirationScheduler) AddBlob(blobRef reference.Canonical, ttl time.Duration) error { 100 ttles.Lock() 101 defer ttles.Unlock() 102 103 if ttles.stopped { 104 return fmt.Errorf("scheduler not started") 105 } 106 107 ttles.add(blobRef, ttl, entryTypeBlob) 108 return nil 109 } 110 111 // AddManifest schedules a manifest cleanup after ttl expires 112 func (ttles *TTLExpirationScheduler) AddManifest(manifestRef reference.Canonical, ttl time.Duration) error { 113 ttles.Lock() 114 defer ttles.Unlock() 115 116 if ttles.stopped { 117 return fmt.Errorf("scheduler not started") 118 } 119 120 ttles.add(manifestRef, ttl, entryTypeManifest) 121 return nil 122 } 123 124 // Start starts the scheduler 125 func (ttles *TTLExpirationScheduler) Start() error { 126 ttles.Lock() 127 defer ttles.Unlock() 128 129 if err := ttles.readState(); err != nil { 130 return err 131 } 132 133 if !ttles.stopped { 134 return fmt.Errorf("scheduler already started") 135 } 136 137 // dcontext.GetLogger(ttles.ctx).Infof("Starting cached object TTL expiration scheduler...") 138 ttles.stopped = false 139 140 // Start timer for each deserialized entry 141 for _, entry := range ttles.entries { 142 entry.timer = ttles.startTimer(entry, time.Until(entry.Expiry)) 143 } 144 145 // Start a ticker to periodically save the entries index 146 147 go func() { 148 for { 149 select { 150 case <-ttles.saveTimer.C: 151 ttles.Lock() 152 if !ttles.indexDirty { 153 ttles.Unlock() 154 continue 155 } 156 157 err := ttles.writeState() 158 if err != nil { 159 dcontext.GetLogger(ttles.ctx).Errorf("Error writing scheduler state: %s", err) 160 } else { 161 ttles.indexDirty = false 162 } 163 ttles.Unlock() 164 165 case <-ttles.doneChan: 166 return 167 } 168 } 169 }() 170 171 return nil 172 } 173 174 func (ttles *TTLExpirationScheduler) add(r reference.Reference, ttl time.Duration, eType int) { 175 entry := &schedulerEntry{ 176 Key: r.String(), 177 Expiry: time.Now().Add(ttl), 178 EntryType: eType, 179 } 180 // dcontext.GetLogger(ttles.ctx).Infof("Adding new scheduler entry for %s with ttl=%s", entry.Key, time.Until(entry.Expiry)) 181 if oldEntry, present := ttles.entries[entry.Key]; present && oldEntry.timer != nil { 182 oldEntry.timer.Stop() 183 } 184 ttles.entries[entry.Key] = entry 185 entry.timer = ttles.startTimer(entry, ttl) 186 ttles.indexDirty = true 187 } 188 189 func (ttles *TTLExpirationScheduler) startTimer(entry *schedulerEntry, ttl time.Duration) *time.Timer { 190 return time.AfterFunc(ttl, func() { 191 ttles.Lock() 192 defer ttles.Unlock() 193 194 var f expiryFunc 195 196 switch entry.EntryType { 197 case entryTypeBlob: 198 f = ttles.onBlobExpire 199 case entryTypeManifest: 200 f = ttles.onManifestExpire 201 default: 202 f = func(reference.Reference) error { 203 return fmt.Errorf("scheduler entry type") 204 } 205 } 206 207 ref, err := reference.Parse(entry.Key) 208 if err == nil { 209 if err := f(ref); err != nil { 210 dcontext.GetLogger(ttles.ctx).Errorf("Scheduler error returned from OnExpire(%s): %s", entry.Key, err) 211 } 212 } else { 213 dcontext.GetLogger(ttles.ctx).Errorf("Error unpacking reference: %s", err) 214 } 215 216 delete(ttles.entries, entry.Key) 217 ttles.indexDirty = true 218 }) 219 } 220 221 // Stop stops the scheduler. 222 func (ttles *TTLExpirationScheduler) Stop() { 223 ttles.Lock() 224 defer ttles.Unlock() 225 226 if err := ttles.writeState(); err != nil { 227 dcontext.GetLogger(ttles.ctx).Errorf("Error writing scheduler state: %s", err) 228 } 229 230 for _, entry := range ttles.entries { 231 entry.timer.Stop() 232 } 233 234 close(ttles.doneChan) 235 ttles.saveTimer.Stop() 236 ttles.stopped = true 237 } 238 239 func (ttles *TTLExpirationScheduler) writeState() error { 240 jsonBytes, err := json.Marshal(ttles.entries) 241 if err != nil { 242 return err 243 } 244 245 err = ttles.driver.PutContent(ttles.ctx, ttles.pathToStateFile, jsonBytes) 246 if err != nil { 247 return err 248 } 249 250 return nil 251 } 252 253 func (ttles *TTLExpirationScheduler) readState() error { 254 if _, err := ttles.driver.Stat(ttles.ctx, ttles.pathToStateFile); err != nil { 255 switch err := err.(type) { 256 case driver.PathNotFoundError: 257 return nil 258 default: 259 return err 260 } 261 } 262 263 bytes, err := ttles.driver.GetContent(ttles.ctx, ttles.pathToStateFile) 264 if err != nil { 265 return err 266 } 267 268 err = json.Unmarshal(bytes, &ttles.entries) 269 if err != nil { 270 return err 271 } 272 return nil 273 }