blob: ee92d608cd3cb55c5f3251a8ef1c020fc5e5317c [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Implements provision_service.proto (see proto for details)
package main
import (
"context"
"fmt"
"log"
"net"
"time"
"chromiumos/lro"
"chromiumos/test/provision/cmd/provisionserver/bootstrap/services"
"chromiumos/test/provision/cmd/provisionserver/bootstrap/services/ashservice"
"chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice"
"chromiumos/test/provision/cmd/provisionserver/bootstrap/services/lacrosservice"
"go.chromium.org/chromiumos/config/go/api/test/tls"
"go.chromium.org/chromiumos/config/go/longrunning"
"go.chromium.org/chromiumos/config/go/test/api"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// ProvisionServer implementation of provision_service.proto
type ProvisionServer struct {
Manager *lro.Manager
logger *log.Logger
dutName string
dutClient api.DutServiceClient
wiringConn *grpc.ClientConn
noReboot bool
}
// newProvisionServer creates a new provision service server to listen to rpc requests.
func newProvisionServer(l net.Listener, logger *log.Logger, dutName string, conn *grpc.ClientConn, wiringConn *grpc.ClientConn, noReboot bool) (*grpc.Server, error) {
s := &ProvisionServer{
Manager: lro.New(),
logger: logger,
dutName: dutName,
dutClient: api.NewDutServiceClient(conn),
wiringConn: wiringConn,
}
defer s.Manager.Close()
server := grpc.NewServer()
api.RegisterProvisionServiceServer(server, s)
longrunning.RegisterOperationsServer(server, s.Manager)
logger.Println("provisionservice listen to request at ", l.Addr().String())
return server, nil
}
// InstallCros installs a specified version of Chrome OS on the DUT, along
// with any specified DLCs.
//
// If the DUT already has the specified list of DLCs, only the missing DLCs
// will be provisioned.
func (s *ProvisionServer) InstallCros(ctx context.Context, req *api.InstallCrosRequest) (*longrunning.Operation, error) {
s.logger.Println("Received api.InstallCrosRequest: ", *req)
op := s.Manager.NewOperation()
cs := crosservice.NewCrOSService(s.dutName, s.dutClient, s.wiringConn, s.noReboot, req)
response := api.InstallCrosResponse{}
if s.provision(ctx, &cs, op) == nil {
response.Outcome = &api.InstallCrosResponse_Success{}
} else {
response.Outcome = &api.InstallCrosResponse_Failure{}
}
s.Manager.SetResult(op.Name, &response)
return op, nil
}
// InstallLacros installs a specified version of Lacros on the DUT.
func (s *ProvisionServer) InstallLacros(ctx context.Context, req *api.InstallLacrosRequest) (*longrunning.Operation, error) {
s.logger.Println("Received api.InstallLacrosRequest: ", *req)
op := s.Manager.NewOperation()
ls, err := lacrosservice.NewLaCrOSService(s.dutName, s.dutClient, s.wiringConn, req)
response := api.InstallLacrosResponse{}
if err != nil {
s.setNewOperationError(
op,
codes.Aborted,
fmt.Sprintf("pre-provision: failed setup: %s", err),
tls.ProvisionDutResponse_REASON_PROVISIONING_FAILED.String(),
)
} else {
if s.provision(ctx, &ls, op) == nil {
response.Outcome = &api.InstallLacrosResponse_Success{}
} else {
response.Outcome = &api.InstallLacrosResponse_Failure{
Failure: &api.InstallFailure{
Reason: api.InstallFailure_REASON_PROVISIONING_FAILED,
},
}
}
}
s.Manager.SetResult(op.Name, &response)
return op, nil
}
// InstallAsh installs a specified version of ash-chrome on the DUT.
//
// This directly overwrites the version of ash-chrome on the current root
// disk partition.
func (s *ProvisionServer) InstallAsh(ctx context.Context, req *api.InstallAshRequest) (*longrunning.Operation, error) {
s.logger.Println("Received api.InstallAshRequest: ", *req)
op := s.Manager.NewOperation()
cs := ashservice.NewAshService(s.dutName, s.dutClient, s.wiringConn, req)
response := api.InstallAshResponse{}
if s.provision(ctx, &cs, op) == nil {
response.Outcome = &api.InstallAshResponse_Success{}
} else {
response.Outcome = &api.InstallAshResponse_Failure{}
}
s.Manager.SetResult(op.Name, &response)
return op, nil
}
// InstallArc installs a specified version of ARC on the DUT.
//
// This directly overwrites the version of ARC on the current root
// disk partition.
func (s *ProvisionServer) InstallArc(ctx context.Context, req *api.InstallArcRequest) (*longrunning.Operation, error) {
s.logger.Println("Received api.InstallArcRequest: ", *req)
op := s.Manager.NewOperation()
s.Manager.SetResult(op.Name, &api.InstallArcResponse{})
return op, nil
}
// provision effectively acts as a state transition runner for each of the
// installation services, transitioning between states as required, and
// executing each state. Operation status is also set at this state in case of
// error.
func (s *ProvisionServer) provision(ctx context.Context, si services.ServiceInterface, operation *longrunning.Operation) error {
// Set a timeout for provisioning.
ctx, cancel := context.WithTimeout(ctx, time.Hour)
defer cancel()
select {
case <-ctx.Done():
s.setNewOperationError(
operation,
codes.DeadlineExceeded,
"provision: timed out before provisioning OS",
tls.ProvisionDutResponse_REASON_PROVISIONING_TIMEDOUT.String())
return fmt.Errorf("deadline failure")
default:
}
for cs := si.GetFirstState(); cs != nil; cs = cs.Next() {
if err := cs.Execute(ctx); err != nil {
s.setNewOperationError(
operation,
codes.Aborted,
fmt.Sprintf("provision: failed %s step: %s", cs.Name(), err),
tls.ProvisionDutResponse_REASON_PROVISIONING_FAILED.String(),
)
return fmt.Errorf("provision step %s failure: %w", cs.Name(), err)
}
}
return nil
}
// setNewOperationError is a simple helper to handle operation error propagation
func (s *ProvisionServer) setNewOperationError(op *longrunning.Operation, code codes.Code, msg, reason string) {
status := status.New(code, msg)
status, err := status.WithDetails(&errdetails.LocalizedMessage{
Message: reason,
})
if err != nil {
panic("Failed to set status details")
}
s.setError(op.Name, status)
}
// setError directly interacts with the Manager to set an error if appropriate
func (s *ProvisionServer) setError(opName string, opErr *status.Status) {
if err := s.Manager.SetError(opName, opErr); err != nil {
log.Printf("Failed to set Operation error, %s", err)
}
}