go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gce/appengine/backend/common.go (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package backend includes cron and task queue handlers. 16 package backend 17 18 import ( 19 "context" 20 "net/http" 21 "strings" 22 "time" 23 24 computealpha "google.golang.org/api/compute/v0.alpha" 25 compute "google.golang.org/api/compute/v1" 26 27 "go.chromium.org/luci/appengine/tq" 28 "go.chromium.org/luci/grpc/prpc" 29 "go.chromium.org/luci/server/auth" 30 "go.chromium.org/luci/server/router" 31 swarmingpb "go.chromium.org/luci/swarming/proto/api_v2" 32 33 "go.chromium.org/luci/gce/api/tasks/v1" 34 "go.chromium.org/luci/gce/appengine/model" 35 ) 36 37 // Operation is a wrapper type over operation results in alpha and stable GCP operations. 38 type Operation struct { 39 Stable *compute.Operation 40 Alpha *computealpha.Operation 41 } 42 43 // CommonOpError exposes just the subset of operation errors that are used 44 type CommonOpError struct { 45 Code string 46 Message string 47 } 48 49 // GetErrors gets the errors for a stable or alpha Operation. 50 func (o Operation) GetErrors() []CommonOpError { 51 switch { 52 case o.Stable != nil: 53 if o.Stable.Error == nil { 54 return nil 55 } 56 errs := make([]CommonOpError, 0, len(o.Stable.Error.Errors)) 57 for _, err := range o.Stable.Error.Errors { 58 errs = append(errs, CommonOpError{ 59 Code: err.Code, 60 Message: err.Message, 61 }) 62 } 63 return errs 64 case o.Alpha != nil: 65 if o.Alpha.Error == nil { 66 return nil 67 } 68 errs := make([]CommonOpError, 0, len(o.Alpha.Error.Errors)) 69 for _, err := range o.Alpha.Error.Errors { 70 errs = append(errs, CommonOpError{ 71 Code: err.Code, 72 Message: err.Message, 73 }) 74 } 75 return errs 76 } 77 return nil 78 } 79 80 // GetStatus gets the status for a stable or alpha operation. 81 func (o Operation) GetStatus() string { 82 switch { 83 case o.Stable != nil: 84 return o.Stable.Status 85 case o.Alpha != nil: 86 return o.Alpha.Status 87 } 88 return "" 89 } 90 91 // ComputeService is a wrapper over a stable or alpha compute service. 92 type ComputeService struct { 93 Stable *compute.Service 94 Alpha *computealpha.Service 95 } 96 97 // InsertInstance inserts a stable or beta compute instance, used to create instances that might use alpha features or might not. 98 func (c ComputeService) InsertInstance(ctx context.Context, project string, zone string, instance model.ComputeInstance, requestID string) (Operation, error) { 99 switch { 100 case instance.Stable != nil: 101 call := c.Stable.Instances.Insert(project, zone, instance.Stable) 102 stable, err := call.RequestId(requestID).Context(ctx).Do() 103 return Operation{Stable: stable}, err 104 default: 105 call := c.Alpha.Instances.Insert(project, zone, instance.Alpha) 106 alpha, err := call.RequestId(requestID).Context(ctx).Do() 107 return Operation{Alpha: alpha}, err 108 } 109 } 110 111 // dspKey is the key to a *tq.Dispatcher in the context. 112 var dspKey = "dsp" 113 114 // withDispatcher returns a new context with the given *tq.Dispatcher installed. 115 func withDispatcher(c context.Context, dsp *tq.Dispatcher) context.Context { 116 return context.WithValue(c, &dspKey, dsp) 117 } 118 119 // getDispatcher returns the *tq.Dispatcher installed in the current context. 120 func getDispatcher(c context.Context) *tq.Dispatcher { 121 return c.Value(&dspKey).(*tq.Dispatcher) 122 } 123 124 // registerTasks registers task handlers with the given *tq.Dispatcher. 125 func registerTasks(dsp *tq.Dispatcher) { 126 dsp.RegisterTask(&tasks.CountVMs{}, countVMs, countVMsQueue, nil) 127 dsp.RegisterTask(&tasks.CreateInstance{}, createInstance, createInstanceQueue, nil) 128 dsp.RegisterTask(&tasks.CreateVM{}, createVM, createVMQueue, nil) 129 dsp.RegisterTask(&tasks.DeleteBot{}, deleteBot, deleteBotQueue, nil) 130 dsp.RegisterTask(&tasks.DestroyInstance{}, destroyInstance, destroyInstanceQueue, nil) 131 dsp.RegisterTask(&tasks.ExpandConfig{}, expandConfig, expandConfigQueue, nil) 132 dsp.RegisterTask(&tasks.ManageBot{}, manageBot, manageBotQueue, nil) 133 dsp.RegisterTask(&tasks.ReportQuota{}, reportQuota, reportQuotaQueue, nil) 134 dsp.RegisterTask(&tasks.TerminateBot{}, terminateBot, terminateBotQueue, nil) 135 dsp.RegisterTask(&tasks.AuditProject{}, auditInstanceInZone, auditInstancesQueue, nil) 136 dsp.RegisterTask(&tasks.DrainVM{}, drainVMQueueHandler, drainVMQueue, nil) 137 dsp.RegisterTask(&tasks.InspectSwarming{}, inspectSwarming, inspectSwarmingQueue, nil) 138 dsp.RegisterTask(&tasks.DeleteStaleSwarmingBots{}, deleteStaleSwarmingBots, deleteStaleSwarmingBotsQueue, nil) 139 } 140 141 // gceKey is the key to a *compute.Service in the context. 142 var gceKey = "gce" 143 144 // withCompute returns a new context with the given *compute.Service installed. 145 func withCompute(c context.Context, gce ComputeService) context.Context { 146 return context.WithValue(c, &gceKey, gce) 147 } 148 149 // getCompute returns the ComputeService installed in the current context. 150 func getCompute(c context.Context) ComputeService { 151 return c.Value(&gceKey).(ComputeService) 152 } 153 154 // newCompute returns a new ComputeService. Panics on error. 155 func newCompute(c context.Context) ComputeService { 156 t, err := auth.GetRPCTransport(c, auth.AsSelf, auth.WithScopes(compute.ComputeScope)) 157 if err != nil { 158 panic(err) 159 } 160 stable, err := compute.New(&http.Client{Transport: t}) 161 if err != nil { 162 panic(err) 163 } 164 alpha, err := computealpha.New(&http.Client{Transport: t}) 165 if err != nil { 166 panic(err) 167 } 168 return ComputeService{ 169 Stable: stable, 170 Alpha: alpha, 171 } 172 } 173 174 // swrKey is the key to swarmingFactory in the context. 175 var swrKey = "swr" 176 177 // swarmingFactroy produces Swarming client connected to the given server. 178 type swarmingFactory func(c context.Context, server string) swarmingpb.BotsClient 179 180 // withSwarming returns a new context with the given swarming client factory. 181 func withSwarming(c context.Context, factory swarmingFactory) context.Context { 182 return context.WithValue(c, &swrKey, factory) 183 } 184 185 // getSwarming returns the swarming client connected to the given server. 186 // 187 // Uses the factory in the context to construct it. 188 func getSwarming(c context.Context, url string) swarmingpb.BotsClient { 189 return c.Value(&swrKey).(swarmingFactory)(c, url) 190 } 191 192 // newSwarming produces a Swarming client connected to the given server. 193 // 194 // Panics on errors. 195 func newSwarming(c context.Context, url string) swarmingpb.BotsClient { 196 t, err := auth.GetRPCTransport(c, auth.AsSelf) 197 if err != nil { 198 panic(err) 199 } 200 return swarmingpb.NewBotsClient( 201 &prpc.Client{ 202 C: &http.Client{Transport: t}, 203 Host: strings.TrimPrefix(url, "https://"), 204 Options: prpc.DefaultOptions(), 205 }, 206 ) 207 } 208 209 // InstallHandlers installs HTTP request handlers into the given router. 210 func InstallHandlers(r *router.Router, mw router.MiddlewareChain) { 211 dsp := &tq.Dispatcher{} 212 registerTasks(dsp) 213 mw = mw.Extend(func(c *router.Context, next router.Handler) { 214 ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) 215 defer cancel() 216 ctx = withDispatcher(ctx, dsp) 217 ctx = withCompute(ctx, newCompute(ctx)) 218 ctx = withSwarming(ctx, newSwarming) 219 c.Request = c.Request.WithContext(ctx) 220 next(c) 221 }) 222 dsp.InstallRoutes(r, mw) 223 r.GET("/internal/cron/count-tasks", mw, newHTTPHandler(countTasks)) 224 r.GET("/internal/cron/count-vms", mw, newHTTPHandler(countVMsAsync)) 225 r.GET("/internal/cron/create-instances", mw, newHTTPHandler(createInstancesAsync)) 226 r.GET("/internal/cron/expand-configs", mw, newHTTPHandler(expandConfigsAsync)) 227 r.GET("/internal/cron/manage-bots", mw, newHTTPHandler(manageBotsAsync)) 228 r.GET("/internal/cron/report-quota", mw, newHTTPHandler(reportQuotasAsync)) 229 r.GET("/internal/cron/audit-project", mw, newHTTPHandler(auditInstances)) 230 r.GET("/internal/cron/drain-vms", mw, newHTTPHandler(drainVMsAsync)) 231 r.GET("/internal/cron/inspect-swarming", mw, newHTTPHandler(inspectSwarmingAsync)) 232 }