golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/relui/sign/server.go (about) 1 // Copyright 2022 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 sign 6 7 import ( 8 "context" 9 "fmt" 10 "log" 11 "sync" 12 "time" 13 14 "github.com/google/uuid" 15 "golang.org/x/build/internal/access" 16 "golang.org/x/build/internal/relui/protos" 17 "golang.org/x/sync/errgroup" 18 "google.golang.org/grpc/codes" 19 "google.golang.org/grpc/status" 20 ) 21 22 var _ Service = (*SigningServer)(nil) 23 24 // SigningServer is a GRPC signing server used to send signing requests and 25 // signing status requests to a client. 26 type SigningServer struct { 27 protos.UnimplementedReleaseServiceServer 28 29 // requests is a channel of outgoing signing requests to be sent to 30 // any of the connected signing clients. 31 requests chan *protos.SigningRequest 32 33 // callback is a map of the message ID to callback association used to 34 // respond to previously created requests to a channel. 35 callbackMu sync.Mutex 36 callback map[string]func(*signResponse) // Key is message ID. 37 } 38 39 // NewServer creates a GRPC signing server used to send signing requests and 40 // signing status requests to a client. 41 func NewServer() *SigningServer { 42 return &SigningServer{ 43 requests: make(chan *protos.SigningRequest), 44 callback: make(map[string]func(*signResponse)), 45 } 46 } 47 48 // UpdateSigningStatus uses a bidirectional streaming connection to send signing requests to the client 49 // and receive status updates on signing requests. There is no specific order which the requests or responses 50 // need to occur in. The connection returns an error once the context is canceled or an error is encountered. 51 func (rs *SigningServer) UpdateSigningStatus(stream protos.ReleaseService_UpdateSigningStatusServer) error { 52 iap, err := access.IAPFromContext(stream.Context()) 53 if err != nil { 54 return status.Errorf(codes.Unauthenticated, "request does not contain the required authentication") 55 } 56 57 t := time.Now() 58 log.Printf("SigningServer: a client connected (iap = %+v)\n", iap) 59 g, ctx := errgroup.WithContext(stream.Context()) 60 g.Go(func() error { 61 for { 62 select { 63 case <-ctx.Done(): 64 return ctx.Err() 65 case req := <-rs.requests: 66 err := stream.Send(req) 67 if err != nil { 68 rs.callAndDeregister(req.GetMessageId(), &signResponse{err: err}) 69 return err 70 } 71 } 72 } 73 }) 74 g.Go(func() error { 75 for { 76 resp, err := stream.Recv() 77 if err != nil { 78 return err 79 } 80 rs.callAndDeregister(resp.GetMessageId(), &signResponse{status: resp}) 81 } 82 }) 83 err = g.Wait() 84 log.Printf("SigningServer: a client disconnected after %v (err = %v)\n", time.Since(t), err) 85 return err 86 } 87 88 // do sends a signing request and returns the corresponding signing response. 89 // It blocks until a response is received or the context times out or is canceled. 90 func (rs *SigningServer) do(ctx context.Context, req *protos.SigningRequest) (resp *protos.SigningStatus, err error) { 91 t := time.Now() 92 defer func() { 93 if err == nil { 94 log.Printf("SigningServer: successfully round-tripped message=%q in %v:\n req = %v\n resp = %v\n", req.GetMessageId(), time.Since(t), req, resp) 95 } else { 96 log.Printf("SigningServer: communication error %v for message=%q after %v:\n req = %v\n", err, req.GetMessageId(), time.Since(t), req) 97 } 98 }() 99 100 // Register where to send the response for this message ID. 101 respCh := make(chan *signResponse, 1) // Room for one response. 102 rs.register(req.GetMessageId(), func(r *signResponse) { respCh <- r }) 103 104 // Send the request. 105 select { 106 case rs.requests <- req: 107 case <-ctx.Done(): 108 rs.deregister(req.GetMessageId()) 109 return nil, ctx.Err() 110 } 111 112 // Wait for the response. 113 select { 114 case resp := <-respCh: 115 return resp.status, resp.err 116 case <-ctx.Done(): 117 rs.deregister(req.GetMessageId()) 118 return nil, ctx.Err() 119 } 120 } 121 122 // SignArtifact implements Service. 123 func (rs *SigningServer) SignArtifact(ctx context.Context, bt BuildType, objectURI []string) (jobID string, _ error) { 124 resp, err := rs.do(ctx, &protos.SigningRequest{ 125 MessageId: uuid.NewString(), 126 RequestOneof: &protos.SigningRequest_Sign{Sign: &protos.SignArtifactRequest{ 127 BuildType: bt.proto(), 128 GcsUri: objectURI, 129 }}, 130 }) 131 if err != nil { 132 return "", err 133 } 134 switch t := resp.StatusOneof.(type) { 135 case *protos.SigningStatus_Started: 136 return t.Started.JobId, nil 137 case *protos.SigningStatus_Failed: 138 return "", fmt.Errorf("failed to start %v signing on %q: %s", bt, objectURI, t.Failed.GetDescription()) 139 default: 140 return "", fmt.Errorf("unexpected response type %T for a sign request", t) 141 } 142 } 143 144 // ArtifactSigningStatus implements Service. 145 func (rs *SigningServer) ArtifactSigningStatus(ctx context.Context, jobID string) (_ Status, desc string, objectURI []string, _ error) { 146 resp, err := rs.do(ctx, &protos.SigningRequest{ 147 MessageId: uuid.NewString(), 148 RequestOneof: &protos.SigningRequest_Status{Status: &protos.SignArtifactStatusRequest{ 149 JobId: jobID, 150 }}, 151 }) 152 if err != nil { 153 return StatusUnknown, "", nil, err 154 } 155 switch t := resp.StatusOneof.(type) { 156 case *protos.SigningStatus_Completed: 157 return StatusCompleted, "", t.Completed.GetGcsUri(), nil 158 case *protos.SigningStatus_Failed: 159 return StatusFailed, t.Failed.GetDescription(), nil, nil 160 case *protos.SigningStatus_NotFound: 161 return StatusNotFound, fmt.Sprintf("signing job %q not found", jobID), nil, nil 162 case *protos.SigningStatus_Running: 163 return StatusRunning, t.Running.GetDescription(), nil, nil 164 default: 165 return 0, "", nil, fmt.Errorf("unexpected response type %T for a status request", t) 166 } 167 } 168 169 // CancelSigning implements Service. 170 func (rs *SigningServer) CancelSigning(ctx context.Context, jobID string) error { 171 _, err := rs.do(ctx, &protos.SigningRequest{ 172 MessageId: uuid.NewString(), 173 RequestOneof: &protos.SigningRequest_Cancel{Cancel: &protos.SignArtifactCancelRequest{ 174 JobId: jobID, 175 }}, 176 }) 177 return err 178 } 179 180 // signResponse contains the response and error from a signing request. 181 type signResponse struct { 182 status *protos.SigningStatus 183 err error 184 } 185 186 // register creates a message ID to channel association. 187 func (s *SigningServer) register(messageID string, f func(*signResponse)) { 188 s.callbackMu.Lock() 189 s.callback[messageID] = f 190 s.callbackMu.Unlock() 191 } 192 193 // deregister removes the channel to message ID association. 194 func (s *SigningServer) deregister(messageID string) { 195 s.callbackMu.Lock() 196 delete(s.callback, messageID) 197 s.callbackMu.Unlock() 198 } 199 200 // callAndDeregister calls the callback associated with the message ID. 201 // If no callback is registered for the message ID, the response is dropped. 202 // The callback registration is always removed if it exists. 203 func (s *SigningServer) callAndDeregister(messageID string, resp *signResponse) { 204 s.callbackMu.Lock() 205 defer s.callbackMu.Unlock() 206 207 respFunc, ok := s.callback[messageID] 208 if !ok { 209 // drop the message 210 log.Printf("SigningServer: caller not found for message=%q", messageID) 211 return 212 } 213 delete(s.callback, messageID) 214 respFunc(resp) 215 }