github.com/uber/kraken@v0.1.4/lib/persistedretry/tagreplication/store.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 tagreplication 15 16 import ( 17 "errors" 18 "fmt" 19 "time" 20 21 "github.com/jmoiron/sqlx" 22 "github.com/mattn/go-sqlite3" 23 24 "github.com/uber/kraken/lib/persistedretry" 25 ) 26 27 // Store stores tags to be replicated asynchronously. 28 type Store struct { 29 db *sqlx.DB 30 } 31 32 // NewStore creates a new Store. 33 func NewStore(db *sqlx.DB, rv RemoteValidator) (*Store, error) { 34 s := &Store{db} 35 if err := s.deleteInvalidTasks(rv); err != nil { 36 return nil, fmt.Errorf("delete invalid tasks: %s", err) 37 } 38 return s, nil 39 } 40 41 // GetPending returns all pending tasks. 42 func (s *Store) GetPending() ([]persistedretry.Task, error) { 43 return s.selectStatus("pending") 44 } 45 46 // GetFailed returns all failed tasks. 47 func (s *Store) GetFailed() ([]persistedretry.Task, error) { 48 return s.selectStatus("failed") 49 } 50 51 // AddPending adds r as pending. 52 func (s *Store) AddPending(r persistedretry.Task) error { 53 return s.addWithStatus(r, "pending") 54 } 55 56 // AddFailed adds r as failed. 57 func (s *Store) AddFailed(r persistedretry.Task) error { 58 return s.addWithStatus(r, "failed") 59 } 60 61 // MarkPending marks r as pending. 62 func (s *Store) MarkPending(r persistedretry.Task) error { 63 res, err := s.db.NamedExec(` 64 UPDATE replicate_tag_task 65 SET status = "pending" 66 WHERE tag=:tag AND destination=:destination 67 `, r.(*Task)) 68 if err != nil { 69 return err 70 } 71 if n, err := res.RowsAffected(); err != nil { 72 panic("driver does not support RowsAffected") 73 } else if n == 0 { 74 return persistedretry.ErrTaskNotFound 75 } 76 return nil 77 } 78 79 // MarkFailed marks r as failed. 80 func (s *Store) MarkFailed(r persistedretry.Task) error { 81 t := r.(*Task) 82 res, err := s.db.NamedExec(` 83 UPDATE replicate_tag_task 84 SET last_attempt = CURRENT_TIMESTAMP, 85 failures = failures + 1, 86 status = "failed" 87 WHERE tag=:tag AND destination=:destination 88 `, t) 89 if err != nil { 90 return err 91 } 92 if n, err := res.RowsAffected(); err != nil { 93 panic("driver does not support RowsAffected") 94 } else if n == 0 { 95 return persistedretry.ErrTaskNotFound 96 } 97 t.Failures++ 98 t.LastAttempt = time.Now() 99 return nil 100 } 101 102 // Remove removes r. 103 func (s *Store) Remove(r persistedretry.Task) error { 104 return s.delete(r) 105 } 106 107 // Find is not supported. 108 func (s *Store) Find(query interface{}) ([]persistedretry.Task, error) { 109 return nil, errors.New("not supported") 110 } 111 112 func (s *Store) addWithStatus(r persistedretry.Task, status string) error { 113 query := fmt.Sprintf(` 114 INSERT INTO replicate_tag_task ( 115 tag, 116 digest, 117 dependencies, 118 destination, 119 last_attempt, 120 failures, 121 delay, 122 status 123 ) VALUES ( 124 :tag, 125 :digest, 126 :dependencies, 127 :destination, 128 :last_attempt, 129 :failures, 130 :delay, 131 %q 132 ) 133 `, status) 134 _, err := s.db.NamedExec(query, r.(*Task)) 135 if se, ok := err.(sqlite3.Error); ok { 136 if se.ExtendedCode == sqlite3.ErrConstraintPrimaryKey { 137 return persistedretry.ErrTaskExists 138 } 139 } 140 return err 141 } 142 143 func (s *Store) selectStatus(status string) ([]persistedretry.Task, error) { 144 var tasks []*Task 145 err := s.db.Select(&tasks, ` 146 SELECT tag, digest, dependencies, destination, created_at, last_attempt, failures, delay 147 FROM replicate_tag_task 148 WHERE status=?`, status) 149 if err != nil { 150 return nil, err 151 } 152 var result []persistedretry.Task 153 for _, t := range tasks { 154 result = append(result, t) 155 } 156 return result, nil 157 } 158 159 // deleteInvalidTasks deletes replication tasks whose destinations are no longer 160 // valid remotes. 161 func (s *Store) deleteInvalidTasks(rv RemoteValidator) error { 162 tasks := []*Task{} 163 if err := s.db.Select(&tasks, `SELECT tag, destination FROM replicate_tag_task`); err != nil { 164 return fmt.Errorf("select all tasks: %s", err) 165 } 166 for _, t := range tasks { 167 if rv.Valid(t.Tag, t.Destination) { 168 continue 169 } 170 if err := s.delete(t); err != nil { 171 return fmt.Errorf("delete: %s", err) 172 } 173 } 174 return nil 175 } 176 177 func (s *Store) delete(r persistedretry.Task) error { 178 _, err := s.db.NamedExec(` 179 DELETE FROM replicate_tag_task 180 WHERE tag=:tag AND destination=:destination`, r.(*Task)) 181 return err 182 }