github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/tide/main.go (about)

     1  /*
     2  Copyright 2017 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  	"flag"
    22  	"net/http"
    23  	"os"
    24  	"os/signal"
    25  	"strconv"
    26  	"syscall"
    27  	"time"
    28  
    29  	"github.com/sirupsen/logrus"
    30  
    31  	"k8s.io/test-infra/pkg/flagutil"
    32  	"k8s.io/test-infra/prow/config"
    33  	"k8s.io/test-infra/prow/config/secret"
    34  	prowflagutil "k8s.io/test-infra/prow/flagutil"
    35  	"k8s.io/test-infra/prow/logrusutil"
    36  	"k8s.io/test-infra/prow/metrics"
    37  	"k8s.io/test-infra/prow/tide"
    38  )
    39  
    40  type options struct {
    41  	port int
    42  
    43  	configPath    string
    44  	jobConfigPath string
    45  
    46  	syncThrottle   int
    47  	statusThrottle int
    48  
    49  	dryRun     bool
    50  	runOnce    bool
    51  	kubernetes prowflagutil.KubernetesOptions
    52  	github     prowflagutil.GitHubOptions
    53  }
    54  
    55  func (o *options) Validate() error {
    56  	for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github} {
    57  		if err := group.Validate(o.dryRun); err != nil {
    58  			return err
    59  		}
    60  	}
    61  
    62  	return nil
    63  }
    64  
    65  func gatherOptions() options {
    66  	o := options{}
    67  	fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
    68  	fs.IntVar(&o.port, "port", 8888, "Port to listen on.")
    69  	fs.StringVar(&o.configPath, "config-path", "/etc/config/config.yaml", "Path to config.yaml.")
    70  	fs.StringVar(&o.jobConfigPath, "job-config-path", "", "Path to prow job configs.")
    71  	fs.BoolVar(&o.dryRun, "dry-run", true, "Whether to mutate any real-world state.")
    72  	fs.BoolVar(&o.runOnce, "run-once", false, "If true, run only once then quit.")
    73  	for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github} {
    74  		group.AddFlags(fs)
    75  	}
    76  	fs.IntVar(&o.syncThrottle, "sync-hourly-tokens", 800, "The maximum number of tokens per hour to be used by the sync controller.")
    77  	fs.IntVar(&o.statusThrottle, "status-hourly-tokens", 400, "The maximum number of tokens per hour to be used by the status controller.")
    78  
    79  	fs.Parse(os.Args[1:])
    80  	return o
    81  }
    82  
    83  func main() {
    84  	o := gatherOptions()
    85  	if err := o.Validate(); err != nil {
    86  		logrus.Fatalf("Invalid options: %v", err)
    87  	}
    88  
    89  	logrus.SetFormatter(
    90  		logrusutil.NewDefaultFieldsFormatter(nil, logrus.Fields{"component": "tide"}),
    91  	)
    92  
    93  	configAgent := &config.Agent{}
    94  	if err := configAgent.Start(o.configPath, o.jobConfigPath); err != nil {
    95  		logrus.WithError(err).Fatal("Error starting config agent.")
    96  	}
    97  
    98  	secretAgent := &secret.Agent{}
    99  	if err := secretAgent.Start([]string{o.github.TokenPath}); err != nil {
   100  		logrus.WithError(err).Fatal("Error starting secrets agent.")
   101  	}
   102  
   103  	githubSync, err := o.github.GitHubClient(secretAgent, o.dryRun)
   104  	if err != nil {
   105  		logrus.WithError(err).Fatal("Error getting GitHub client.")
   106  	}
   107  
   108  	githubStatus, err := o.github.GitHubClient(secretAgent, o.dryRun)
   109  	if err != nil {
   110  		logrus.WithError(err).Fatal("Error getting GitHub client.")
   111  	}
   112  
   113  	// The sync loop should be allowed more tokens than the status loop because
   114  	// it has to list all PRs in the pool every loop while the status loop only
   115  	// has to list changed PRs every loop.
   116  	// The sync loop should have a much lower burst allowance than the status
   117  	// loop which may need to update many statuses upon restarting Tide after
   118  	// changing the context format or starting Tide on a new repo.
   119  	githubSync.Throttle(o.syncThrottle, 3*tokensPerIteration(o.syncThrottle, configAgent.Config().Tide.SyncPeriod))
   120  	githubStatus.Throttle(o.statusThrottle, o.statusThrottle/2)
   121  
   122  	gitClient, err := o.github.GitClient(secretAgent, o.dryRun)
   123  	if err != nil {
   124  		logrus.WithError(err).Fatal("Error getting Git client.")
   125  	}
   126  	defer gitClient.Clean()
   127  
   128  	kubeClient, err := o.kubernetes.Client(configAgent.Config().ProwJobNamespace, o.dryRun)
   129  	if err != nil {
   130  		logrus.WithError(err).Fatal("Error getting Kubernetes client.")
   131  	}
   132  
   133  	c := tide.NewController(githubSync, githubStatus, kubeClient, configAgent, gitClient, nil)
   134  	defer c.Shutdown()
   135  	http.Handle("/", c)
   136  	http.Handle("/history", c.History)
   137  	server := &http.Server{Addr: ":" + strconv.Itoa(o.port)}
   138  
   139  	// Push metrics to the configured prometheus pushgateway endpoint.
   140  	pushGateway := configAgent.Config().PushGateway
   141  	if pushGateway.Endpoint != "" {
   142  		go metrics.PushMetrics("tide", pushGateway.Endpoint, pushGateway.Interval)
   143  	}
   144  
   145  	start := time.Now()
   146  	sync(c)
   147  	if o.runOnce {
   148  		return
   149  	}
   150  	go func() {
   151  		sig := make(chan os.Signal, 1)
   152  		signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
   153  		for {
   154  			select {
   155  			case <-time.After(time.Until(start.Add(configAgent.Config().Tide.SyncPeriod))):
   156  				start = time.Now()
   157  				sync(c)
   158  			case <-sig:
   159  				logrus.Info("Tide is shutting down...")
   160  				// Shutdown the http server with a 10s timeout then return to execute
   161  				// deferred c.Shutdown()
   162  				ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
   163  				defer cancel() // frees ctx resources
   164  				server.Shutdown(ctx)
   165  				return
   166  			}
   167  		}
   168  	}()
   169  	logrus.WithError(server.ListenAndServe()).Warn("Tide HTTP server stopped.")
   170  }
   171  
   172  func sync(c *tide.Controller) {
   173  	if err := c.Sync(); err != nil {
   174  		logrus.WithError(err).Error("Error syncing.")
   175  	}
   176  }
   177  
   178  func tokensPerIteration(hourlyTokens int, iterPeriod time.Duration) int {
   179  	tokenRate := float64(hourlyTokens) / float64(time.Hour)
   180  	return int(tokenRate * float64(iterPeriod))
   181  }