github.com/uber/kraken@v0.1.4/lib/persistedretry/writeback/executor.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 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 package writeback 15 16 import ( 17 "fmt" 18 "os" 19 "time" 20 21 "github.com/uber-go/tally" 22 "github.com/uber/kraken/lib/backend" 23 "github.com/uber/kraken/lib/persistedretry" 24 "github.com/uber/kraken/lib/store" 25 "github.com/uber/kraken/lib/store/metadata" 26 "github.com/uber/kraken/utils/log" 27 ) 28 29 // FileStore defines store operations required for write-back. 30 type FileStore interface { 31 DeleteCacheFileMetadata(name string, md metadata.Metadata) error 32 GetCacheFileReader(name string) (store.FileReader, error) 33 } 34 35 // Executor executes write back tasks. 36 type Executor struct { 37 stats tally.Scope 38 fs FileStore 39 backends *backend.Manager 40 } 41 42 // NewExecutor creates a new Executor. 43 func NewExecutor( 44 stats tally.Scope, 45 fs FileStore, 46 backends *backend.Manager) *Executor { 47 48 stats = stats.Tagged(map[string]string{ 49 "module": "writebackexecutor", 50 }) 51 52 return &Executor{stats, fs, backends} 53 } 54 55 // Name returns the executor name. 56 func (e *Executor) Name() string { 57 return "writeback" 58 } 59 60 // Exec uploads the cache file corresponding to r's digest to the remote backend 61 // that matches r's namespace. 62 func (e *Executor) Exec(r persistedretry.Task) error { 63 t := r.(*Task) 64 if err := e.upload(t); err != nil { 65 return err 66 } 67 err := e.fs.DeleteCacheFileMetadata(t.Name, &metadata.Persist{}) 68 if err != nil && !os.IsNotExist(err) { 69 return fmt.Errorf("delete persist metadata: %s", err) 70 } 71 return nil 72 } 73 74 func (e *Executor) upload(t *Task) error { 75 start := time.Now() 76 77 client, err := e.backends.GetClient(t.Namespace) 78 if err != nil { 79 if err == backend.ErrNamespaceNotFound { 80 log.With( 81 "namespace", t.Namespace, 82 "name", t.Name).Info("Dropping writeback for unconfigured namespace") 83 return nil 84 } 85 return fmt.Errorf("get client: %s", err) 86 } 87 88 if _, err := client.Stat(t.Namespace, t.Name); err == nil { 89 // File already uploaded, no-op. 90 return nil 91 } 92 93 f, err := e.fs.GetCacheFileReader(t.Name) 94 if err != nil { 95 if os.IsNotExist(err) { 96 // Nothing we can do about this but make noise and drop the task. 97 e.stats.Counter("missing_files").Inc(1) 98 log.With("name", t.Name).Error("Invariant violation: writeback cache file missing") 99 return nil 100 } 101 return fmt.Errorf("get file: %s", err) 102 } 103 defer f.Close() 104 105 if err := client.Upload(t.Namespace, t.Name, f); err != nil { 106 return fmt.Errorf("upload: %s", err) 107 } 108 109 // We don't want to time noops nor errors. 110 e.stats.Timer("upload").Record(time.Since(start)) 111 e.stats.Timer("lifetime").Record(time.Since(t.CreatedAt)) 112 113 return nil 114 }