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  }