golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/gomote/create.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "context" 9 "encoding/json" 10 "flag" 11 "fmt" 12 "io" 13 "log" 14 "net/http" 15 "os" 16 "path/filepath" 17 "sort" 18 "strings" 19 "sync" 20 "time" 21 22 "golang.org/x/build/internal/gomote/protos" 23 "golang.org/x/sync/errgroup" 24 ) 25 26 type builderType struct { 27 Name string 28 IsReverse bool 29 ExpectNum int 30 } 31 32 func builders() (bt []builderType) { 33 type builderInfo struct { 34 HostType string 35 } 36 type hostInfo struct { 37 IsReverse bool 38 ExpectNum int 39 ContainerImage string 40 VMImage string 41 } 42 // resj is the response JSON from the builders. 43 var resj struct { 44 Builders map[string]builderInfo 45 Hosts map[string]hostInfo 46 } 47 res, err := http.Get("https://farmer.golang.org/builders?mode=json") 48 if err != nil { 49 log.Fatal(err) 50 } 51 defer res.Body.Close() 52 if res.StatusCode != 200 { 53 log.Fatalf("fetching builder types: %s", res.Status) 54 } 55 if err := json.NewDecoder(res.Body).Decode(&resj); err != nil { 56 log.Fatalf("decoding builder types: %v", err) 57 } 58 for b, bi := range resj.Builders { 59 if strings.HasPrefix(b, "misc-compile") { 60 continue 61 } 62 hi, ok := resj.Hosts[bi.HostType] 63 if !ok { 64 continue 65 } 66 if !hi.IsReverse && hi.ContainerImage == "" && hi.VMImage == "" { 67 continue 68 } 69 bt = append(bt, builderType{ 70 Name: b, 71 IsReverse: hi.IsReverse, 72 ExpectNum: hi.ExpectNum, 73 }) 74 } 75 sort.Slice(bt, func(i, j int) bool { 76 return bt[i].Name < bt[j].Name 77 }) 78 return 79 } 80 81 func swarmingBuilders() ([]string, error) { 82 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) 83 client := gomoteServerClient(ctx) 84 resp, err := client.ListSwarmingBuilders(ctx, &protos.ListSwarmingBuildersRequest{}) 85 if err != nil { 86 return nil, fmt.Errorf("unable to retrieve swarming builders: %s", err) 87 } 88 return resp.Builders, nil 89 } 90 91 func create(args []string) error { 92 fs := flag.NewFlagSet("create", flag.ContinueOnError) 93 94 fs.Usage = func() { 95 fmt.Fprintln(os.Stderr, "create usage: gomote create [create-opts] <type>") 96 fmt.Fprintln(os.Stderr) 97 fmt.Fprintln(os.Stderr, "If there's a valid group specified, new instances are") 98 fmt.Fprintln(os.Stderr, "automatically added to the group. If the group in") 99 fmt.Fprintln(os.Stderr, "$GOMOTE_GROUP doesn't exist, and there's no other group") 100 fmt.Fprintln(os.Stderr, "specified, it will be created and new instances will be") 101 fmt.Fprintln(os.Stderr, "added to that group.") 102 fs.PrintDefaults() 103 fmt.Fprintln(os.Stderr, "\nValid types:") 104 if luciDisabled() { 105 for _, bt := range builders() { 106 var warn string 107 if bt.IsReverse { 108 if bt.ExpectNum > 0 { 109 warn = fmt.Sprintf(" [limited capacity: %d machines]", bt.ExpectNum) 110 } else { 111 warn = " [limited capacity]" 112 } 113 } 114 fmt.Fprintf(os.Stderr, " * %s%s\n", bt.Name, warn) 115 } 116 os.Exit(1) 117 } else { 118 swarmingBuilders, err := swarmingBuilders() 119 if err != nil { 120 fmt.Fprintf(os.Stderr, " %s\n", err) 121 } else { 122 for _, builder := range swarmingBuilders { 123 fmt.Fprintf(os.Stderr, " * %s\n", builder) 124 } 125 } 126 os.Exit(1) 127 } 128 } 129 var status bool 130 fs.BoolVar(&status, "status", true, "print regular status updates while waiting") 131 var count int 132 fs.IntVar(&count, "count", 1, "number of instances to create") 133 var setup bool 134 fs.BoolVar(&setup, "setup", false, "set up the instance by pushing GOROOT and building the Go toolchain") 135 var newGroup string 136 fs.StringVar(&newGroup, "new-group", "", "also create a new group and add the new instances to it") 137 var useGolangbuild bool 138 fs.BoolVar(&useGolangbuild, "use-golangbuild", true, "disable the installation of build dependencies installed by golangbuild") 139 140 fs.Parse(args) 141 if fs.NArg() != 1 { 142 fs.Usage() 143 } 144 builderType := fs.Arg(0) 145 146 var groupMu sync.Mutex 147 group := activeGroup 148 var err error 149 if newGroup != "" { 150 group, err = doCreateGroup(newGroup) 151 if err != nil { 152 return err 153 } 154 } 155 if group == nil && os.Getenv("GOMOTE_GROUP") != "" { 156 group, err = doCreateGroup(os.Getenv("GOMOTE_GROUP")) 157 if err != nil { 158 return err 159 } 160 } 161 162 var tmpOutDir string 163 var tmpOutDirOnce sync.Once 164 eg, ctx := errgroup.WithContext(context.Background()) 165 client := gomoteServerClient(ctx) 166 for i := 0; i < count; i++ { 167 i := i 168 eg.Go(func() error { 169 start := time.Now() 170 var exp []string 171 if !useGolangbuild { 172 exp = append(exp, "disable-golang-build") 173 } 174 stream, err := client.CreateInstance(ctx, &protos.CreateInstanceRequest{BuilderType: builderType, ExperimentOption: exp}) 175 if err != nil { 176 return fmt.Errorf("failed to create buildlet: %w", err) 177 } 178 var inst string 179 updateLoop: 180 for { 181 update, err := stream.Recv() 182 switch { 183 case err == io.EOF: 184 break updateLoop 185 case err != nil: 186 return fmt.Errorf("failed to create buildlet (%d): %w", i+1, err) 187 case update.GetStatus() != protos.CreateInstanceResponse_COMPLETE && status: 188 fmt.Fprintf(os.Stderr, "# still creating %s (%d) after %v; %d requests ahead of you\n", builderType, i+1, time.Since(start).Round(time.Second), update.GetWaitersAhead()) 189 case update.GetStatus() == protos.CreateInstanceResponse_COMPLETE: 190 inst = update.GetInstance().GetGomoteId() 191 } 192 } 193 fmt.Println(inst) 194 if group != nil { 195 groupMu.Lock() 196 group.Instances = append(group.Instances, inst) 197 groupMu.Unlock() 198 } 199 if !setup { 200 return nil 201 } 202 203 // -setup is set, so push GOROOT and run make.bash. 204 205 tmpOutDirOnce.Do(func() { 206 tmpOutDir, err = os.MkdirTemp("", "gomote") 207 }) 208 if err != nil { 209 return fmt.Errorf("failed to create a temporary directory for setup output: %w", err) 210 } 211 212 // Push GOROOT. 213 detailedProgress := count == 1 214 goroot, err := getGOROOT() 215 if err != nil { 216 return err 217 } 218 if !detailedProgress { 219 fmt.Fprintf(os.Stderr, "# Pushing GOROOT %q to %q...\n", goroot, inst) 220 } 221 if err := doPush(ctx, inst, goroot, false, detailedProgress); err != nil { 222 return err 223 } 224 225 // Run make.bash or make.bat. 226 cmd := "go/src/make.bash" 227 if strings.Contains(builderType, "windows") { 228 cmd = "go/src/make.bat" 229 } 230 231 // Create a file to write output to so it doesn't get lost. 232 outf, err := os.Create(filepath.Join(tmpOutDir, fmt.Sprintf("%s.stdout", inst))) 233 if err != nil { 234 return err 235 } 236 defer func() { 237 outf.Close() 238 fmt.Fprintf(os.Stderr, "# Wrote results from %q to %q.\n", inst, outf.Name()) 239 }() 240 fmt.Fprintf(os.Stderr, "# Streaming results from %q to %q...\n", inst, outf.Name()) 241 242 // If this is the only command running, print to stdout too, for convenience and 243 // backwards compatibility. 244 outputs := []io.Writer{outf} 245 if detailedProgress { 246 outputs = append(outputs, os.Stdout) 247 } else { 248 fmt.Fprintf(os.Stderr, "# Running %q on %q...\n", cmd, inst) 249 } 250 return doRun(ctx, inst, cmd, []string{}, runWriters(outputs...)) 251 }) 252 } 253 if err := eg.Wait(); err != nil { 254 return err 255 } 256 if group != nil { 257 return storeGroup(group) 258 } 259 return nil 260 }