github.com/GoogleCloudPlatform/testgrid@v0.0.174/cmd/updater/main.go (about)

     1  /*
     2  Copyright 2018 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 main
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"os"
    25  	"runtime"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/GoogleCloudPlatform/testgrid/pkg/pubsub"
    30  	"github.com/GoogleCloudPlatform/testgrid/pkg/updater"
    31  	"github.com/GoogleCloudPlatform/testgrid/pkg/updater/resultstore"
    32  	"github.com/GoogleCloudPlatform/testgrid/util"
    33  	"github.com/GoogleCloudPlatform/testgrid/util/gcs"
    34  	"github.com/GoogleCloudPlatform/testgrid/util/metrics/prometheus"
    35  	"github.com/sirupsen/logrus"
    36  	"google.golang.org/api/option"
    37  
    38  	gpubsub "cloud.google.com/go/pubsub"
    39  	configpb "github.com/GoogleCloudPlatform/testgrid/pb/config"
    40  )
    41  
    42  // options configures the updater
    43  type options struct {
    44  	config            gcs.Path // gs://path/to/config/proto
    45  	persistQueue      gcs.Path
    46  	creds             string
    47  	confirm           bool
    48  	groups            util.Strings
    49  	groupConcurrency  int
    50  	buildConcurrency  int
    51  	wait              time.Duration
    52  	groupTimeout      time.Duration
    53  	buildTimeout      time.Duration
    54  	gridPrefix        string
    55  	subscriptions     util.Strings
    56  	reprocessList     util.Strings
    57  	enableIgnoreSkip  bool
    58  	enableResultStore bool
    59  
    60  	debug    bool
    61  	trace    bool
    62  	jsonLogs bool
    63  }
    64  
    65  // validate ensures sane options
    66  func (o *options) validate() error {
    67  	if o.config.String() == "" {
    68  		return errors.New("empty --config")
    69  	}
    70  	if o.config.Bucket() == "k8s-testgrid" && o.gridPrefix == "" && o.confirm {
    71  		return fmt.Errorf("--config=%s: cannot write grid state to gs://k8s-testgrid", o.config)
    72  	}
    73  	if o.groupConcurrency == 0 {
    74  		o.groupConcurrency = runtime.NumCPU()
    75  	}
    76  	if o.buildConcurrency == 0 {
    77  		o.buildConcurrency = o.groupConcurrency * 4
    78  	}
    79  
    80  	return subscribeGCS(o.subscriptions.Strings()...)
    81  }
    82  
    83  func subscribeGCS(subs ...string) error {
    84  	for _, sub := range subs {
    85  		parts := strings.SplitN(sub, "=", 2)
    86  		if len(parts) != 2 {
    87  			return fmt.Errorf("--subscribe format is prefix=proj/sub, got %q", sub)
    88  		}
    89  		prefix := parts[0]
    90  		parts = strings.SplitN(parts[1], "/", 2)
    91  		if len(parts) != 2 {
    92  			return fmt.Errorf("--subscribe format is prefix=proj/sub, got %q", sub)
    93  		}
    94  		proj, sub := parts[0], parts[1]
    95  		updater.AddManualSubscription(proj, sub, prefix)
    96  	}
    97  	return nil
    98  }
    99  
   100  // gatherOptions reads options from flags
   101  func gatherFlagOptions(fs *flag.FlagSet, args ...string) options {
   102  	var o options
   103  	fs.Var(&o.config, "config", "gs://path/to/config.pb")
   104  	fs.Var(&o.persistQueue, "persist-queue", "Load previous queue state from gs://path/to/queue-state.json and regularly save to it thereafter")
   105  	fs.StringVar(&o.creds, "gcp-service-account", "", "/path/to/gcp/creds (use local creds if empty)")
   106  	fs.BoolVar(&o.confirm, "confirm", false, "Upload data if set")
   107  	fs.Var(&o.groups, "test-group", "Only update named groups if set (repeatable)")
   108  	fs.IntVar(&o.groupConcurrency, "group-concurrency", 0, "Manually define the number of groups to concurrently update if non-zero")
   109  	fs.IntVar(&o.buildConcurrency, "build-concurrency", 0, "Manually define the number of builds to concurrently read if non-zero")
   110  	fs.DurationVar(&o.wait, "wait", 0, "Ensure at least this much time has passed since the last loop (exit if zero).")
   111  	fs.Var(&o.subscriptions, "subscribe", "gcs-prefix=project-id/sub-id (repeatable)")
   112  	fs.Var(&o.reprocessList, "reprocess-group-on-change", "Limit reprocessing to specific groups if set (repeatable)")
   113  
   114  	fs.DurationVar(&o.groupTimeout, "group-timeout", 10*time.Minute, "Maximum time to wait for each group to update")
   115  	fs.DurationVar(&o.buildTimeout, "build-timeout", 3*time.Minute, "Maximum time to wait to read each build")
   116  	fs.StringVar(&o.gridPrefix, "grid-prefix", "grid", "Join this with the grid name to create the GCS suffix")
   117  
   118  	fs.BoolVar(&o.enableIgnoreSkip, "enable-ignore-skip", false, "If true, enable ignore_skip behavior.")
   119  	fs.BoolVar(&o.enableResultStore, "enable-resultstore", false, "If true, fetch results from ResultStore.")
   120  
   121  	fs.BoolVar(&o.debug, "debug", false, "Log debug lines if set")
   122  	fs.BoolVar(&o.trace, "trace", false, "Log trace and debug lines if set")
   123  	fs.BoolVar(&o.jsonLogs, "json-logs", false, "Uses a json logrus formatter when set")
   124  
   125  	fs.Parse(args)
   126  	return o
   127  }
   128  
   129  // gatherOptions reads options from flags
   130  func gatherOptions() options {
   131  	return gatherFlagOptions(flag.CommandLine, os.Args[1:]...)
   132  }
   133  
   134  type resultSource string
   135  
   136  const (
   137  	gcsSource         resultSource = "GCS"
   138  	resultStoreSource resultSource = "ResultStore"
   139  	unknownSource     resultSource = "unknown"
   140  )
   141  
   142  func source(tg *configpb.TestGroup) resultSource {
   143  	if tg.GetUseKubernetesClient() || tg.GetResultSource().GetGcsConfig() != nil {
   144  		return gcsSource
   145  	}
   146  	if tg.GetResultSource().GetResultstoreConfig() != nil {
   147  		return resultStoreSource
   148  	}
   149  	return unknownSource
   150  }
   151  
   152  func updateGroup(updateGCS, updateResultStore updater.GroupUpdater) updater.GroupUpdater {
   153  	return func(parent context.Context, log logrus.FieldLogger, client gcs.Client, tg *configpb.TestGroup, gridPath gcs.Path) (bool, error) {
   154  		source := source(tg)
   155  		switch source {
   156  		case gcsSource:
   157  			return updateGCS(parent, log, client, tg, gridPath)
   158  		case resultStoreSource:
   159  			return updateResultStore(parent, log, client, tg, gridPath)
   160  		default:
   161  			return false, errors.New("invalid result source (must be one of GCS, ResultStore)")
   162  		}
   163  	}
   164  }
   165  
   166  func main() {
   167  	opt := gatherOptions()
   168  	if err := opt.validate(); err != nil {
   169  		logrus.Fatalf("Invalid flags: %v", err)
   170  	}
   171  	if !opt.confirm {
   172  		logrus.Warning("--confirm=false (DRY-RUN): will not write to gcs")
   173  	}
   174  	switch {
   175  	case opt.trace:
   176  		logrus.SetLevel(logrus.TraceLevel)
   177  	case opt.debug:
   178  		logrus.SetLevel(logrus.DebugLevel)
   179  	}
   180  
   181  	if opt.jsonLogs {
   182  		logrus.SetFormatter(&logrus.JSONFormatter{})
   183  	}
   184  	logrus.SetReportCaller(true)
   185  
   186  	ctx, cancel := context.WithCancel(context.Background())
   187  	defer cancel()
   188  
   189  	storageClient, err := gcs.ClientWithCreds(ctx, opt.creds)
   190  	if err != nil {
   191  		logrus.WithError(err).Fatal("Failed to create storage client")
   192  	}
   193  	defer storageClient.Close()
   194  
   195  	client := gcs.NewClient(storageClient)
   196  
   197  	log := logrus.WithFields(logrus.Fields{
   198  		"group": opt.groupConcurrency,
   199  		"build": opt.buildConcurrency,
   200  	})
   201  	log.Info("Configured concurrency")
   202  
   203  	var rsClient *resultstore.DownloadClient
   204  	if opt.enableResultStore {
   205  		rsConn, err := resultstore.Connect(ctx, "")
   206  		if err != nil {
   207  			logrus.WithError(err).Fatal("Failed to connect to ResultStore.")
   208  		}
   209  		rsClient = resultstore.NewClient(rsConn)
   210  	}
   211  
   212  	updateGCS := updater.GCS(ctx, client, opt.groupTimeout, opt.buildTimeout, opt.buildConcurrency, opt.confirm, opt.enableIgnoreSkip)
   213  	updateResultStore := resultstore.Updater(rsClient, client, opt.groupTimeout, opt.confirm)
   214  	updateAll := updateGroup(updateGCS, updateResultStore)
   215  
   216  	mets := updater.CreateMetrics(prometheus.NewFactory())
   217  
   218  	pubsubClient, err := gpubsub.NewClient(ctx, "", option.WithCredentialsFile(opt.creds))
   219  	if err != nil {
   220  		logrus.WithError(err).Fatal("Failed to create pubsub client")
   221  	}
   222  
   223  	fixers := []updater.Fixer{
   224  		updater.FixGCS(pubsub.NewClient(pubsubClient)),
   225  	}
   226  
   227  	if path := opt.persistQueue; path.String() != "" {
   228  		const freq = time.Minute
   229  		ticker := time.NewTicker(freq)
   230  		log := logrus.WithField("frequency", freq)
   231  		fixers = append(fixers, updater.FixPersistent(log, client, path, ticker.C))
   232  	}
   233  
   234  	opts := &updater.UpdateOptions{
   235  		ConfigPath:       opt.config,
   236  		GridPrefix:       opt.gridPrefix,
   237  		GroupConcurrency: opt.groupConcurrency,
   238  		GroupNames:       opt.groups.Strings(),
   239  		Write:            opt.confirm,
   240  		Freq:             opt.wait,
   241  	}
   242  
   243  	if err := updater.Update(ctx, client, mets, updateAll, opts, fixers...); err != nil {
   244  		logrus.WithError(err).Error("Could not update")
   245  	}
   246  }