sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/spyglass/testgrid.go (about)

     1  /*
     2  Copyright 2019 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 spyglass
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	tgconf "github.com/GoogleCloudPlatform/testgrid/config"
    26  	tgconfpb "github.com/GoogleCloudPlatform/testgrid/pb/config"
    27  	"github.com/sirupsen/logrus"
    28  
    29  	"sigs.k8s.io/prow/pkg/config"
    30  	"sigs.k8s.io/prow/pkg/io"
    31  )
    32  
    33  // TestGrid manages a TestGrid configuration, and handles lookups of TestGrid configuration.
    34  type TestGrid struct {
    35  	mut    sync.RWMutex
    36  	c      *tgconfpb.Configuration
    37  	conf   config.Getter
    38  	ctx    context.Context
    39  	opener io.Opener
    40  }
    41  
    42  // Start synchronously requests the testgrid config, then continues to update it periodically.
    43  func (tg *TestGrid) Start() {
    44  	if err := tg.updateConfig(); err != nil {
    45  		logrus.WithError(err).Error("Couldn't fetch TestGrid config.")
    46  	}
    47  	go func() {
    48  		for range time.Tick(10 * time.Minute) {
    49  			if err := tg.updateConfig(); err != nil {
    50  				logrus.WithError(err).WithField("path", tg.conf().Deck.Spyglass.TestGridConfig).Error("Couldn't update TestGrid config.")
    51  			}
    52  		}
    53  	}()
    54  }
    55  
    56  // FindPath returns a 'query' for a job name, e.g. 'sig-testing-misc#bazel' for ci-test-infra-bazel.
    57  // This is based on the same logic Gubernator uses: find the dashboard with the fewest tabs, where
    58  // our tab doesn't have any BaseOptions.
    59  func (tg *TestGrid) FindQuery(jobName string) (string, error) {
    60  	if tg.config() == nil {
    61  		return "", fmt.Errorf("no testgrid config loaded")
    62  	}
    63  	bestOption := ""
    64  	bestScore := 0
    65  	for _, dashboard := range tg.config().Dashboards {
    66  		for _, tab := range dashboard.DashboardTab {
    67  			penalty := 0
    68  			if tab.BaseOptions != "" {
    69  				penalty = 1000
    70  			}
    71  			if tab.TestGroupName == jobName {
    72  				score := -len(dashboard.DashboardTab) + penalty
    73  				if bestOption == "" || score < bestScore {
    74  					bestScore = score
    75  					bestOption = dashboard.Name + "#" + tab.Name
    76  				}
    77  			}
    78  		}
    79  	}
    80  	if bestOption == "" {
    81  		return "", fmt.Errorf("couldn't find a testgrid tab for %q", jobName)
    82  	}
    83  	return bestOption, nil
    84  }
    85  
    86  // Ready returns true if a usable TestGrid config is loaded, otherwise false.
    87  func (tg *TestGrid) Ready() bool {
    88  	return tg.c != nil
    89  }
    90  
    91  func (tg *TestGrid) updateConfig() error {
    92  	if tg.conf().Deck.Spyglass.TestGridConfig == "" {
    93  		tg.setConfig(nil)
    94  		return nil
    95  	}
    96  	r, err := tg.opener.Reader(tg.ctx, tg.conf().Deck.Spyglass.TestGridConfig)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	c, err := tgconf.Unmarshal(r)
   101  	if err != nil {
   102  		return err
   103  	}
   104  	tg.setConfig(c)
   105  	return nil
   106  }
   107  
   108  func (tg *TestGrid) setConfig(c *tgconfpb.Configuration) {
   109  	tg.mut.Lock()
   110  	defer tg.mut.Unlock()
   111  	tg.c = c
   112  }
   113  
   114  func (tg *TestGrid) config() *tgconfpb.Configuration {
   115  	tg.mut.RLock()
   116  	defer tg.mut.RUnlock()
   117  	return tg.c
   118  }