golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/buildgo/basepin.go (about) 1 // Copyright 2014 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 buildgo 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "log" 12 "path" 13 "sort" 14 "strings" 15 "time" 16 17 "golang.org/x/build/dashboard" 18 "google.golang.org/api/compute/v1" 19 ) 20 21 // diskPrefix is the name prefix of GCE disks used by MakeBasepinDisks. 22 const diskPrefix = "basepin-" 23 24 // MakeBasepinDisks looks at the list of all the project's VM images 25 // and creates (if needed) a disk for each one, named with the prefix 26 // "basepin-". The purpose of these "basepin" disks is to speed up GCE 27 // VM creations. When GCE creates a new disk for a new VM, it takes a 28 // fast path when creating the disk if there's another disk in the 29 // same zone for that image, and makes the new disk a thin 30 // Copy-on-Write shadow over the basepin disk. GCE also does this if 31 // there's been a VM created within the past N minutes of that type, 32 // but we want VMs to always create quickly. 33 func (c *Client) MakeBasepinDisks(ctx context.Context) error { 34 currentImage := map[string]bool{} 35 for _, hc := range dashboard.Hosts { 36 if hc.VMImage != "" { 37 currentImage[hc.VMImage] = true 38 } 39 } 40 41 // Try to find it by name. 42 svc := c.Compute() 43 imList, err := svc.Images.List(c.Env.ProjectName).Do() 44 if err != nil { 45 return fmt.Errorf("Error listing images for %s: %v", c.Env.ProjectName, err) 46 } 47 if imList.NextPageToken != "" { 48 return errors.New("too many images; pagination not supported") 49 } 50 51 getNeedMap := func() map[string]*compute.Image { 52 need := make(map[string]*compute.Image) // keys like "https://www.googleapis.com/compute/v1/projects/symbolic-datum-552/global/images/linux-buildlet-arm" 53 for _, im := range imList.Items { 54 imBase := path.Base(im.SelfLink) 55 if strings.Contains(im.SelfLink, "-debug") { 56 continue 57 } 58 if !currentImage[imBase] { 59 continue 60 } 61 need[im.SelfLink] = im 62 } 63 return need 64 } 65 66 for _, zone := range c.Env.VMZones { 67 diskList, err := svc.Disks.List(c.Env.ProjectName, zone).Filter(fmt.Sprintf(`name="%s*"`, diskPrefix)).Do() 68 if err != nil { 69 return fmt.Errorf("error listing disks in zone %v: %v", zone, err) 70 } 71 if diskList.NextPageToken != "" { 72 return fmt.Errorf("too many disks in %v; pagination not supported (yet?)", zone) 73 } 74 75 need := getNeedMap() 76 77 for _, d := range diskList.Items { 78 if si, ok := need[d.SourceImage]; ok && d.SourceImageId == fmt.Sprint(si.Id) { 79 if c.Verbose { 80 log.Printf("basepin: have %s: %s (%v)\n", d.Name, d.SourceImage, d.SourceImageId) 81 } 82 delete(need, d.SourceImage) 83 continue 84 } 85 if zone != c.Env.VMZones[0] { 86 log.Printf("basepin: deleting unnecessary disk %v in zone %v", d.Name, zone) 87 op, err := svc.Disks.Delete(c.Env.ProjectName, zone, d.Name).Do() 88 if err != nil { 89 log.Printf("basepin: failed to delete disk %v in zone %v: %v", d.Name, zone, err) 90 continue 91 } 92 if err := c.AwaitOp(ctx, op); err != nil { 93 log.Printf("basepin: failed to delete disk %v in zone %v: %v", d.Name, zone, err) 94 continue 95 } 96 } 97 } 98 99 var needed []string 100 for imageName := range need { 101 needed = append(needed, imageName) 102 } 103 sort.Strings(needed) 104 for _, n := range needed { 105 log.Printf("basepin: need %v disk in %v", n, zone) 106 } 107 for i, imName := range needed { 108 im := need[imName] 109 log.Printf("basepin: (%d/%d) creating %s ...", i+1, len(needed), im.Name) 110 op, err := svc.Disks.Insert(c.Env.ProjectName, zone, &compute.Disk{ 111 Description: "zone-cached basepin image of " + im.Name, 112 Name: diskPrefix + im.Name + "-" + fmt.Sprint(im.Id), 113 SizeGb: im.DiskSizeGb, 114 SourceImage: im.SelfLink, 115 SourceImageId: fmt.Sprint(im.Id), 116 Type: "https://www.googleapis.com/compute/v1/projects/" + c.Env.ProjectName + "/zones/" + zone + "/diskTypes/pd-ssd", 117 }).Do() 118 if err != nil { 119 return err 120 } 121 if err := c.AwaitOp(ctx, op); err != nil { 122 return fmt.Errorf("basepin: failed to create: %v", err) 123 } 124 } 125 log.Printf("basepin: created %d images in %v", len(needed), zone) 126 } 127 return nil 128 } 129 130 // AwaitOp waits for op to finish. It returns nil if the operating 131 // finished successfully. 132 func (c *Client) AwaitOp(ctx context.Context, op *compute.Operation) error { 133 // TODO: clean this up with respect to status updates & logging. 134 svc := c.Compute() 135 opName := op.Name 136 // TODO: move logging to Client c.logger. and add Client.WithLogger shallow copier. 137 log.Printf("Waiting on operation %v (in %q)", opName, op.Zone) 138 for { 139 time.Sleep(2 * time.Second) 140 op, err := svc.ZoneOperations.Get(c.Env.ProjectName, path.Base(op.Zone), opName).Do() 141 if err != nil { 142 return fmt.Errorf("Failed to get op %s: %v", opName, err) 143 } 144 switch op.Status { 145 case "PENDING", "RUNNING": 146 log.Printf("Waiting on operation %v", opName) 147 continue 148 case "DONE": 149 if op.Error != nil { 150 var last error 151 for _, operr := range op.Error.Errors { 152 log.Printf("Error: %+v", operr) 153 last = fmt.Errorf("%v", operr) 154 } 155 return last 156 } 157 return nil 158 default: 159 return fmt.Errorf("Unknown status %q: %+v", op.Status, op) 160 } 161 } 162 }