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  }