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  }