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