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 }