github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/crier/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  	"errors"
    21  	"flag"
    22  	"os"
    23  	"os/signal"
    24  	"sync"
    25  	"syscall"
    26  	"time"
    27  
    28  	"github.com/sirupsen/logrus"
    29  	"golang.org/x/time/rate"
    30  	"k8s.io/client-go/util/workqueue"
    31  
    32  	prowjobinformer "k8s.io/test-infra/prow/client/informers/externalversions"
    33  	"k8s.io/test-infra/prow/config"
    34  	"k8s.io/test-infra/prow/config/secret"
    35  	"k8s.io/test-infra/prow/crier"
    36  	prowflagutil "k8s.io/test-infra/prow/flagutil"
    37  	gerritclient "k8s.io/test-infra/prow/gerrit/client"
    38  	gerritreporter "k8s.io/test-infra/prow/gerrit/reporter"
    39  	githubreporter "k8s.io/test-infra/prow/github/reporter"
    40  	"k8s.io/test-infra/prow/logrusutil"
    41  	pubsubreporter "k8s.io/test-infra/prow/pubsub/reporter"
    42  )
    43  
    44  const (
    45  	resync = 0 * time.Minute
    46  )
    47  
    48  type options struct {
    49  	client         prowflagutil.KubernetesClientOptions
    50  	cookiefilePath string
    51  	gerritProjects gerritclient.ProjectsFlag
    52  	github         prowflagutil.GitHubOptions
    53  
    54  	// TODO(krzyzacy): drop config agent!
    55  	configPath    string
    56  	jobConfigPath string
    57  
    58  	gerritWorkers int
    59  	pubsubWorkers int
    60  	githubWorkers int
    61  
    62  	dryrun bool
    63  }
    64  
    65  func (o *options) validate() error {
    66  	if o.gerritWorkers > 1 {
    67  		// TODO(krzyzacy): try to see how to handle racy better for gerrit aggregate report.
    68  		logrus.Warn("gerrit reporter only supports one worker")
    69  		o.gerritWorkers = 1
    70  	}
    71  
    72  	if o.gerritWorkers+o.pubsubWorkers+o.githubWorkers <= 0 {
    73  		return errors.New("crier need to have at least one report worker to start")
    74  	}
    75  
    76  	if o.gerritWorkers > 0 {
    77  		if len(o.gerritProjects) == 0 {
    78  			return errors.New("--gerrit-projects must be set")
    79  		}
    80  
    81  		if o.cookiefilePath == "" {
    82  			logrus.Info("--cookiefile is not set, using anonymous authentication")
    83  		}
    84  	}
    85  
    86  	if o.githubWorkers > 0 {
    87  		if err := o.github.Validate(o.dryrun); err != nil {
    88  			return err
    89  		}
    90  	}
    91  
    92  	return nil
    93  }
    94  
    95  func (o *options) parseArgs(fs *flag.FlagSet, args []string) error {
    96  	fs.StringVar(&o.cookiefilePath, "cookiefile", "", "Path to git http.cookiefile, leave empty for anonymous")
    97  	fs.Var(&o.gerritProjects, "gerrit-projects", "Set of gerrit repos to monitor on a host example: --gerrit-host=https://android.googlesource.com=platform/build,toolchain/llvm, repeat flag for each host")
    98  	fs.IntVar(&o.gerritWorkers, "gerrit-workers", 0, "Number of gerrit report workers (0 means disabled)")
    99  	fs.IntVar(&o.pubsubWorkers, "pubsub-workers", 0, "Number of pubsub report workers (0 means disabled)")
   100  	fs.IntVar(&o.githubWorkers, "github-workers", 0, "Number of github report workers (0 means disabled)")
   101  
   102  	fs.StringVar(&o.configPath, "config-path", "", "Path to config.yaml.")
   103  	fs.StringVar(&o.jobConfigPath, "job-config-path", "", "Path to prow job configs.")
   104  
   105  	// TODO(krzyzacy): implement dryrun for gerrit/pubsub
   106  	fs.BoolVar(&o.dryrun, "dry-run", false, "Run in dry-run mode, not doing actual report (effective for github only)")
   107  
   108  	o.github.AddFlags(fs)
   109  	o.client.AddFlags(fs)
   110  
   111  	fs.Parse(args)
   112  
   113  	return o.validate()
   114  }
   115  
   116  func parseOptions() options {
   117  	o := options{
   118  		gerritProjects: gerritclient.ProjectsFlag{},
   119  	}
   120  
   121  	if err := o.parseArgs(flag.CommandLine, os.Args[1:]); err != nil {
   122  		logrus.WithError(err).Fatal("Invalid flag options")
   123  	}
   124  
   125  	return o
   126  }
   127  
   128  func main() {
   129  	o := parseOptions()
   130  
   131  	logrus.SetFormatter(
   132  		logrusutil.NewDefaultFieldsFormatter(nil, logrus.Fields{"component": "crier"}),
   133  	)
   134  
   135  	prowjobClient, err := o.client.ProwJobClient()
   136  	if err != nil {
   137  		logrus.WithError(err).Fatal("unable to create prow job client")
   138  	}
   139  
   140  	prowjobInformerFactory := prowjobinformer.NewSharedInformerFactory(prowjobClient, resync)
   141  
   142  	queue := workqueue.NewRateLimitingQueue(
   143  		workqueue.NewMaxOfRateLimiter(
   144  			workqueue.NewItemExponentialFailureRateLimiter(1*time.Second, 60*time.Second),
   145  			// 10 qps, 100 bucket size.  This is only for retry speed and its only the overall factor (not per item)
   146  			&workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
   147  		))
   148  
   149  	var controllers []*crier.Controller
   150  
   151  	// track all worker status before shutdown
   152  	wg := &sync.WaitGroup{}
   153  
   154  	if o.gerritWorkers > 0 {
   155  		informer := prowjobInformerFactory.Prow().V1().ProwJobs()
   156  		gerritReporter, err := gerritreporter.NewReporter(o.cookiefilePath, o.gerritProjects, informer.Lister())
   157  		if err != nil {
   158  			logrus.WithError(err).Fatal("Error starting gerrit reporter")
   159  		}
   160  
   161  		controllers = append(
   162  			controllers,
   163  			crier.NewController(
   164  				prowjobClient,
   165  				queue,
   166  				informer,
   167  				gerritReporter,
   168  				o.gerritWorkers,
   169  				wg))
   170  	}
   171  
   172  	if o.pubsubWorkers > 0 {
   173  		controllers = append(
   174  			controllers,
   175  			crier.NewController(
   176  				prowjobClient,
   177  				queue,
   178  				prowjobInformerFactory.Prow().V1().ProwJobs(),
   179  				pubsubreporter.NewReporter(),
   180  				o.pubsubWorkers,
   181  				wg))
   182  	}
   183  
   184  	if o.githubWorkers > 0 {
   185  		secretAgent := &secret.Agent{}
   186  		if o.github.TokenPath != "" {
   187  			if err := secretAgent.Start([]string{o.github.TokenPath}); err != nil {
   188  				logrus.WithError(err).Fatal("Error starting secrets agent")
   189  			}
   190  		}
   191  
   192  		githubClient, err := o.github.GitHubClient(secretAgent, o.dryrun)
   193  		if err != nil {
   194  			logrus.WithError(err).Fatal("Error getting GitHub client.")
   195  		}
   196  
   197  		configAgent := &config.Agent{}
   198  		if err := configAgent.Start(o.configPath, o.jobConfigPath); err != nil {
   199  			logrus.WithError(err).Fatal("Error starting config agent.")
   200  		}
   201  
   202  		controllers = append(
   203  			controllers,
   204  			crier.NewController(
   205  				prowjobClient,
   206  				queue,
   207  				prowjobInformerFactory.Prow().V1().ProwJobs(),
   208  				githubreporter.NewReporter(githubClient, configAgent),
   209  				o.githubWorkers,
   210  				wg))
   211  	}
   212  
   213  	if len(controllers) == 0 {
   214  		logrus.Fatalf("should have at least one controller to start crier.")
   215  	}
   216  
   217  	stopCh := make(chan struct{})
   218  	defer close(stopCh)
   219  
   220  	// run the controller loop to process items
   221  	prowjobInformerFactory.Start(stopCh)
   222  	for _, controller := range controllers {
   223  		go controller.Run(stopCh)
   224  	}
   225  
   226  	sigTerm := make(chan os.Signal, 1)
   227  	signal.Notify(sigTerm, syscall.SIGTERM)
   228  	signal.Notify(sigTerm, syscall.SIGINT)
   229  
   230  	<-sigTerm
   231  	logrus.Info("Crier received a termination signal and is shutting down...")
   232  	for range controllers {
   233  		stopCh <- struct{}{}
   234  	}
   235  
   236  	// waiting for all crier worker to finish
   237  	c := make(chan struct{})
   238  	go func() {
   239  		defer close(c)
   240  		wg.Wait()
   241  	}()
   242  	select {
   243  	case <-c:
   244  		logrus.Info("All worker finished, exiting crier")
   245  	case <-time.After(10 * time.Second):
   246  		logrus.Info("timed out waiting for all worker to finish")
   247  	}
   248  }