github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/gerrit/client/syncer.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package client implements client that interacts with gerrit instances 18 package client 19 20 import ( 21 "context" 22 "encoding/json" 23 "fmt" 24 stdio "io" 25 "sync" 26 "time" 27 28 "github.com/sirupsen/logrus" 29 "sigs.k8s.io/prow/pkg/config" 30 "sigs.k8s.io/prow/pkg/io" 31 ) 32 33 // opener has methods to read and write paths 34 type opener interface { 35 Reader(ctx context.Context, path string) (io.ReadCloser, error) 36 Writer(ctx context.Context, path string, opts ...io.WriterOptions) (io.WriteCloser, error) 37 } 38 39 type SyncTime struct { 40 val LastSyncState 41 lock sync.RWMutex 42 path string 43 opener opener 44 ctx context.Context 45 } 46 47 func NewSyncTime(path string, opener opener, ctx context.Context) *SyncTime { 48 return &SyncTime{ 49 path: path, 50 opener: opener, 51 ctx: ctx, 52 } 53 } 54 55 func (st *SyncTime) Init(hostProjects map[string]map[string]*config.GerritQueryFilter) error { 56 st.lock.RLock() 57 zero := st.val == nil 58 st.lock.RUnlock() 59 if !zero { 60 return nil 61 } 62 return st.update(hostProjects) 63 } 64 65 func (st *SyncTime) update(hostProjects map[string]map[string]*config.GerritQueryFilter) error { 66 timeNow := time.Now() 67 st.lock.Lock() 68 defer st.lock.Unlock() 69 state, err := st.currentState() 70 if err != nil { 71 return err 72 } 73 if state != nil { 74 // Initialize new hosts, projects 75 for host, projects := range hostProjects { 76 if _, ok := state[host]; !ok { 77 state[host] = map[string]time.Time{} 78 } 79 for project := range projects { 80 if _, ok := state[host][project]; !ok { 81 state[host][project] = timeNow 82 } 83 } 84 } 85 st.val = state 86 logrus.WithField("lastSync", st.val).Infoln("Initialized successfully from lastSyncFallback.") 87 } else { 88 targetState := LastSyncState{} 89 for host, projects := range hostProjects { 90 targetState[host] = map[string]time.Time{} 91 for project := range projects { 92 targetState[host][project] = timeNow 93 } 94 } 95 st.val = targetState 96 } 97 return nil 98 } 99 100 func (st *SyncTime) currentState() (LastSyncState, error) { 101 r, err := st.opener.Reader(st.ctx, st.path) 102 if io.IsNotExist(err) { 103 logrus.Warnf("lastSyncFallback not found at %q", st.path) 104 return nil, nil 105 } else if err != nil { 106 return nil, fmt.Errorf("open: %w", err) 107 } 108 defer io.LogClose(r) 109 buf, err := stdio.ReadAll(r) 110 if err != nil { 111 return nil, fmt.Errorf("read: %w", err) 112 } 113 var state LastSyncState 114 if err := json.Unmarshal(buf, &state); err != nil { 115 // Don't error on unmarshall error, let it default 116 logrus.WithField("lastSync", st.val).Warnln("Failed to unmarshal lastSyncFallback, resetting all last update times to current.") 117 return nil, nil 118 } 119 return state, nil 120 } 121 122 func (st *SyncTime) Current() LastSyncState { 123 st.lock.RLock() 124 defer st.lock.RUnlock() 125 return st.val 126 } 127 128 func (st *SyncTime) Update(newState LastSyncState) error { 129 st.lock.Lock() 130 defer st.lock.Unlock() 131 132 targetState := st.val.DeepCopy() 133 134 var changed bool 135 for host, newLastSyncs := range newState { 136 if _, ok := targetState[host]; !ok { 137 targetState[host] = map[string]time.Time{} 138 } 139 for project, newLastSync := range newLastSyncs { 140 currentLastSync, ok := targetState[host][project] 141 if !ok || currentLastSync.Before(newLastSync) { 142 targetState[host][project] = newLastSync 143 changed = true 144 } 145 } 146 } 147 148 if !changed { 149 return nil 150 } 151 152 // TODO: consider writing to disk/storage in a separate thread so that processing is not blocked on writing the checkpoint file and to reduce the number of writes. 153 // Flushing this on termination would be important to avoid reprocessing events we've already handled. 154 w, err := st.opener.Writer(st.ctx, st.path) 155 if err != nil { 156 return fmt.Errorf("open for write %q: %w", st.path, err) 157 } 158 stateBytes, err := json.Marshal(targetState) 159 if err != nil { 160 return fmt.Errorf("marshall state: %w", err) 161 } 162 if _, err := fmt.Fprint(w, string(stateBytes)); err != nil { 163 io.LogClose(w) 164 return fmt.Errorf("write %q: %w", st.path, err) 165 } 166 if err := w.Close(); err != nil { 167 return fmt.Errorf("close %q: %w", st.path, err) 168 } 169 st.val = targetState 170 return nil 171 }