diff --git a/api/submitqueue/gateway/proto/gateway.proto b/api/submitqueue/gateway/proto/gateway.proto index 244f7b43..eb153901 100644 --- a/api/submitqueue/gateway/proto/gateway.proto +++ b/api/submitqueue/gateway/proto/gateway.proto @@ -82,22 +82,46 @@ message CancelRequest { message CancelResponse { } -// StatusRequest defines a request to look up the current status of a previously submitted request. -message StatusRequest { - // Globally unique identifier for the request, as returned by Land in the LandResponse. +// RequestSummary is the gateway-owned materialized current view of one received request. +message RequestSummary { + // Globally unique request identifier. This is request_id in internal storage. string sqid = 1; + // Queue supplied when the request was received. + string queue = 2; + // Change URIs supplied when the request was received, in caller order. + repeated string change_uris = 3; + // Receipt time in Unix milliseconds. + int64 received_at_ms = 4; + // Current customer-friendly status of the request. + string status = 5; + // Last error associated with the current status. Empty when absent. + string last_error = 6; + // Display and debugging metadata associated with the current status. + map metadata = 7; +} + +// StatusByIDRequest selects one request by the sqid returned from Land. +message StatusByIDRequest { + // Globally unique identifier for the request. + string sqid = 1; +} + +// StatusByIDResponse contains the current materialized request view. +message StatusByIDResponse { + // Matching request. + RequestSummary request = 1; +} + +// StatusByChangeIDRequest selects requests by an exact pinned change URI. +message StatusByChangeIDRequest { + // Exact change URI supplied in a Land request. + string change_id = 1; } -// StatusResponse defines the response to a status request. -// The status is eventually consistent with the request store; it might take some time to converge, typically no more than a few seconds. -message StatusResponse { - // Current customer-friendly status of the request (e.g. "accepted", "validating", "landed", "error"). - // Status is a free-form string because the system may introduce new statuses without breaking existing clients. - string status = 1; - // Last error message associated with the current status. Empty string if there is no error. - string last_error = 2; - // Free-form key-value metadata associated with the current status, for display or debugging purposes. Empty if none. - map metadata = 3; +// StatusByChangeIDResponse contains matching requests newest first. +message StatusByChangeIDResponse { + // Matching requests ordered by receipt time descending, then sqid descending. + repeated RequestSummary requests = 1; } // *************** @@ -124,6 +148,8 @@ message RequestNotFoundError { Error error = 1; // The sqid that was not found. string sqid = 2; + // Exact change ID that was not found. Populated only for change-ID lookup. + string change_id = 3; } // *************** @@ -152,7 +178,10 @@ service SubmitQueueGateway { // error) must be checked through the separate status / request-log API. rpc Cancel(CancelRequest) returns (CancelResponse) {} - // Status returns the current status of a previously submitted request, identified by its sqid. - // The status is eventually consistent with the request store and reconciled from the append-only request log. - rpc Status(StatusRequest) returns (StatusResponse) {} + // StatusByID returns the current materialized status of one request. + rpc StatusByID(StatusByIDRequest) returns (StatusByIDResponse) {} + + // StatusByChangeID returns current materialized statuses for an exact pinned change URI. + rpc StatusByChangeID(StatusByChangeIDRequest) returns (StatusByChangeIDResponse) {} + } diff --git a/api/submitqueue/gateway/protopb/gateway.pb.go b/api/submitqueue/gateway/protopb/gateway.pb.go index 20c020f3..2b231da9 100644 --- a/api/submitqueue/gateway/protopb/gateway.pb.go +++ b/api/submitqueue/gateway/protopb/gateway.pb.go @@ -367,29 +367,41 @@ func (*CancelResponse) Descriptor() ([]byte, []int) { return file_gateway_proto_rawDescGZIP(), []int{5} } -// StatusRequest defines a request to look up the current status of a previously submitted request. -type StatusRequest struct { +// RequestSummary is the gateway-owned materialized current view of one received request. +type RequestSummary struct { state protoimpl.MessageState `protogen:"open.v1"` - // Globally unique identifier for the request, as returned by Land in the LandResponse. - Sqid string `protobuf:"bytes,1,opt,name=sqid,proto3" json:"sqid,omitempty"` + // Globally unique request identifier. This is request_id in internal storage. + Sqid string `protobuf:"bytes,1,opt,name=sqid,proto3" json:"sqid,omitempty"` + // Queue supplied when the request was received. + Queue string `protobuf:"bytes,2,opt,name=queue,proto3" json:"queue,omitempty"` + // Change URIs supplied when the request was received, in caller order. + ChangeUris []string `protobuf:"bytes,3,rep,name=change_uris,json=changeUris,proto3" json:"change_uris,omitempty"` + // Receipt time in Unix milliseconds. + ReceivedAtMs int64 `protobuf:"varint,4,opt,name=received_at_ms,json=receivedAtMs,proto3" json:"received_at_ms,omitempty"` + // Current customer-friendly status of the request. + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + // Last error associated with the current status. Empty when absent. + LastError string `protobuf:"bytes,6,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` + // Display and debugging metadata associated with the current status. + Metadata map[string]string `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *StatusRequest) Reset() { - *x = StatusRequest{} +func (x *RequestSummary) Reset() { + *x = RequestSummary{} mi := &file_gateway_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *StatusRequest) String() string { +func (x *RequestSummary) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StatusRequest) ProtoMessage() {} +func (*RequestSummary) ProtoMessage() {} -func (x *StatusRequest) ProtoReflect() protoreflect.Message { +func (x *RequestSummary) ProtoReflect() protoreflect.Message { mi := &file_gateway_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -401,47 +413,83 @@ func (x *StatusRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StatusRequest.ProtoReflect.Descriptor instead. -func (*StatusRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use RequestSummary.ProtoReflect.Descriptor instead. +func (*RequestSummary) Descriptor() ([]byte, []int) { return file_gateway_proto_rawDescGZIP(), []int{6} } -func (x *StatusRequest) GetSqid() string { +func (x *RequestSummary) GetSqid() string { if x != nil { return x.Sqid } return "" } -// StatusResponse defines the response to a status request. -// The status is eventually consistent with the request store; it might take some time to converge, typically no more than a few seconds. -type StatusResponse struct { +func (x *RequestSummary) GetQueue() string { + if x != nil { + return x.Queue + } + return "" +} + +func (x *RequestSummary) GetChangeUris() []string { + if x != nil { + return x.ChangeUris + } + return nil +} + +func (x *RequestSummary) GetReceivedAtMs() int64 { + if x != nil { + return x.ReceivedAtMs + } + return 0 +} + +func (x *RequestSummary) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *RequestSummary) GetLastError() string { + if x != nil { + return x.LastError + } + return "" +} + +func (x *RequestSummary) GetMetadata() map[string]string { + if x != nil { + return x.Metadata + } + return nil +} + +// StatusByIDRequest selects one request by the sqid returned from Land. +type StatusByIDRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Current customer-friendly status of the request (e.g. "accepted", "validating", "landed", "error"). - // Status is a free-form string because the system may introduce new statuses without breaking existing clients. - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - // Last error message associated with the current status. Empty string if there is no error. - LastError string `protobuf:"bytes,2,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` - // Free-form key-value metadata associated with the current status, for display or debugging purposes. Empty if none. - Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Globally unique identifier for the request. + Sqid string `protobuf:"bytes,1,opt,name=sqid,proto3" json:"sqid,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *StatusResponse) Reset() { - *x = StatusResponse{} +func (x *StatusByIDRequest) Reset() { + *x = StatusByIDRequest{} mi := &file_gateway_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *StatusResponse) String() string { +func (x *StatusByIDRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StatusResponse) ProtoMessage() {} +func (*StatusByIDRequest) ProtoMessage() {} -func (x *StatusResponse) ProtoReflect() protoreflect.Message { +func (x *StatusByIDRequest) ProtoReflect() protoreflect.Message { mi := &file_gateway_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -453,28 +501,152 @@ func (x *StatusResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StatusResponse.ProtoReflect.Descriptor instead. -func (*StatusResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use StatusByIDRequest.ProtoReflect.Descriptor instead. +func (*StatusByIDRequest) Descriptor() ([]byte, []int) { return file_gateway_proto_rawDescGZIP(), []int{7} } -func (x *StatusResponse) GetStatus() string { +func (x *StatusByIDRequest) GetSqid() string { if x != nil { - return x.Status + return x.Sqid } return "" } -func (x *StatusResponse) GetLastError() string { +// StatusByIDResponse contains the current materialized request view. +type StatusByIDResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Matching request. + Request *RequestSummary `protobuf:"bytes,1,opt,name=request,proto3" json:"request,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StatusByIDResponse) Reset() { + *x = StatusByIDResponse{} + mi := &file_gateway_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StatusByIDResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusByIDResponse) ProtoMessage() {} + +func (x *StatusByIDResponse) ProtoReflect() protoreflect.Message { + mi := &file_gateway_proto_msgTypes[8] if x != nil { - return x.LastError + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusByIDResponse.ProtoReflect.Descriptor instead. +func (*StatusByIDResponse) Descriptor() ([]byte, []int) { + return file_gateway_proto_rawDescGZIP(), []int{8} +} + +func (x *StatusByIDResponse) GetRequest() *RequestSummary { + if x != nil { + return x.Request + } + return nil +} + +// StatusByChangeIDRequest selects requests by an exact pinned change URI. +type StatusByChangeIDRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Exact change URI supplied in a Land request. + ChangeId string `protobuf:"bytes,1,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StatusByChangeIDRequest) Reset() { + *x = StatusByChangeIDRequest{} + mi := &file_gateway_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StatusByChangeIDRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusByChangeIDRequest) ProtoMessage() {} + +func (x *StatusByChangeIDRequest) ProtoReflect() protoreflect.Message { + mi := &file_gateway_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusByChangeIDRequest.ProtoReflect.Descriptor instead. +func (*StatusByChangeIDRequest) Descriptor() ([]byte, []int) { + return file_gateway_proto_rawDescGZIP(), []int{9} +} + +func (x *StatusByChangeIDRequest) GetChangeId() string { + if x != nil { + return x.ChangeId } return "" } -func (x *StatusResponse) GetMetadata() map[string]string { +// StatusByChangeIDResponse contains matching requests newest first. +type StatusByChangeIDResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Matching requests ordered by receipt time descending, then sqid descending. + Requests []*RequestSummary `protobuf:"bytes,1,rep,name=requests,proto3" json:"requests,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StatusByChangeIDResponse) Reset() { + *x = StatusByChangeIDResponse{} + mi := &file_gateway_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StatusByChangeIDResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusByChangeIDResponse) ProtoMessage() {} + +func (x *StatusByChangeIDResponse) ProtoReflect() protoreflect.Message { + mi := &file_gateway_proto_msgTypes[10] if x != nil { - return x.Metadata + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusByChangeIDResponse.ProtoReflect.Descriptor instead. +func (*StatusByChangeIDResponse) Descriptor() ([]byte, []int) { + return file_gateway_proto_rawDescGZIP(), []int{10} +} + +func (x *StatusByChangeIDResponse) GetRequests() []*RequestSummary { + if x != nil { + return x.Requests } return nil } @@ -490,7 +662,7 @@ type Error struct { func (x *Error) Reset() { *x = Error{} - mi := &file_gateway_proto_msgTypes[8] + mi := &file_gateway_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -502,7 +674,7 @@ func (x *Error) String() string { func (*Error) ProtoMessage() {} func (x *Error) ProtoReflect() protoreflect.Message { - mi := &file_gateway_proto_msgTypes[8] + mi := &file_gateway_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -515,7 +687,7 @@ func (x *Error) ProtoReflect() protoreflect.Message { // Deprecated: Use Error.ProtoReflect.Descriptor instead. func (*Error) Descriptor() ([]byte, []int) { - return file_gateway_proto_rawDescGZIP(), []int{8} + return file_gateway_proto_rawDescGZIP(), []int{11} } func (x *Error) GetMessage() string { @@ -538,7 +710,7 @@ type UnrecognizedQueueError struct { func (x *UnrecognizedQueueError) Reset() { *x = UnrecognizedQueueError{} - mi := &file_gateway_proto_msgTypes[9] + mi := &file_gateway_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -550,7 +722,7 @@ func (x *UnrecognizedQueueError) String() string { func (*UnrecognizedQueueError) ProtoMessage() {} func (x *UnrecognizedQueueError) ProtoReflect() protoreflect.Message { - mi := &file_gateway_proto_msgTypes[9] + mi := &file_gateway_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -563,7 +735,7 @@ func (x *UnrecognizedQueueError) ProtoReflect() protoreflect.Message { // Deprecated: Use UnrecognizedQueueError.ProtoReflect.Descriptor instead. func (*UnrecognizedQueueError) Descriptor() ([]byte, []int) { - return file_gateway_proto_rawDescGZIP(), []int{9} + return file_gateway_proto_rawDescGZIP(), []int{12} } func (x *UnrecognizedQueueError) GetError() *Error { @@ -586,14 +758,16 @@ type RequestNotFoundError struct { // Free text error message describing the error. Error *Error `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` // The sqid that was not found. - Sqid string `protobuf:"bytes,2,opt,name=sqid,proto3" json:"sqid,omitempty"` + Sqid string `protobuf:"bytes,2,opt,name=sqid,proto3" json:"sqid,omitempty"` + // Exact change ID that was not found. Populated only for change-ID lookup. + ChangeId string `protobuf:"bytes,3,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RequestNotFoundError) Reset() { *x = RequestNotFoundError{} - mi := &file_gateway_proto_msgTypes[10] + mi := &file_gateway_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -605,7 +779,7 @@ func (x *RequestNotFoundError) String() string { func (*RequestNotFoundError) ProtoMessage() {} func (x *RequestNotFoundError) ProtoReflect() protoreflect.Message { - mi := &file_gateway_proto_msgTypes[10] + mi := &file_gateway_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -618,7 +792,7 @@ func (x *RequestNotFoundError) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestNotFoundError.ProtoReflect.Descriptor instead. func (*RequestNotFoundError) Descriptor() ([]byte, []int) { - return file_gateway_proto_rawDescGZIP(), []int{10} + return file_gateway_proto_rawDescGZIP(), []int{13} } func (x *RequestNotFoundError) GetError() *Error { @@ -635,6 +809,13 @@ func (x *RequestNotFoundError) GetSqid() string { return "" } +func (x *RequestNotFoundError) GetChangeId() string { + if x != nil { + return x.ChangeId + } + return "" +} + var File_gateway_proto protoreflect.FileDescriptor const file_gateway_proto_rawDesc = "" + @@ -656,30 +837,44 @@ const file_gateway_proto_rawDesc = "" + "\rCancelRequest\x12\x12\n" + "\x04sqid\x18\x01 \x01(\tR\x04sqid\x12\x16\n" + "\x06reason\x18\x02 \x01(\tR\x06reason\"\x10\n" + - "\x0eCancelResponse\"#\n" + - "\rStatusRequest\x12\x12\n" + - "\x04sqid\x18\x01 \x01(\tR\x04sqid\"\xd8\x01\n" + - "\x0eStatusResponse\x12\x16\n" + - "\x06status\x18\x01 \x01(\tR\x06status\x12\x1d\n" + + "\x0eCancelResponse\"\xc9\x02\n" + + "\x0eRequestSummary\x12\x12\n" + + "\x04sqid\x18\x01 \x01(\tR\x04sqid\x12\x14\n" + + "\x05queue\x18\x02 \x01(\tR\x05queue\x12\x1f\n" + + "\vchange_uris\x18\x03 \x03(\tR\n" + + "changeUris\x12$\n" + + "\x0ereceived_at_ms\x18\x04 \x01(\x03R\freceivedAtMs\x12\x16\n" + + "\x06status\x18\x05 \x01(\tR\x06status\x12\x1d\n" + "\n" + - "last_error\x18\x02 \x01(\tR\tlastError\x12R\n" + - "\bmetadata\x18\x03 \x03(\v26.uber.submitqueue.gateway.StatusResponse.MetadataEntryR\bmetadata\x1a;\n" + + "last_error\x18\x06 \x01(\tR\tlastError\x12R\n" + + "\bmetadata\x18\a \x03(\v26.uber.submitqueue.gateway.RequestSummary.MetadataEntryR\bmetadata\x1a;\n" + "\rMetadataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"!\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"'\n" + + "\x11StatusByIDRequest\x12\x12\n" + + "\x04sqid\x18\x01 \x01(\tR\x04sqid\"X\n" + + "\x12StatusByIDResponse\x12B\n" + + "\arequest\x18\x01 \x01(\v2(.uber.submitqueue.gateway.RequestSummaryR\arequest\"6\n" + + "\x17StatusByChangeIDRequest\x12\x1b\n" + + "\tchange_id\x18\x01 \x01(\tR\bchangeId\"`\n" + + "\x18StatusByChangeIDResponse\x12D\n" + + "\brequests\x18\x01 \x03(\v2(.uber.submitqueue.gateway.RequestSummaryR\brequests\"!\n" + "\x05Error\x12\x18\n" + "\amessage\x18\x01 \x01(\tR\amessage\"e\n" + "\x16UnrecognizedQueueError\x125\n" + "\x05error\x18\x01 \x01(\v2\x1f.uber.submitqueue.gateway.ErrorR\x05error\x12\x14\n" + - "\x05queue\x18\x02 \x01(\tR\x05queue\"a\n" + + "\x05queue\x18\x02 \x01(\tR\x05queue\"~\n" + "\x14RequestNotFoundError\x125\n" + "\x05error\x18\x01 \x01(\v2\x1f.uber.submitqueue.gateway.ErrorR\x05error\x12\x12\n" + - "\x04sqid\x18\x02 \x01(\tR\x04sqid2\x84\x03\n" + + "\x04sqid\x18\x02 \x01(\tR\x04sqid\x12\x1b\n" + + "\tchange_id\x18\x03 \x01(\tR\bchangeId2\x8d\x04\n" + "\x12SubmitQueueGateway\x12W\n" + "\x04Ping\x12%.uber.submitqueue.gateway.PingRequest\x1a&.uber.submitqueue.gateway.PingResponse\"\x00\x12W\n" + "\x04Land\x12%.uber.submitqueue.gateway.LandRequest\x1a&.uber.submitqueue.gateway.LandResponse\"\x00\x12]\n" + - "\x06Cancel\x12'.uber.submitqueue.gateway.CancelRequest\x1a(.uber.submitqueue.gateway.CancelResponse\"\x00\x12]\n" + - "\x06Status\x12'.uber.submitqueue.gateway.StatusRequest\x1a(.uber.submitqueue.gateway.StatusResponse\"\x00Bk\n" + + "\x06Cancel\x12'.uber.submitqueue.gateway.CancelRequest\x1a(.uber.submitqueue.gateway.CancelResponse\"\x00\x12i\n" + + "\n" + + "StatusByID\x12+.uber.submitqueue.gateway.StatusByIDRequest\x1a,.uber.submitqueue.gateway.StatusByIDResponse\"\x00\x12{\n" + + "\x10StatusByChangeID\x121.uber.submitqueue.gateway.StatusByChangeIDRequest\x1a2.uber.submitqueue.gateway.StatusByChangeIDResponse\"\x00Bk\n" + "\x1ccom.uber.submitqueue.gatewayB\fGatewayProtoP\x01Z;github.com/uber/submitqueue/api/submitqueue/gateway/protopbb\x06proto3" var ( @@ -694,42 +889,49 @@ func file_gateway_proto_rawDescGZIP() []byte { return file_gateway_proto_rawDescData } -var file_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_gateway_proto_goTypes = []any{ - (*PingRequest)(nil), // 0: uber.submitqueue.gateway.PingRequest - (*PingResponse)(nil), // 1: uber.submitqueue.gateway.PingResponse - (*LandRequest)(nil), // 2: uber.submitqueue.gateway.LandRequest - (*LandResponse)(nil), // 3: uber.submitqueue.gateway.LandResponse - (*CancelRequest)(nil), // 4: uber.submitqueue.gateway.CancelRequest - (*CancelResponse)(nil), // 5: uber.submitqueue.gateway.CancelResponse - (*StatusRequest)(nil), // 6: uber.submitqueue.gateway.StatusRequest - (*StatusResponse)(nil), // 7: uber.submitqueue.gateway.StatusResponse - (*Error)(nil), // 8: uber.submitqueue.gateway.Error - (*UnrecognizedQueueError)(nil), // 9: uber.submitqueue.gateway.UnrecognizedQueueError - (*RequestNotFoundError)(nil), // 10: uber.submitqueue.gateway.RequestNotFoundError - nil, // 11: uber.submitqueue.gateway.StatusResponse.MetadataEntry - (*protopb.Change)(nil), // 12: uber.base.change.Change - (protopb1.Strategy)(0), // 13: uber.base.mergestrategy.Strategy + (*PingRequest)(nil), // 0: uber.submitqueue.gateway.PingRequest + (*PingResponse)(nil), // 1: uber.submitqueue.gateway.PingResponse + (*LandRequest)(nil), // 2: uber.submitqueue.gateway.LandRequest + (*LandResponse)(nil), // 3: uber.submitqueue.gateway.LandResponse + (*CancelRequest)(nil), // 4: uber.submitqueue.gateway.CancelRequest + (*CancelResponse)(nil), // 5: uber.submitqueue.gateway.CancelResponse + (*RequestSummary)(nil), // 6: uber.submitqueue.gateway.RequestSummary + (*StatusByIDRequest)(nil), // 7: uber.submitqueue.gateway.StatusByIDRequest + (*StatusByIDResponse)(nil), // 8: uber.submitqueue.gateway.StatusByIDResponse + (*StatusByChangeIDRequest)(nil), // 9: uber.submitqueue.gateway.StatusByChangeIDRequest + (*StatusByChangeIDResponse)(nil), // 10: uber.submitqueue.gateway.StatusByChangeIDResponse + (*Error)(nil), // 11: uber.submitqueue.gateway.Error + (*UnrecognizedQueueError)(nil), // 12: uber.submitqueue.gateway.UnrecognizedQueueError + (*RequestNotFoundError)(nil), // 13: uber.submitqueue.gateway.RequestNotFoundError + nil, // 14: uber.submitqueue.gateway.RequestSummary.MetadataEntry + (*protopb.Change)(nil), // 15: uber.base.change.Change + (protopb1.Strategy)(0), // 16: uber.base.mergestrategy.Strategy } var file_gateway_proto_depIdxs = []int32{ - 12, // 0: uber.submitqueue.gateway.LandRequest.change:type_name -> uber.base.change.Change - 13, // 1: uber.submitqueue.gateway.LandRequest.strategy:type_name -> uber.base.mergestrategy.Strategy - 11, // 2: uber.submitqueue.gateway.StatusResponse.metadata:type_name -> uber.submitqueue.gateway.StatusResponse.MetadataEntry - 8, // 3: uber.submitqueue.gateway.UnrecognizedQueueError.error:type_name -> uber.submitqueue.gateway.Error - 8, // 4: uber.submitqueue.gateway.RequestNotFoundError.error:type_name -> uber.submitqueue.gateway.Error - 0, // 5: uber.submitqueue.gateway.SubmitQueueGateway.Ping:input_type -> uber.submitqueue.gateway.PingRequest - 2, // 6: uber.submitqueue.gateway.SubmitQueueGateway.Land:input_type -> uber.submitqueue.gateway.LandRequest - 4, // 7: uber.submitqueue.gateway.SubmitQueueGateway.Cancel:input_type -> uber.submitqueue.gateway.CancelRequest - 6, // 8: uber.submitqueue.gateway.SubmitQueueGateway.Status:input_type -> uber.submitqueue.gateway.StatusRequest - 1, // 9: uber.submitqueue.gateway.SubmitQueueGateway.Ping:output_type -> uber.submitqueue.gateway.PingResponse - 3, // 10: uber.submitqueue.gateway.SubmitQueueGateway.Land:output_type -> uber.submitqueue.gateway.LandResponse - 5, // 11: uber.submitqueue.gateway.SubmitQueueGateway.Cancel:output_type -> uber.submitqueue.gateway.CancelResponse - 7, // 12: uber.submitqueue.gateway.SubmitQueueGateway.Status:output_type -> uber.submitqueue.gateway.StatusResponse - 9, // [9:13] is the sub-list for method output_type - 5, // [5:9] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 15, // 0: uber.submitqueue.gateway.LandRequest.change:type_name -> uber.base.change.Change + 16, // 1: uber.submitqueue.gateway.LandRequest.strategy:type_name -> uber.base.mergestrategy.Strategy + 14, // 2: uber.submitqueue.gateway.RequestSummary.metadata:type_name -> uber.submitqueue.gateway.RequestSummary.MetadataEntry + 6, // 3: uber.submitqueue.gateway.StatusByIDResponse.request:type_name -> uber.submitqueue.gateway.RequestSummary + 6, // 4: uber.submitqueue.gateway.StatusByChangeIDResponse.requests:type_name -> uber.submitqueue.gateway.RequestSummary + 11, // 5: uber.submitqueue.gateway.UnrecognizedQueueError.error:type_name -> uber.submitqueue.gateway.Error + 11, // 6: uber.submitqueue.gateway.RequestNotFoundError.error:type_name -> uber.submitqueue.gateway.Error + 0, // 7: uber.submitqueue.gateway.SubmitQueueGateway.Ping:input_type -> uber.submitqueue.gateway.PingRequest + 2, // 8: uber.submitqueue.gateway.SubmitQueueGateway.Land:input_type -> uber.submitqueue.gateway.LandRequest + 4, // 9: uber.submitqueue.gateway.SubmitQueueGateway.Cancel:input_type -> uber.submitqueue.gateway.CancelRequest + 7, // 10: uber.submitqueue.gateway.SubmitQueueGateway.StatusByID:input_type -> uber.submitqueue.gateway.StatusByIDRequest + 9, // 11: uber.submitqueue.gateway.SubmitQueueGateway.StatusByChangeID:input_type -> uber.submitqueue.gateway.StatusByChangeIDRequest + 1, // 12: uber.submitqueue.gateway.SubmitQueueGateway.Ping:output_type -> uber.submitqueue.gateway.PingResponse + 3, // 13: uber.submitqueue.gateway.SubmitQueueGateway.Land:output_type -> uber.submitqueue.gateway.LandResponse + 5, // 14: uber.submitqueue.gateway.SubmitQueueGateway.Cancel:output_type -> uber.submitqueue.gateway.CancelResponse + 8, // 15: uber.submitqueue.gateway.SubmitQueueGateway.StatusByID:output_type -> uber.submitqueue.gateway.StatusByIDResponse + 10, // 16: uber.submitqueue.gateway.SubmitQueueGateway.StatusByChangeID:output_type -> uber.submitqueue.gateway.StatusByChangeIDResponse + 12, // [12:17] is the sub-list for method output_type + 7, // [7:12] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_gateway_proto_init() } @@ -743,7 +945,7 @@ func file_gateway_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_gateway_proto_rawDesc), len(file_gateway_proto_rawDesc)), NumEnums: 0, - NumMessages: 12, + NumMessages: 15, NumExtensions: 0, NumServices: 1, }, diff --git a/api/submitqueue/gateway/protopb/gateway.pb.yarpc.go b/api/submitqueue/gateway/protopb/gateway.pb.yarpc.go index a549dbd2..a2f0a65b 100644 --- a/api/submitqueue/gateway/protopb/gateway.pb.yarpc.go +++ b/api/submitqueue/gateway/protopb/gateway.pb.yarpc.go @@ -24,7 +24,8 @@ type SubmitQueueGatewayYARPCClient interface { Ping(context.Context, *PingRequest, ...yarpc.CallOption) (*PingResponse, error) Land(context.Context, *LandRequest, ...yarpc.CallOption) (*LandResponse, error) Cancel(context.Context, *CancelRequest, ...yarpc.CallOption) (*CancelResponse, error) - Status(context.Context, *StatusRequest, ...yarpc.CallOption) (*StatusResponse, error) + StatusByID(context.Context, *StatusByIDRequest, ...yarpc.CallOption) (*StatusByIDResponse, error) + StatusByChangeID(context.Context, *StatusByChangeIDRequest, ...yarpc.CallOption) (*StatusByChangeIDResponse, error) } func newSubmitQueueGatewayYARPCClient(clientConfig transport.ClientConfig, anyResolver v2.AnyResolver, options ...v2.ClientOption) SubmitQueueGatewayYARPCClient { @@ -48,7 +49,8 @@ type SubmitQueueGatewayYARPCServer interface { Ping(context.Context, *PingRequest) (*PingResponse, error) Land(context.Context, *LandRequest) (*LandResponse, error) Cancel(context.Context, *CancelRequest) (*CancelResponse, error) - Status(context.Context, *StatusRequest) (*StatusResponse, error) + StatusByID(context.Context, *StatusByIDRequest) (*StatusByIDResponse, error) + StatusByChangeID(context.Context, *StatusByChangeIDRequest) (*StatusByChangeIDResponse, error) } type buildSubmitQueueGatewayYARPCProceduresParams struct { @@ -93,11 +95,21 @@ func buildSubmitQueueGatewayYARPCProcedures(params buildSubmitQueueGatewayYARPCP ), }, { - MethodName: "Status", + MethodName: "StatusByID", Handler: v2.NewUnaryHandler( v2.UnaryHandlerParams{ - Handle: handler.Status, - NewRequest: newSubmitQueueGatewayServiceStatusYARPCRequest, + Handle: handler.StatusByID, + NewRequest: newSubmitQueueGatewayServiceStatusByIDYARPCRequest, + AnyResolver: params.AnyResolver, + }, + ), + }, + { + MethodName: "StatusByChangeID", + Handler: v2.NewUnaryHandler( + v2.UnaryHandlerParams{ + Handle: handler.StatusByChangeID, + NewRequest: newSubmitQueueGatewayServiceStatusByChangeIDYARPCRequest, AnyResolver: params.AnyResolver, }, ), @@ -250,14 +262,26 @@ func (c *_SubmitQueueGatewayYARPCCaller) Cancel(ctx context.Context, request *Ca return response, err } -func (c *_SubmitQueueGatewayYARPCCaller) Status(ctx context.Context, request *StatusRequest, options ...yarpc.CallOption) (*StatusResponse, error) { - responseMessage, err := c.streamClient.Call(ctx, "Status", request, newSubmitQueueGatewayServiceStatusYARPCResponse, options...) +func (c *_SubmitQueueGatewayYARPCCaller) StatusByID(ctx context.Context, request *StatusByIDRequest, options ...yarpc.CallOption) (*StatusByIDResponse, error) { + responseMessage, err := c.streamClient.Call(ctx, "StatusByID", request, newSubmitQueueGatewayServiceStatusByIDYARPCResponse, options...) + if responseMessage == nil { + return nil, err + } + response, ok := responseMessage.(*StatusByIDResponse) + if !ok { + return nil, v2.CastError(emptySubmitQueueGatewayServiceStatusByIDYARPCResponse, responseMessage) + } + return response, err +} + +func (c *_SubmitQueueGatewayYARPCCaller) StatusByChangeID(ctx context.Context, request *StatusByChangeIDRequest, options ...yarpc.CallOption) (*StatusByChangeIDResponse, error) { + responseMessage, err := c.streamClient.Call(ctx, "StatusByChangeID", request, newSubmitQueueGatewayServiceStatusByChangeIDYARPCResponse, options...) if responseMessage == nil { return nil, err } - response, ok := responseMessage.(*StatusResponse) + response, ok := responseMessage.(*StatusByChangeIDResponse) if !ok { - return nil, v2.CastError(emptySubmitQueueGatewayServiceStatusYARPCResponse, responseMessage) + return nil, v2.CastError(emptySubmitQueueGatewayServiceStatusByChangeIDYARPCResponse, responseMessage) } return response, err } @@ -314,16 +338,32 @@ func (h *_SubmitQueueGatewayYARPCHandler) Cancel(ctx context.Context, requestMes return response, err } -func (h *_SubmitQueueGatewayYARPCHandler) Status(ctx context.Context, requestMessage proto.Message) (proto.Message, error) { - var request *StatusRequest +func (h *_SubmitQueueGatewayYARPCHandler) StatusByID(ctx context.Context, requestMessage proto.Message) (proto.Message, error) { + var request *StatusByIDRequest var ok bool if requestMessage != nil { - request, ok = requestMessage.(*StatusRequest) + request, ok = requestMessage.(*StatusByIDRequest) if !ok { - return nil, v2.CastError(emptySubmitQueueGatewayServiceStatusYARPCRequest, requestMessage) + return nil, v2.CastError(emptySubmitQueueGatewayServiceStatusByIDYARPCRequest, requestMessage) } } - response, err := h.server.Status(ctx, request) + response, err := h.server.StatusByID(ctx, request) + if response == nil { + return nil, err + } + return response, err +} + +func (h *_SubmitQueueGatewayYARPCHandler) StatusByChangeID(ctx context.Context, requestMessage proto.Message) (proto.Message, error) { + var request *StatusByChangeIDRequest + var ok bool + if requestMessage != nil { + request, ok = requestMessage.(*StatusByChangeIDRequest) + if !ok { + return nil, v2.CastError(emptySubmitQueueGatewayServiceStatusByChangeIDYARPCRequest, requestMessage) + } + } + response, err := h.server.StatusByChangeID(ctx, request) if response == nil { return nil, err } @@ -354,67 +394,87 @@ func newSubmitQueueGatewayServiceCancelYARPCResponse() proto.Message { return &CancelResponse{} } -func newSubmitQueueGatewayServiceStatusYARPCRequest() proto.Message { - return &StatusRequest{} +func newSubmitQueueGatewayServiceStatusByIDYARPCRequest() proto.Message { + return &StatusByIDRequest{} +} + +func newSubmitQueueGatewayServiceStatusByIDYARPCResponse() proto.Message { + return &StatusByIDResponse{} +} + +func newSubmitQueueGatewayServiceStatusByChangeIDYARPCRequest() proto.Message { + return &StatusByChangeIDRequest{} } -func newSubmitQueueGatewayServiceStatusYARPCResponse() proto.Message { - return &StatusResponse{} +func newSubmitQueueGatewayServiceStatusByChangeIDYARPCResponse() proto.Message { + return &StatusByChangeIDResponse{} } var ( - emptySubmitQueueGatewayServicePingYARPCRequest = &PingRequest{} - emptySubmitQueueGatewayServicePingYARPCResponse = &PingResponse{} - emptySubmitQueueGatewayServiceLandYARPCRequest = &LandRequest{} - emptySubmitQueueGatewayServiceLandYARPCResponse = &LandResponse{} - emptySubmitQueueGatewayServiceCancelYARPCRequest = &CancelRequest{} - emptySubmitQueueGatewayServiceCancelYARPCResponse = &CancelResponse{} - emptySubmitQueueGatewayServiceStatusYARPCRequest = &StatusRequest{} - emptySubmitQueueGatewayServiceStatusYARPCResponse = &StatusResponse{} + emptySubmitQueueGatewayServicePingYARPCRequest = &PingRequest{} + emptySubmitQueueGatewayServicePingYARPCResponse = &PingResponse{} + emptySubmitQueueGatewayServiceLandYARPCRequest = &LandRequest{} + emptySubmitQueueGatewayServiceLandYARPCResponse = &LandResponse{} + emptySubmitQueueGatewayServiceCancelYARPCRequest = &CancelRequest{} + emptySubmitQueueGatewayServiceCancelYARPCResponse = &CancelResponse{} + emptySubmitQueueGatewayServiceStatusByIDYARPCRequest = &StatusByIDRequest{} + emptySubmitQueueGatewayServiceStatusByIDYARPCResponse = &StatusByIDResponse{} + emptySubmitQueueGatewayServiceStatusByChangeIDYARPCRequest = &StatusByChangeIDRequest{} + emptySubmitQueueGatewayServiceStatusByChangeIDYARPCResponse = &StatusByChangeIDResponse{} ) var yarpcFileDescriptorClosuref1a937782ebbded5 = [][]byte{ // gateway.proto []byte{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0x5f, 0x6b, 0xd4, 0x40, - 0x10, 0x6f, 0x2e, 0xed, 0xd9, 0xce, 0xdd, 0x95, 0xb2, 0x94, 0x12, 0x42, 0xc5, 0x76, 0x45, 0x7b, - 0x4f, 0x49, 0x39, 0x51, 0xc4, 0xe2, 0x4b, 0x4b, 0xf5, 0x45, 0x4b, 0x4d, 0x11, 0x41, 0x90, 0xb2, - 0xc9, 0x0d, 0xb9, 0xd0, 0x4b, 0x72, 0x97, 0xdd, 0x54, 0xce, 0x67, 0xfd, 0x06, 0x7e, 0x38, 0x3f, - 0x8e, 0xec, 0x9f, 0xdc, 0x25, 0x60, 0xae, 0x0f, 0x3e, 0x65, 0x67, 0xf2, 0xfb, 0xcd, 0xcc, 0xce, - 0xfc, 0x66, 0x61, 0x10, 0x33, 0x81, 0xdf, 0xd9, 0xc2, 0x9b, 0x15, 0xb9, 0xc8, 0x89, 0x53, 0x86, - 0x58, 0x78, 0xbc, 0x0c, 0xd3, 0x44, 0xcc, 0x4b, 0x2c, 0xd1, 0x33, 0xff, 0x5d, 0xca, 0x66, 0x89, - 0x1f, 0x32, 0x8e, 0x7e, 0x34, 0x61, 0x59, 0x8c, 0xbe, 0x22, 0x18, 0x43, 0xb3, 0xdd, 0xd3, 0x25, - 0x26, 0xc5, 0x22, 0x46, 0x2e, 0x0a, 0x26, 0x30, 0x5e, 0x18, 0x68, 0xc3, 0xa7, 0x19, 0xf4, 0x04, - 0x7a, 0xd7, 0x49, 0x16, 0x07, 0x38, 0x2f, 0x91, 0x0b, 0xe2, 0xc0, 0xa3, 0x14, 0x39, 0x67, 0x31, - 0x3a, 0xd6, 0x91, 0x35, 0xdc, 0x09, 0x2a, 0x93, 0xfe, 0xb2, 0xa0, 0xaf, 0x91, 0x7c, 0x96, 0x67, - 0x1c, 0xdb, 0xa1, 0xe4, 0x18, 0xfa, 0x1c, 0x8b, 0xfb, 0x24, 0xc2, 0xdb, 0x8c, 0xa5, 0xe8, 0x74, - 0xd4, 0xef, 0x9e, 0xf1, 0x5d, 0xb1, 0x14, 0xc9, 0x21, 0xec, 0x88, 0x24, 0x45, 0x2e, 0x58, 0x3a, - 0x73, 0xec, 0x23, 0x6b, 0x68, 0x07, 0x2b, 0x07, 0x71, 0x61, 0x7b, 0x92, 0x73, 0xa1, 0xc8, 0x9b, - 0x8a, 0xbc, 0xb4, 0xe9, 0x6f, 0x0b, 0x7a, 0x1f, 0x58, 0x36, 0xae, 0x2a, 0xde, 0x87, 0x2d, 0xd5, - 0x27, 0x53, 0x84, 0x36, 0xc8, 0x29, 0x74, 0x75, 0x63, 0x54, 0xf2, 0xde, 0xc8, 0xf1, 0x54, 0x5f, - 0x65, 0x6b, 0x3c, 0xd3, 0xb1, 0x0b, 0xf5, 0x09, 0x0c, 0x8e, 0xbc, 0x85, 0xed, 0xaa, 0x35, 0x2a, - 0xe7, 0xee, 0xe8, 0xb8, 0xc6, 0x69, 0xb6, 0xee, 0xc6, 0x1c, 0x82, 0x25, 0x85, 0x52, 0xe8, 0xeb, - 0xaa, 0x4c, 0x77, 0x08, 0x6c, 0xf2, 0x79, 0x32, 0x36, 0x55, 0xa9, 0x33, 0x3d, 0x83, 0xc1, 0x05, - 0xcb, 0x22, 0x9c, 0x56, 0xb5, 0xff, 0x03, 0x44, 0x0e, 0xa0, 0x5b, 0x20, 0xe3, 0x79, 0x66, 0xda, - 0x66, 0x2c, 0xba, 0x07, 0xbb, 0x15, 0x59, 0xa7, 0xa0, 0x4f, 0x61, 0x70, 0x23, 0x98, 0x28, 0xf9, - 0x9a, 0x70, 0xf4, 0x8f, 0x05, 0xbb, 0x15, 0xca, 0x94, 0x76, 0x00, 0x5d, 0xae, 0x3c, 0x06, 0x68, - 0x2c, 0xf2, 0x18, 0x60, 0xca, 0xb8, 0xb8, 0xc5, 0xa2, 0xc8, 0x0b, 0x93, 0x7d, 0x47, 0x7a, 0x2e, - 0xa5, 0x83, 0x04, 0xb0, 0x9d, 0xa2, 0x60, 0x63, 0x26, 0x98, 0x63, 0x1f, 0xd9, 0xc3, 0xde, 0xe8, - 0x95, 0xd7, 0x26, 0x56, 0xaf, 0x99, 0xd2, 0xfb, 0x68, 0x88, 0x97, 0x99, 0x28, 0x16, 0xc1, 0x32, - 0x8e, 0x7b, 0x06, 0x83, 0xc6, 0x2f, 0xb2, 0x07, 0xf6, 0x1d, 0x2e, 0x4c, 0x61, 0xf2, 0x28, 0xe7, - 0x7b, 0xcf, 0xa6, 0x65, 0xa5, 0x22, 0x6d, 0xbc, 0xe9, 0xbc, 0xb6, 0xe8, 0x31, 0x6c, 0xe9, 0xca, - 0xda, 0x45, 0x8b, 0x70, 0xf0, 0x39, 0x2b, 0x30, 0xca, 0xe3, 0x2c, 0xf9, 0x81, 0xe3, 0x4f, 0xb2, - 0x46, 0xcd, 0x79, 0x09, 0x5b, 0xfa, 0x9e, 0x96, 0xd2, 0xc7, 0x93, 0xf6, 0xab, 0x28, 0x7c, 0xa0, - 0xd1, 0x2b, 0xb5, 0x75, 0x6a, 0x6a, 0xa3, 0x0c, 0xf6, 0xcd, 0x0c, 0xae, 0x72, 0xf1, 0x2e, 0x2f, - 0xb3, 0xf1, 0x7f, 0x25, 0xa9, 0xe6, 0xd8, 0x59, 0xcd, 0x71, 0xf4, 0xd3, 0x06, 0x72, 0xa3, 0x88, - 0xea, 0x12, 0xef, 0x35, 0x8f, 0x7c, 0x81, 0x4d, 0xb9, 0x94, 0xe4, 0x59, 0x7b, 0xe8, 0xda, 0x7a, - 0xbb, 0xcf, 0x1f, 0x82, 0x19, 0x69, 0x6d, 0xc8, 0xc0, 0x52, 0xcf, 0xeb, 0x02, 0xd7, 0xb6, 0x70, - 0x5d, 0xe0, 0xfa, 0x5a, 0xd0, 0x0d, 0xf2, 0x0d, 0xba, 0x5a, 0xc7, 0xe4, 0xa4, 0x9d, 0xd3, 0x58, - 0x13, 0x77, 0xf8, 0x30, 0xb0, 0x1e, 0x5e, 0x6b, 0x6f, 0x5d, 0xf8, 0xc6, 0xda, 0xac, 0x0b, 0xdf, - 0x94, 0x31, 0xdd, 0x38, 0xbf, 0x83, 0xc3, 0x28, 0x4f, 0x5b, 0x09, 0xe7, 0x7d, 0x33, 0x98, 0x6b, - 0xf9, 0xb8, 0x5e, 0x5b, 0x5f, 0xcf, 0xe2, 0x44, 0x4c, 0xca, 0xd0, 0x8b, 0xf2, 0xd4, 0x97, 0x24, - 0xbf, 0x46, 0xf2, 0xe5, 0x63, 0x5d, 0xb7, 0x4d, 0x10, 0xfd, 0x5a, 0xcf, 0xc2, 0xb0, 0xab, 0x0e, - 0x2f, 0xfe, 0x06, 0x00, 0x00, 0xff, 0xff, 0x01, 0x08, 0x76, 0xa7, 0x23, 0x06, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x51, 0x4f, 0xdb, 0x48, + 0x10, 0xc6, 0x71, 0x12, 0x92, 0x49, 0x88, 0xb8, 0x15, 0xe2, 0x2c, 0x1f, 0x27, 0xc2, 0xea, 0xee, + 0x88, 0x74, 0x27, 0x87, 0xcb, 0xe9, 0xd0, 0xe9, 0xd0, 0x3d, 0x5c, 0x80, 0x56, 0x48, 0x05, 0x51, + 0x47, 0xa8, 0x55, 0xa5, 0x2a, 0xdd, 0x24, 0x2b, 0x63, 0x81, 0xed, 0xb0, 0xbb, 0xa6, 0x4a, 0x2b, + 0xf5, 0xad, 0x8f, 0x7d, 0xeb, 0x9f, 0xea, 0xbf, 0xaa, 0xbc, 0xbb, 0x76, 0x1c, 0xc0, 0x40, 0xd5, + 0x27, 0xef, 0xcc, 0xce, 0xf7, 0xcd, 0xec, 0xb7, 0xb3, 0x63, 0x58, 0xf1, 0x88, 0xa0, 0x6f, 0xc9, + 0xcc, 0x99, 0xb2, 0x48, 0x44, 0xc8, 0x8a, 0x47, 0x94, 0x39, 0x3c, 0x1e, 0x05, 0xbe, 0xb8, 0x8a, + 0x69, 0x4c, 0x1d, 0xbd, 0x6f, 0x63, 0x32, 0xf5, 0xbb, 0x23, 0xc2, 0x69, 0x77, 0x7c, 0x4e, 0x42, + 0x8f, 0x76, 0x25, 0x40, 0x1b, 0x0a, 0x6d, 0xef, 0x64, 0x31, 0x01, 0x65, 0x1e, 0xe5, 0x82, 0x11, + 0x41, 0xbd, 0x99, 0x0e, 0x5d, 0xf0, 0x29, 0x04, 0xde, 0x86, 0xc6, 0xa9, 0x1f, 0x7a, 0x2e, 0xbd, + 0x8a, 0x29, 0x17, 0xc8, 0x82, 0xe5, 0x80, 0x72, 0x4e, 0x3c, 0x6a, 0x19, 0x6d, 0xa3, 0x53, 0x77, + 0x53, 0x13, 0x7f, 0x34, 0xa0, 0xa9, 0x22, 0xf9, 0x34, 0x0a, 0x39, 0x2d, 0x0e, 0x45, 0x5b, 0xd0, + 0xe4, 0x94, 0x5d, 0xfb, 0x63, 0x3a, 0x0c, 0x49, 0x40, 0xad, 0x92, 0xdc, 0x6e, 0x68, 0xdf, 0x09, + 0x09, 0x28, 0xda, 0x80, 0xba, 0xf0, 0x03, 0xca, 0x05, 0x09, 0xa6, 0x96, 0xd9, 0x36, 0x3a, 0xa6, + 0x3b, 0x77, 0x20, 0x1b, 0x6a, 0xe7, 0x11, 0x17, 0x12, 0x5c, 0x96, 0xe0, 0xcc, 0xc6, 0x9f, 0x0d, + 0x68, 0x3c, 0x23, 0xe1, 0x24, 0xad, 0x78, 0x0d, 0x2a, 0x52, 0x27, 0x5d, 0x84, 0x32, 0xd0, 0x0e, + 0x54, 0x95, 0x30, 0x32, 0x79, 0xa3, 0x67, 0x39, 0x52, 0xd7, 0x44, 0x1a, 0x47, 0x2b, 0xb6, 0x2f, + 0x3f, 0xae, 0x8e, 0x43, 0xff, 0x41, 0x2d, 0x95, 0x46, 0xe6, 0x6c, 0xf5, 0xb6, 0x72, 0x98, 0x45, + 0xe9, 0x06, 0x7a, 0xe1, 0x66, 0x10, 0x8c, 0xa1, 0xa9, 0xaa, 0xd2, 0xea, 0x20, 0x28, 0xf3, 0x2b, + 0x7f, 0xa2, 0xab, 0x92, 0x6b, 0xbc, 0x07, 0x2b, 0xfb, 0x24, 0x1c, 0xd3, 0xcb, 0xb4, 0xf6, 0x3b, + 0x82, 0xd0, 0x3a, 0x54, 0x19, 0x25, 0x3c, 0x0a, 0xb5, 0x6c, 0xda, 0xc2, 0xab, 0xd0, 0x4a, 0xc1, + 0x2a, 0x05, 0xfe, 0x52, 0x82, 0x96, 0x66, 0x1a, 0xc4, 0x41, 0x40, 0xd8, 0xec, 0x4e, 0xc2, 0x4c, + 0xa0, 0x52, 0x5e, 0xa0, 0x4d, 0x68, 0xa8, 0x83, 0x0f, 0x63, 0xe6, 0x73, 0xcb, 0x6c, 0x9b, 0x9d, + 0xba, 0x0b, 0xca, 0x75, 0xc6, 0x7c, 0x8e, 0x7e, 0x81, 0x16, 0xa3, 0x63, 0xea, 0x5f, 0xd3, 0xc9, + 0x90, 0x88, 0x61, 0xc0, 0xa5, 0x2a, 0xa6, 0xdb, 0x4c, 0xbd, 0xff, 0x8b, 0x63, 0x9e, 0x54, 0xcb, + 0x05, 0x11, 0x31, 0xb7, 0x2a, 0xaa, 0x5a, 0x65, 0xa1, 0x9f, 0x01, 0x2e, 0x09, 0x17, 0x43, 0xca, + 0x58, 0xc4, 0xac, 0xaa, 0xdc, 0xab, 0x27, 0x9e, 0xc3, 0xc4, 0x81, 0x5c, 0xa8, 0x05, 0x54, 0x90, + 0x09, 0x11, 0xc4, 0x5a, 0x6e, 0x9b, 0x9d, 0x46, 0x6f, 0xd7, 0x29, 0x6a, 0x7c, 0x67, 0xf1, 0x8c, + 0xce, 0xb1, 0x06, 0x1e, 0x86, 0x82, 0xcd, 0xdc, 0x8c, 0xc7, 0xde, 0x83, 0x95, 0x85, 0x2d, 0xb4, + 0x0a, 0xe6, 0x05, 0x9d, 0x69, 0x2d, 0x92, 0x65, 0x22, 0xc5, 0x35, 0xb9, 0x9c, 0x4b, 0x21, 0x8d, + 0x7f, 0x4b, 0xff, 0x18, 0x78, 0x1b, 0x7e, 0x18, 0xc8, 0xca, 0xfb, 0xb3, 0xa3, 0x83, 0x7b, 0xae, + 0x07, 0xbf, 0x04, 0x94, 0x0f, 0xd4, 0xb7, 0xdd, 0x87, 0x65, 0xa6, 0x40, 0x32, 0xb8, 0xd1, 0xeb, + 0x3c, 0xf6, 0x38, 0x6e, 0x0a, 0xc4, 0xbb, 0xf0, 0x63, 0xca, 0xac, 0x5a, 0x73, 0x5e, 0xc8, 0x4f, + 0x50, 0xd7, 0x97, 0x95, 0x55, 0x53, 0x53, 0x8e, 0xa3, 0x09, 0x7e, 0x03, 0xd6, 0x6d, 0x9c, 0xae, + 0xeb, 0x00, 0x6a, 0x9a, 0x9e, 0x5b, 0x86, 0xd4, 0xf9, 0xf1, 0x85, 0x65, 0x48, 0xbc, 0x05, 0x15, + 0x75, 0x6d, 0xc5, 0xd3, 0x81, 0xc2, 0xfa, 0x59, 0xc8, 0xe8, 0x38, 0xf2, 0x42, 0xff, 0x1d, 0x9d, + 0x3c, 0x4f, 0x88, 0x15, 0xe6, 0x6f, 0xa8, 0xa8, 0x26, 0x50, 0xc2, 0x6c, 0x16, 0xe7, 0x97, 0xf1, + 0xae, 0x8a, 0xbe, 0xbb, 0x6b, 0xf1, 0x07, 0x58, 0xd3, 0x55, 0x9e, 0x44, 0xe2, 0x49, 0x14, 0x87, + 0x93, 0xef, 0x4a, 0x92, 0x5e, 0x70, 0x29, 0xf7, 0x5c, 0x16, 0xb4, 0x36, 0x17, 0xb5, 0xee, 0x7d, + 0x2a, 0x03, 0x1a, 0x48, 0x56, 0x79, 0xc2, 0xa7, 0x8a, 0x14, 0xbd, 0x80, 0x72, 0x32, 0x1a, 0xd1, + 0xaf, 0xc5, 0x79, 0x73, 0x43, 0xd6, 0xfe, 0xed, 0xa1, 0x30, 0xfd, 0xc0, 0x97, 0x12, 0xe2, 0x64, + 0xaa, 0xdc, 0x47, 0x9c, 0x9b, 0x85, 0xf7, 0x11, 0xe7, 0x87, 0x13, 0x5e, 0x42, 0xaf, 0xa1, 0xaa, + 0xa6, 0x09, 0xda, 0x2e, 0xc6, 0x2c, 0x0c, 0x2b, 0xbb, 0xf3, 0x70, 0x60, 0x46, 0xef, 0x03, 0xcc, + 0x5f, 0x09, 0xfa, 0xbd, 0x18, 0x79, 0xeb, 0xd1, 0xd9, 0x7f, 0x3c, 0x2e, 0x38, 0x4b, 0xf5, 0x1e, + 0x56, 0x6f, 0xb6, 0x3f, 0xfa, 0xf3, 0x61, 0x8e, 0x1b, 0x4f, 0xcc, 0xee, 0x7d, 0x0b, 0x24, 0x4d, + 0xde, 0xbf, 0x80, 0x8d, 0x71, 0x14, 0x14, 0x42, 0xfb, 0x4d, 0xdd, 0x21, 0xa7, 0xc9, 0xbf, 0xf6, + 0xd4, 0x78, 0xb5, 0xe7, 0xf9, 0xe2, 0x3c, 0x1e, 0x39, 0xe3, 0x28, 0xe8, 0x26, 0xa0, 0x6e, 0x0e, + 0xd4, 0x4d, 0xfe, 0xdd, 0x79, 0x5b, 0x93, 0xa8, 0x9f, 0xf7, 0x74, 0x34, 0xaa, 0xca, 0xc5, 0x5f, + 0x5f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x16, 0x9b, 0xb0, 0x1e, 0x32, 0x08, 0x00, 0x00, }, // api/base/change/proto/change.proto []byte{ diff --git a/api/submitqueue/gateway/protopb/gateway_grpc.pb.go b/api/submitqueue/gateway/protopb/gateway_grpc.pb.go index f271309f..39596374 100644 --- a/api/submitqueue/gateway/protopb/gateway_grpc.pb.go +++ b/api/submitqueue/gateway/protopb/gateway_grpc.pb.go @@ -34,10 +34,11 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - SubmitQueueGateway_Ping_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/Ping" - SubmitQueueGateway_Land_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/Land" - SubmitQueueGateway_Cancel_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/Cancel" - SubmitQueueGateway_Status_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/Status" + SubmitQueueGateway_Ping_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/Ping" + SubmitQueueGateway_Land_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/Land" + SubmitQueueGateway_Cancel_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/Cancel" + SubmitQueueGateway_StatusByID_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/StatusByID" + SubmitQueueGateway_StatusByChangeID_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/StatusByChangeID" ) // SubmitQueueGatewayClient is the client API for SubmitQueueGateway service. @@ -63,9 +64,10 @@ type SubmitQueueGatewayClient interface { // successful Cancel response means the request was cancelled — the actual terminal outcome (cancelled, landed, or // error) must be checked through the separate status / request-log API. Cancel(ctx context.Context, in *CancelRequest, opts ...grpc.CallOption) (*CancelResponse, error) - // Status returns the current status of a previously submitted request, identified by its sqid. - // The status is eventually consistent with the request store and reconciled from the append-only request log. - Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) + // StatusByID returns the current materialized status of one request. + StatusByID(ctx context.Context, in *StatusByIDRequest, opts ...grpc.CallOption) (*StatusByIDResponse, error) + // StatusByChangeID returns current materialized statuses for an exact pinned change URI. + StatusByChangeID(ctx context.Context, in *StatusByChangeIDRequest, opts ...grpc.CallOption) (*StatusByChangeIDResponse, error) } type submitQueueGatewayClient struct { @@ -106,10 +108,20 @@ func (c *submitQueueGatewayClient) Cancel(ctx context.Context, in *CancelRequest return out, nil } -func (c *submitQueueGatewayClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { +func (c *submitQueueGatewayClient) StatusByID(ctx context.Context, in *StatusByIDRequest, opts ...grpc.CallOption) (*StatusByIDResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(StatusResponse) - err := c.cc.Invoke(ctx, SubmitQueueGateway_Status_FullMethodName, in, out, cOpts...) + out := new(StatusByIDResponse) + err := c.cc.Invoke(ctx, SubmitQueueGateway_StatusByID_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *submitQueueGatewayClient) StatusByChangeID(ctx context.Context, in *StatusByChangeIDRequest, opts ...grpc.CallOption) (*StatusByChangeIDResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(StatusByChangeIDResponse) + err := c.cc.Invoke(ctx, SubmitQueueGateway_StatusByChangeID_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -139,9 +151,10 @@ type SubmitQueueGatewayServer interface { // successful Cancel response means the request was cancelled — the actual terminal outcome (cancelled, landed, or // error) must be checked through the separate status / request-log API. Cancel(context.Context, *CancelRequest) (*CancelResponse, error) - // Status returns the current status of a previously submitted request, identified by its sqid. - // The status is eventually consistent with the request store and reconciled from the append-only request log. - Status(context.Context, *StatusRequest) (*StatusResponse, error) + // StatusByID returns the current materialized status of one request. + StatusByID(context.Context, *StatusByIDRequest) (*StatusByIDResponse, error) + // StatusByChangeID returns current materialized statuses for an exact pinned change URI. + StatusByChangeID(context.Context, *StatusByChangeIDRequest) (*StatusByChangeIDResponse, error) mustEmbedUnimplementedSubmitQueueGatewayServer() } @@ -161,8 +174,11 @@ func (UnimplementedSubmitQueueGatewayServer) Land(context.Context, *LandRequest) func (UnimplementedSubmitQueueGatewayServer) Cancel(context.Context, *CancelRequest) (*CancelResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Cancel not implemented") } -func (UnimplementedSubmitQueueGatewayServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Status not implemented") +func (UnimplementedSubmitQueueGatewayServer) StatusByID(context.Context, *StatusByIDRequest) (*StatusByIDResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StatusByID not implemented") +} +func (UnimplementedSubmitQueueGatewayServer) StatusByChangeID(context.Context, *StatusByChangeIDRequest) (*StatusByChangeIDResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StatusByChangeID not implemented") } func (UnimplementedSubmitQueueGatewayServer) mustEmbedUnimplementedSubmitQueueGatewayServer() {} func (UnimplementedSubmitQueueGatewayServer) testEmbeddedByValue() {} @@ -239,20 +255,38 @@ func _SubmitQueueGateway_Cancel_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } -func _SubmitQueueGateway_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(StatusRequest) +func _SubmitQueueGateway_StatusByID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatusByIDRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(SubmitQueueGatewayServer).Status(ctx, in) + return srv.(SubmitQueueGatewayServer).StatusByID(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: SubmitQueueGateway_Status_FullMethodName, + FullMethod: SubmitQueueGateway_StatusByID_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SubmitQueueGatewayServer).Status(ctx, req.(*StatusRequest)) + return srv.(SubmitQueueGatewayServer).StatusByID(ctx, req.(*StatusByIDRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SubmitQueueGateway_StatusByChangeID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatusByChangeIDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SubmitQueueGatewayServer).StatusByChangeID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SubmitQueueGateway_StatusByChangeID_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SubmitQueueGatewayServer).StatusByChangeID(ctx, req.(*StatusByChangeIDRequest)) } return interceptor(ctx, in, info, handler) } @@ -277,8 +311,12 @@ var SubmitQueueGateway_ServiceDesc = grpc.ServiceDesc{ Handler: _SubmitQueueGateway_Cancel_Handler, }, { - MethodName: "Status", - Handler: _SubmitQueueGateway_Status_Handler, + MethodName: "StatusByID", + Handler: _SubmitQueueGateway_StatusByID_Handler, + }, + { + MethodName: "StatusByChangeID", + Handler: _SubmitQueueGateway_StatusByChangeID_Handler, }, }, Streams: []grpc.StreamDesc{}, diff --git a/service/submitqueue/gateway/server/main.go b/service/submitqueue/gateway/server/main.go index 90a220cd..7bbd6a66 100644 --- a/service/submitqueue/gateway/server/main.go +++ b/service/submitqueue/gateway/server/main.go @@ -86,14 +86,37 @@ func (s *GatewayServer) Cancel(ctx context.Context, req *pb.CancelRequest) (*pb. return &pb.CancelResponse{}, nil } -// Status maps the wire request to an entity, delegates to the controller, and -// maps the read-model result back to the wire response. -func (s *GatewayServer) Status(ctx context.Context, req *pb.StatusRequest) (*pb.StatusResponse, error) { - state, err := s.statusController.Status(ctx, mapper.ProtoToStatusRequest(req)) +// StatusByID maps the wire request to an entity, delegates to the controller, and maps the result back to the wire response. +func (s *GatewayServer) StatusByID(ctx context.Context, req *pb.StatusByIDRequest) (*pb.StatusByIDResponse, error) { + summary, err := s.statusController.StatusByID(ctx, mapper.ProtoToStatusByIDRequest(req)) if err != nil { return nil, err } - return mapper.CurrentStateToProto(state), nil + return &pb.StatusByIDResponse{Request: mapper.RequestSummaryToProto(summary)}, nil +} + +// StatusByChangeID maps the wire request to an entity, delegates to the controller, and maps the results back to the wire response. +func (s *GatewayServer) StatusByChangeID(ctx context.Context, req *pb.StatusByChangeIDRequest) (*pb.StatusByChangeIDResponse, error) { + summaries, err := s.statusController.StatusByChangeID(ctx, mapper.ProtoToStatusByChangeIDRequest(req)) + if err != nil { + return nil, err + } + return &pb.StatusByChangeIDResponse{Requests: mapper.RequestSummariesToProto(summaries)}, nil +} + +func gatewayStatusError(err error) error { + switch { + case controller.IsRequestNotFound(err): + return status.Error(codes.NotFound, err.Error()) + case controller.IsTooManyChangeRequests(err): + return status.Error(codes.ResourceExhausted, err.Error()) + case controller.IsInternalConsistency(err): + return status.Error(codes.Internal, err.Error()) + case controller.IsInvalidRequest(err), controller.IsUnrecognizedQueue(err): + return status.Error(codes.InvalidArgument, err.Error()) + default: + return err + } } func main() { @@ -233,29 +256,26 @@ func run() error { return fmt.Errorf("failed to create topic registry: %w", err) } - // Create gRPC server with a unary interceptor that translates user-input - // validation errors (anything in the chain that matches controller.ErrInvalidRequest) - // into codes.InvalidArgument so gRPC clients can distinguish bad input from - // infrastructure failures. Other errors pass through unchanged. + // Create gRPC server with a unary interceptor that translates gateway + // controller errors into stable transport status codes. grpcServer := grpc.NewServer(grpc.UnaryInterceptor( func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { resp, err := handler(ctx, req) - if err != nil && controller.IsInvalidRequest(err) { - return nil, status.Error(codes.InvalidArgument, err.Error()) + if err != nil { + return nil, gatewayStatusError(err) } - return resp, err + return resp, nil }, )) // Initialize storage from the shared app database connection. The land - // controller writes to this store directly; cancel/status use the request - // log store directly. The log consumer (registered below) is the sole - // persister of request log entries published by the orchestrator. + // controller writes to this store directly; cancel and status use its + // request stores. The log consumer registered below persists request log + // entries published by the orchestrator. store, err := mysqlstorage.NewStorage(appDB, scope.SubScope("storage")) if err != nil { return fmt.Errorf("failed to create storage: %w", err) } - requestLogStore := store.GetRequestLogStore() // Load queue configurations from YAML. Path is required so the gateway // can reject requests for unknown queues at the edge. @@ -272,7 +292,12 @@ func run() error { pingController := controller.NewPingController(logger, scope) landController := controller.NewLandController(logger.Sugar(), scope, cnt, store, queueConfigs, registry) cancelController := controller.NewCancelController(logger.Sugar(), scope, store, registry) - statusController := controller.NewStatusController(logger.Sugar(), scope, requestLogStore) + statusController := controller.NewStatusController( + logger.Sugar(), + scope, + store.GetRequestSummaryStore(), + store.GetRequestURIStore(), + ) gatewayServer := &GatewayServer{ pingController: pingController, landController: landController, diff --git a/service/submitqueue/gateway/server/mapper/BUILD.bazel b/service/submitqueue/gateway/server/mapper/BUILD.bazel index a8f79d5c..12c5ebd8 100644 --- a/service/submitqueue/gateway/server/mapper/BUILD.bazel +++ b/service/submitqueue/gateway/server/mapper/BUILD.bazel @@ -14,7 +14,6 @@ go_library( "//api/submitqueue/gateway/protopb:go_default_library", "//platform/base/change:go_default_library", "//platform/base/mergestrategy:go_default_library", - "//submitqueue/core/request:go_default_library", "//submitqueue/entity:go_default_library", ], ) @@ -33,7 +32,6 @@ go_test( "//api/submitqueue/gateway/protopb:go_default_library", "//platform/base/change:go_default_library", "//platform/base/mergestrategy:go_default_library", - "//submitqueue/core/request:go_default_library", "//submitqueue/entity:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", "@com_github_stretchr_testify//require:go_default_library", diff --git a/service/submitqueue/gateway/server/mapper/status.go b/service/submitqueue/gateway/server/mapper/status.go index d0c92593..1cd13531 100644 --- a/service/submitqueue/gateway/server/mapper/status.go +++ b/service/submitqueue/gateway/server/mapper/status.go @@ -16,23 +16,52 @@ package mapper import ( pb "github.com/uber/submitqueue/api/submitqueue/gateway/protopb" - "github.com/uber/submitqueue/submitqueue/core/request" "github.com/uber/submitqueue/submitqueue/entity" ) -// ProtoToStatusRequest maps the wire StatusRequest to the entity.StatusRequest -// the controller operates on. -func ProtoToStatusRequest(req *pb.StatusRequest) entity.StatusRequest { - return entity.StatusRequest{ +// ProtoToStatusByIDRequest maps the wire request to the entity request the controller operates on. +func ProtoToStatusByIDRequest(req *pb.StatusByIDRequest) entity.StatusByIDRequest { + return entity.StatusByIDRequest{ ID: req.GetSqid(), } } -// CurrentStateToProto maps the domain read model to the wire StatusResponse. -func CurrentStateToProto(state request.CurrentState) *pb.StatusResponse { - return &pb.StatusResponse{ - Status: string(state.Status), - LastError: state.LastError, - Metadata: state.Metadata, +// ProtoToStatusByChangeIDRequest maps the wire request to the entity request the controller operates on. +func ProtoToStatusByChangeIDRequest(req *pb.StatusByChangeIDRequest) entity.StatusByChangeIDRequest { + return entity.StatusByChangeIDRequest{ + ChangeID: req.GetChangeId(), } } + +// RequestSummaryToProto maps a domain request summary to the wire request summary. +func RequestSummaryToProto(summary entity.RequestSummary) *pb.RequestSummary { + return &pb.RequestSummary{ + Sqid: summary.RequestID, + Queue: summary.Queue, + ChangeUris: append([]string{}, summary.ChangeURIs...), + ReceivedAtMs: summary.ReceivedAtMs, + Status: string(summary.Status), + LastError: summary.LastError, + Metadata: cloneStringMap(summary.Metadata), + } +} + +// RequestSummariesToProto maps domain request summaries to wire request summaries. +func RequestSummariesToProto(summaries []entity.RequestSummary) []*pb.RequestSummary { + requests := make([]*pb.RequestSummary, 0, len(summaries)) + for _, summary := range summaries { + requests = append(requests, RequestSummaryToProto(summary)) + } + return requests +} + +func cloneStringMap(input map[string]string) map[string]string { + if input == nil { + return map[string]string{} + } + output := make(map[string]string, len(input)) + for key, value := range input { + output[key] = value + } + return output +} diff --git a/service/submitqueue/gateway/server/mapper/status_test.go b/service/submitqueue/gateway/server/mapper/status_test.go index 6c25aeef..4a2f59dd 100644 --- a/service/submitqueue/gateway/server/mapper/status_test.go +++ b/service/submitqueue/gateway/server/mapper/status_test.go @@ -19,68 +19,52 @@ import ( "github.com/stretchr/testify/assert" pb "github.com/uber/submitqueue/api/submitqueue/gateway/protopb" - "github.com/uber/submitqueue/submitqueue/core/request" "github.com/uber/submitqueue/submitqueue/entity" ) -func TestProtoToStatusRequest(t *testing.T) { - tests := []struct { - name string - req *pb.StatusRequest - expected entity.StatusRequest - }{ - { - name: "maps sqid to ID", - req: &pb.StatusRequest{Sqid: "test-queue/42"}, - expected: entity.StatusRequest{ID: "test-queue/42"}, - }, - { - name: "empty request yields zero value", - req: &pb.StatusRequest{}, - expected: entity.StatusRequest{}, - }, - } +func TestProtoToStatusByIDRequest(t *testing.T) { + assert.Equal(t, + entity.StatusByIDRequest{ID: "test-queue/42"}, + ProtoToStatusByIDRequest(&pb.StatusByIDRequest{Sqid: "test-queue/42"}), + ) +} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := ProtoToStatusRequest(tt.req) - assert.Equal(t, tt.expected, got) - }) - } +func TestProtoToStatusByChangeIDRequest(t *testing.T) { + assert.Equal(t, + entity.StatusByChangeIDRequest{ChangeID: "github://uber/repo/pull/1/abc"}, + ProtoToStatusByChangeIDRequest(&pb.StatusByChangeIDRequest{ChangeId: "github://uber/repo/pull/1/abc"}), + ) } -func TestCurrentStateToProto(t *testing.T) { - tests := []struct { - name string - state request.CurrentState - expected *pb.StatusResponse - }{ - { - name: "maps all fields", - state: request.CurrentState{ - Status: entity.RequestStatusValidating, - LastError: "validation failed", - Metadata: map[string]string{"step": "lint"}, - }, - expected: &pb.StatusResponse{ - Status: string(entity.RequestStatusValidating), - LastError: "validation failed", - Metadata: map[string]string{"step": "lint"}, - }, - }, - { - name: "zero value state maps to empty response", - state: request.CurrentState{}, - expected: &pb.StatusResponse{ - Status: "", - }, - }, +func TestRequestSummaryToProto(t *testing.T) { + summary := entity.RequestSummary{ + RequestID: "test-queue/42", + Queue: "test-queue", + ChangeURIs: []string{"github://uber/repo/pull/1/abc"}, + ReceivedAtMs: 100, + Status: entity.RequestStatusValidating, + LastError: "validation failed", + Metadata: map[string]string{"step": "lint"}, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := CurrentStateToProto(tt.state) - assert.Equal(t, tt.expected, got) - }) + assert.Equal(t, &pb.RequestSummary{ + Sqid: "test-queue/42", + Queue: "test-queue", + ChangeUris: []string{"github://uber/repo/pull/1/abc"}, + ReceivedAtMs: 100, + Status: string(entity.RequestStatusValidating), + LastError: "validation failed", + Metadata: map[string]string{"step": "lint"}, + }, RequestSummaryToProto(summary)) +} + +func TestRequestSummariesToProto(t *testing.T) { + summaries := []entity.RequestSummary{ + {RequestID: "test-queue/2", Status: entity.RequestStatusLanded}, + {RequestID: "test-queue/1", Status: entity.RequestStatusError}, } + + requests := RequestSummariesToProto(summaries) + + assert.Equal(t, []string{"test-queue/2", "test-queue/1"}, []string{requests[0].Sqid, requests[1].Sqid}) } diff --git a/submitqueue/entity/status.go b/submitqueue/entity/status.go index 3c6fe6de..476b1e5e 100644 --- a/submitqueue/entity/status.go +++ b/submitqueue/entity/status.go @@ -14,8 +14,14 @@ package entity -// StatusRequest identifies a request whose current status is being queried. -type StatusRequest struct { +// StatusByIDRequest identifies a request whose current status is being queried. +type StatusByIDRequest struct { // ID is the globally unique identifier of the request. Format: "/". ID string } + +// StatusByChangeIDRequest identifies requests by an exact pinned change URI. +type StatusByChangeIDRequest struct { + // ChangeID is the exact change URI supplied in a Land request. + ChangeID string +} diff --git a/submitqueue/gateway/controller/read_errors.go b/submitqueue/gateway/controller/read_errors.go index feaa3e3a..2caab801 100644 --- a/submitqueue/gateway/controller/read_errors.go +++ b/submitqueue/gateway/controller/read_errors.go @@ -14,11 +14,15 @@ package controller -import "fmt" +import ( + "errors" + "fmt" +) const ( maxQueueIdentifierBytes = 235 maxStorageIdentifierBytes = 255 + maxChangeRequestResults = 100 ) func validateQueueIdentifier(queue string) error { @@ -31,6 +35,59 @@ func validateQueueIdentifier(queue string) error { return nil } +// RequestNotFoundError indicates that no request exists for the selected sqid or change ID. +type RequestNotFoundError struct { + Sqid string + ChangeID string +} + +// Error implements the error interface. +func (e *RequestNotFoundError) Error() string { + if e.Sqid != "" { + return fmt.Sprintf("request not found for sqid %q", e.Sqid) + } + return fmt.Sprintf("request not found for change ID %q", e.ChangeID) +} + +// IsRequestNotFound returns true if any error in the chain is a *RequestNotFoundError. +func IsRequestNotFound(err error) bool { + var target *RequestNotFoundError + return errors.As(err, &target) +} + +// TooManyChangeRequestsError indicates that a change ID exceeded the API result limit. +type TooManyChangeRequestsError struct { + ChangeID string + Limit int +} + +// Error implements the error interface. +func (e *TooManyChangeRequestsError) Error() string { + return fmt.Sprintf("change ID %q matched more than %d requests", e.ChangeID, e.Limit) +} + +// IsTooManyChangeRequests returns true if any error in the chain is a *TooManyChangeRequestsError. +func IsTooManyChangeRequests(err error) bool { + var target *TooManyChangeRequestsError + return errors.As(err, &target) +} + +// InternalConsistencyError indicates that gateway-owned read models disagree. +type InternalConsistencyError struct { + Message string +} + +// Error implements the error interface. +func (e *InternalConsistencyError) Error() string { + return e.Message +} + +// IsInternalConsistency returns true if any error in the chain is an *InternalConsistencyError. +func IsInternalConsistency(err error) bool { + var target *InternalConsistencyError + return errors.As(err, &target) +} + func validateStoredIdentifier(name, value string) error { if value == "" { return fmt.Errorf("%s must be non-empty: %w", name, ErrInvalidRequest) diff --git a/submitqueue/gateway/controller/status.go b/submitqueue/gateway/controller/status.go index ea6c04d4..b1328fc5 100644 --- a/submitqueue/gateway/controller/status.go +++ b/submitqueue/gateway/controller/status.go @@ -16,83 +16,102 @@ package controller import ( "context" - "errors" "fmt" "time" "github.com/uber-go/tally" "github.com/uber/submitqueue/platform/errs" - "github.com/uber/submitqueue/submitqueue/core/request" "github.com/uber/submitqueue/submitqueue/entity" "github.com/uber/submitqueue/submitqueue/extension/storage" "go.uber.org/zap" ) -// RequestNotFoundError indicates that no request log records exist for the -// requested sqid. Either the sqid is wrong or the request has not been -// accepted yet. -type RequestNotFoundError struct { - Sqid string -} - -// Error implements the error interface. -func (e *RequestNotFoundError) Error() string { - return fmt.Sprintf("request not found for sqid %q", e.Sqid) -} - -// IsRequestNotFound returns true if any error in the chain is a -// *RequestNotFoundError. -func IsRequestNotFound(err error) bool { - var target *RequestNotFoundError - return errors.As(err, &target) -} - -// StatusController handles request status business logic for the gateway. +// StatusController handles materialized request status lookups for the gateway. type StatusController struct { - logger *zap.SugaredLogger - metricsScope tally.Scope - requestLogStore storage.RequestLogStore + logger *zap.SugaredLogger + metricsScope tally.Scope + requestSummaryStore storage.RequestSummaryStore + requestURIStore storage.RequestURIStore } -// NewStatusController creates a new instance of the gateway status controller. -func NewStatusController(logger *zap.SugaredLogger, scope tally.Scope, requestLogStore storage.RequestLogStore) *StatusController { +// NewStatusController creates a gateway status controller. +func NewStatusController(logger *zap.SugaredLogger, scope tally.Scope, requestSummaryStore storage.RequestSummaryStore, requestURIStore storage.RequestURIStore) *StatusController { return &StatusController{ - logger: logger, - metricsScope: scope, - requestLogStore: requestLogStore, + logger: logger, + metricsScope: scope.SubScope("status_controller"), + requestSummaryStore: requestSummaryStore, + requestURIStore: requestURIStore, } } -// StatusResult is the outcome of a status query. It is a type alias for -// request.CurrentState so that the controller's return type follows the -// same Result naming convention as LandResult and other controller outputs. -type StatusResult = request.CurrentState - -// Status returns the current reconciled status of a request identified by its sqid. -func (c *StatusController) Status(ctx context.Context, req entity.StatusRequest) (StatusResult, error) { +// StatusByID returns the current materialized view of one request. +func (c *StatusController) StatusByID(ctx context.Context, req entity.StatusByIDRequest) (entity.RequestSummary, error) { start := time.Now() defer func() { - c.metricsScope.Timer("status_latency").Record(time.Since(start)) + c.metricsScope.Timer("by_id_latency").Record(time.Since(start)) }() + c.metricsScope.Counter("by_id_count").Inc(1) - c.metricsScope.Counter("status_count").Inc(1) - - if req.ID == "" { - return StatusResult{}, fmt.Errorf("StatusController requires the request to have a sqid specified: %w", ErrInvalidRequest) + if err := validateStoredIdentifier("sqid", req.ID); err != nil { + return entity.RequestSummary{}, fmt.Errorf("StatusByID invalid request: %w", err) } - state, err := request.GetCurrentStateFromRequestLog(ctx, c.requestLogStore, req.ID) + summary, err := c.requestSummaryStore.Get(ctx, req.ID) if err != nil { if storage.IsNotFound(err) { - return StatusResult{}, errs.NewUserError(&RequestNotFoundError{Sqid: req.ID}) + return entity.RequestSummary{}, errs.NewUserError(&RequestNotFoundError{Sqid: req.ID}) } - return StatusResult{}, fmt.Errorf("StatusController failed to get current state for sqid=%s: %w", req.ID, err) + return entity.RequestSummary{}, fmt.Errorf("StatusByID failed to get request summary sqid=%s: %w", req.ID, err) + } + if summary.Status == entity.RequestStatusAccepting { + return entity.RequestSummary{}, errs.NewUserError(&RequestNotFoundError{Sqid: req.ID}) } c.logger.Debugw("request status retrieved", "sqid", req.ID, - "status", string(state.Status), + "status", string(summary.Status), ) + return summary, nil +} - return state, nil +// StatusByChangeID returns current materialized views for an exact pinned change URI. +func (c *StatusController) StatusByChangeID(ctx context.Context, req entity.StatusByChangeIDRequest) ([]entity.RequestSummary, error) { + start := time.Now() + defer func() { + c.metricsScope.Timer("by_change_id_latency").Record(time.Since(start)) + }() + c.metricsScope.Counter("by_change_id_count").Inc(1) + + if err := validateStoredIdentifier("change ID", req.ChangeID); err != nil { + return nil, fmt.Errorf("StatusByChangeID invalid request: %w", err) + } + + mappings, err := c.requestURIStore.ListByURI(ctx, req.ChangeID, maxChangeRequestResults+1) + if err != nil { + return nil, fmt.Errorf("StatusByChangeID failed to list request mappings change_id=%s: %w", req.ChangeID, err) + } + if len(mappings) == 0 { + return nil, errs.NewUserError(&RequestNotFoundError{ChangeID: req.ChangeID}) + } + if len(mappings) > maxChangeRequestResults { + return nil, errs.NewUserError(&TooManyChangeRequestsError{ChangeID: req.ChangeID, Limit: maxChangeRequestResults}) + } + + requests := make([]entity.RequestSummary, 0, len(mappings)) + for _, mapping := range mappings { + summary, err := c.requestSummaryStore.Get(ctx, mapping.RequestID) + if err != nil { + if storage.IsNotFound(err) { + return nil, &InternalConsistencyError{Message: fmt.Sprintf("request summary missing for mapped change ID %q and sqid %q", req.ChangeID, mapping.RequestID)} + } + return nil, fmt.Errorf("StatusByChangeID failed to get request summary change_id=%s sqid=%s: %w", req.ChangeID, mapping.RequestID, err) + } + requests = append(requests, summary) + } + + c.logger.Debugw("request statuses retrieved", + "change_id", req.ChangeID, + "request_count", len(requests), + ) + return requests, nil } diff --git a/submitqueue/gateway/controller/status_test.go b/submitqueue/gateway/controller/status_test.go index 3791f059..74b83f3f 100644 --- a/submitqueue/gateway/controller/status_test.go +++ b/submitqueue/gateway/controller/status_test.go @@ -30,83 +30,177 @@ import ( "go.uber.org/zap" ) -func TestStatus_ReturnsCurrentState(t *testing.T) { +func TestStatusByID(t *testing.T) { ctrl := gomock.NewController(t) - - store := storagemock.NewMockRequestLogStore(ctrl) - store.EXPECT().List(gomock.Any(), "test-queue/1").Return([]entity.RequestLog{ - {RequestID: "test-queue/1", TimestampMs: 100, Status: entity.RequestStatusAccepted}, - {RequestID: "test-queue/1", TimestampMs: 200, Status: entity.RequestStatusValidating, LastError: "boom", Metadata: map[string]string{"k": "v"}}, + summaryStore := storagemock.NewMockRequestSummaryStore(ctrl) + uriStore := storagemock.NewMockRequestURIStore(ctrl) + summaryStore.EXPECT().Get(gomock.Any(), "test-queue/1").Return(entity.RequestSummary{ + RequestID: "test-queue/1", + Queue: "test-queue", + ChangeURIs: []string{"github://uber/repo/pull/1/abc"}, + ReceivedAtMs: 100, + Status: entity.RequestStatusValidating, + LastError: "boom", + Metadata: map[string]string{"k": "v"}, }, nil) - controller := NewStatusController(zap.NewNop().Sugar(), tally.NoopScope, store) + controller := NewStatusController(zap.NewNop().Sugar(), tally.NoopScope, summaryStore, uriStore) + summary, err := controller.StatusByID(context.Background(), entity.StatusByIDRequest{ID: "test-queue/1"}) - state, err := controller.Status(context.Background(), entity.StatusRequest{ID: "test-queue/1"}) + require.NoError(t, err) + assert.Equal(t, "test-queue/1", summary.RequestID) + assert.Equal(t, "test-queue", summary.Queue) + assert.Equal(t, []string{"github://uber/repo/pull/1/abc"}, summary.ChangeURIs) + assert.Equal(t, int64(100), summary.ReceivedAtMs) + assert.Equal(t, entity.RequestStatusValidating, summary.Status) + assert.Equal(t, "boom", summary.LastError) + assert.Equal(t, map[string]string{"k": "v"}, summary.Metadata) +} + +func TestStatusByChangeID(t *testing.T) { + ctrl := gomock.NewController(t) + summaryStore := storagemock.NewMockRequestSummaryStore(ctrl) + uriStore := storagemock.NewMockRequestURIStore(ctrl) + uriStore.EXPECT().ListByURI(gomock.Any(), "uri", 101).Return([]entity.RequestURI{ + {ChangeURI: "uri", ReceivedAtMs: 200, RequestID: "queue/2"}, + {ChangeURI: "uri", ReceivedAtMs: 100, RequestID: "queue/1"}, + }, nil) + summaryStore.EXPECT().Get(gomock.Any(), "queue/2").Return(entity.RequestSummary{RequestID: "queue/2", ReceivedAtMs: 200, Status: entity.RequestStatusLanded, ChangeURIs: []string{}}, nil) + summaryStore.EXPECT().Get(gomock.Any(), "queue/1").Return(entity.RequestSummary{RequestID: "queue/1", ReceivedAtMs: 100, Status: entity.RequestStatusError, ChangeURIs: []string{}}, nil) + + controller := NewStatusController(zap.NewNop().Sugar(), tally.NoopScope, summaryStore, uriStore) + summaries, err := controller.StatusByChangeID(context.Background(), entity.StatusByChangeIDRequest{ChangeID: "uri"}) require.NoError(t, err) - assert.Equal(t, entity.RequestStatusValidating, state.Status) - assert.Equal(t, "boom", state.LastError) - assert.Equal(t, map[string]string{"k": "v"}, state.Metadata) + require.Len(t, summaries, 2) + assert.Equal(t, []string{"queue/2", "queue/1"}, []string{summaries[0].RequestID, summaries[1].RequestID}) } -func TestStatus_Errors(t *testing.T) { +func TestStatusErrors(t *testing.T) { + backendErr := fmt.Errorf("backend down") tests := []struct { - name string - sqid string - setupStore func(*storagemock.MockRequestLogStore) - wantInvalid bool - wantNotFound bool - wantUserError bool + name string + call func(*StatusController) error + setup func(*storagemock.MockRequestSummaryStore, *storagemock.MockRequestURIStore) + wantInvalid bool + wantNotFound bool + wantTooMany bool + wantInternal bool + wantUser bool }{ { - name: "empty sqid is an invalid request", - sqid: "", - wantInvalid: true, - wantUserError: true, + name: "empty sqid", + call: func(c *StatusController) error { + _, err := c.StatusByID(context.Background(), entity.StatusByIDRequest{}) + return err + }, + wantInvalid: true, + wantUser: true, + }, + { + name: "empty change ID", + call: func(c *StatusController) error { + _, err := c.StatusByChangeID(context.Background(), entity.StatusByChangeIDRequest{}) + return err + }, + wantInvalid: true, + wantUser: true, + }, + { + name: "sqid not found", + setup: func(summaryStore *storagemock.MockRequestSummaryStore, _ *storagemock.MockRequestURIStore) { + summaryStore.EXPECT().Get(gomock.Any(), "missing/1").Return(entity.RequestSummary{}, storage.ErrNotFound) + }, + call: func(c *StatusController) error { + _, err := c.StatusByID(context.Background(), entity.StatusByIDRequest{ID: "missing/1"}) + return err + }, + wantNotFound: true, + wantUser: true, + }, + { + name: "sqid still accepting", + setup: func(summaryStore *storagemock.MockRequestSummaryStore, _ *storagemock.MockRequestURIStore) { + summaryStore.EXPECT().Get(gomock.Any(), "queue/1").Return(entity.RequestSummary{ + RequestID: "queue/1", + Status: entity.RequestStatusAccepting, + }, nil) + }, + call: func(c *StatusController) error { + _, err := c.StatusByID(context.Background(), entity.StatusByIDRequest{ID: "queue/1"}) + return err + }, + wantNotFound: true, + wantUser: true, }, { - name: "unknown sqid maps to not found", - sqid: "missing/1", - setupStore: func(s *storagemock.MockRequestLogStore) { - s.EXPECT().List(gomock.Any(), "missing/1").Return(nil, storage.ErrNotFound) + name: "sqid storage failure", + setup: func(summaryStore *storagemock.MockRequestSummaryStore, _ *storagemock.MockRequestURIStore) { + summaryStore.EXPECT().Get(gomock.Any(), "queue/1").Return(entity.RequestSummary{}, backendErr) + }, + call: func(c *StatusController) error { + _, err := c.StatusByID(context.Background(), entity.StatusByIDRequest{ID: "queue/1"}) + return err }, - wantNotFound: true, - wantUserError: true, }, { - name: "store failure propagates as infra error", - sqid: "test-queue/1", - setupStore: func(s *storagemock.MockRequestLogStore) { - s.EXPECT().List(gomock.Any(), "test-queue/1").Return(nil, fmt.Errorf("log backend down")) + name: "change ID not found", + setup: func(_ *storagemock.MockRequestSummaryStore, uriStore *storagemock.MockRequestURIStore) { + uriStore.EXPECT().ListByURI(gomock.Any(), "uri", 101).Return([]entity.RequestURI{}, nil) }, + call: func(c *StatusController) error { + _, err := c.StatusByChangeID(context.Background(), entity.StatusByChangeIDRequest{ChangeID: "uri"}) + return err + }, + wantNotFound: true, + wantUser: true, + }, + { + name: "too many change matches", + setup: func(_ *storagemock.MockRequestSummaryStore, uriStore *storagemock.MockRequestURIStore) { + uriStore.EXPECT().ListByURI(gomock.Any(), "uri", 101).Return(make([]entity.RequestURI, 101), nil) + }, + call: func(c *StatusController) error { + _, err := c.StatusByChangeID(context.Background(), entity.StatusByChangeIDRequest{ChangeID: "uri"}) + return err + }, + wantTooMany: true, + wantUser: true, + }, + { + name: "mapped summary missing", + setup: func(summaryStore *storagemock.MockRequestSummaryStore, uriStore *storagemock.MockRequestURIStore) { + uriStore.EXPECT().ListByURI(gomock.Any(), "uri", 101).Return([]entity.RequestURI{{RequestID: "missing/1"}}, nil) + summaryStore.EXPECT().Get(gomock.Any(), "missing/1").Return(entity.RequestSummary{}, storage.ErrNotFound) + }, + call: func(c *StatusController) error { + _, err := c.StatusByChangeID(context.Background(), entity.StatusByChangeIDRequest{ChangeID: "uri"}) + return err + }, + wantInternal: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctrl := gomock.NewController(t) - store := storagemock.NewMockRequestLogStore(ctrl) - if tt.setupStore != nil { - tt.setupStore(store) + summaryStore := storagemock.NewMockRequestSummaryStore(ctrl) + uriStore := storagemock.NewMockRequestURIStore(ctrl) + if tt.setup != nil { + tt.setup(summaryStore, uriStore) } + controller := NewStatusController(zap.NewNop().Sugar(), tally.NoopScope, summaryStore, uriStore) - controller := NewStatusController(zap.NewNop().Sugar(), tally.NoopScope, store) - - _, err := controller.Status(context.Background(), entity.StatusRequest{ID: tt.sqid}) + err := tt.call(controller) require.Error(t, err) - assert.Equal(t, tt.wantNotFound, IsRequestNotFound(err)) - assert.Equal(t, tt.wantUserError, errs.IsUserError(err)) - assert.False(t, errs.IsRetryable(err)) if tt.wantInvalid { assert.True(t, IsInvalidRequest(err)) } - - if tt.wantNotFound { - var typed *RequestNotFoundError - require.ErrorAs(t, err, &typed) - assert.Equal(t, tt.sqid, typed.Sqid) - } + assert.Equal(t, tt.wantNotFound, IsRequestNotFound(err)) + assert.Equal(t, tt.wantTooMany, IsTooManyChangeRequests(err)) + assert.Equal(t, tt.wantInternal, IsInternalConsistency(err)) + assert.Equal(t, tt.wantUser, errs.IsUserError(err)) }) } } diff --git a/test/e2e/submitqueue/harness_test.go b/test/e2e/submitqueue/harness_test.go index b8d18eef..5dc3b32c 100644 --- a/test/e2e/submitqueue/harness_test.go +++ b/test/e2e/submitqueue/harness_test.go @@ -15,10 +15,10 @@ package e2e_test // Reusable e2e helpers so tests read as intent, not plumbing. They drive the -// stack through the real gateway gRPC surface (Land / Cancel / Status) and +// stack through the real gateway gRPC surface (Land / Cancel / StatusByID) and // observe outcomes two ways: // -// - black-box, by polling the Status RPC to a target/terminal status; and +// - black-box, by polling the StatusByID RPC to a target/terminal status; and // - white-box, by reading the request_log timeline (RequestLogStore.List on // mysql-app) to assert the ordered stage progression. // @@ -28,6 +28,8 @@ package e2e_test // stage is genuinely stuck, not a timing race. import ( + "fmt" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" changepb "github.com/uber/submitqueue/api/base/change/protopb" @@ -51,32 +53,35 @@ func (s *E2EIntegrationSuite) land(queue string, uris ...string) string { return resp.Sqid } -// currentStatus reads the request's current customer-facing status via the -// Status RPC. A transport error is returned so callers can keep polling. +// currentStatus reads the request's current customer-facing status via +// StatusByID. A transport error is returned so callers can keep polling. func (s *E2EIntegrationSuite) currentStatus(sqid string) (entity.RequestStatus, error) { - resp, err := s.gatewayClient.Status(s.ctx, &gatewaypb.StatusRequest{Sqid: sqid}) + resp, err := s.gatewayClient.StatusByID(s.ctx, &gatewaypb.StatusByIDRequest{Sqid: sqid}) if err != nil { return entity.RequestStatusUnknown, err } - return entity.RequestStatus(resp.Status), nil + if resp.Request == nil { + return entity.RequestStatusUnknown, fmt.Errorf("StatusByID(%s) returned no request", sqid) + } + return entity.RequestStatus(resp.Request.Status), nil } -// awaitStatus polls Status until the request reaches exactly want. +// awaitStatus polls StatusByID until the request reaches exactly want. func (s *E2EIntegrationSuite) awaitStatus(sqid string, want entity.RequestStatus) { t := s.T() require.Eventually(t, func() bool { got, err := s.currentStatus(sqid) if err != nil { - s.log.Logf("Status(%s) not ready yet: %v", sqid, err) + s.log.Logf("StatusByID(%s) not ready yet: %v", sqid, err) return false } - s.log.Logf("Status(%s) = %q (want %q)", sqid, got, want) + s.log.Logf("StatusByID(%s) = %q (want %q)", sqid, got, want) return got == want }, persistTimeout, persistPollInterval, "request %s should reach status %q", sqid, want) } -// awaitTerminal polls Status until the request reaches a terminal status +// awaitTerminal polls StatusByID until the request reaches a terminal status // (landed, error, or cancelled) and returns it. func (s *E2EIntegrationSuite) awaitTerminal(sqid string) entity.RequestStatus { t := s.T() @@ -84,19 +89,17 @@ func (s *E2EIntegrationSuite) awaitTerminal(sqid string) entity.RequestStatus { require.Eventually(t, func() bool { got, err := s.currentStatus(sqid) if err != nil { - s.log.Logf("Status(%s) not ready yet: %v", sqid, err) + s.log.Logf("StatusByID(%s) not ready yet: %v", sqid, err) return false } last = got - s.log.Logf("Status(%s) = %q (awaiting terminal)", sqid, got) + s.log.Logf("StatusByID(%s) = %q (awaiting terminal)", sqid, got) return isTerminalStatus(got) }, persistTimeout, persistPollInterval, "request %s should reach a terminal status", sqid) return last } -// timeline returns the ordered status history from the request_log (the audit -// trail persisted by the gateway log consumer on mysql-app). // timeline returns the ordered status history from the request_log (the audit // trail persisted by the gateway log consumer on mysql-app). These are the // customer-facing RequestStatus values — the only ordered history in the system @@ -141,13 +144,13 @@ func (s *E2EIntegrationSuite) terminalState(sqid string) entity.RequestState { return req.State } -// lastError returns the LastError reported by the Status RPC (populated on the -// error path). +// lastError returns the LastError reported by StatusByID. func (s *E2EIntegrationSuite) lastError(sqid string) string { t := s.T() - resp, err := s.gatewayClient.Status(s.ctx, &gatewaypb.StatusRequest{Sqid: sqid}) - require.NoError(t, err, "Status failed for %s", sqid) - return resp.LastError + resp, err := s.gatewayClient.StatusByID(s.ctx, &gatewaypb.StatusByIDRequest{Sqid: sqid}) + require.NoError(t, err, "StatusByID failed for %s", sqid) + require.NotNil(t, resp.Request) + return resp.Request.LastError } // isTerminalStatus reports whether a customer-facing status is terminal. diff --git a/test/integration/submitqueue/gateway/BUILD.bazel b/test/integration/submitqueue/gateway/BUILD.bazel index 36fda2d6..34fa0ad0 100644 --- a/test/integration/submitqueue/gateway/BUILD.bazel +++ b/test/integration/submitqueue/gateway/BUILD.bazel @@ -31,6 +31,8 @@ go_test( "@com_github_stretchr_testify//suite:go_default_library", "@com_github_uber_go_tally//:go_default_library", "@org_golang_google_grpc//:go_default_library", + "@org_golang_google_grpc//codes:go_default_library", + "@org_golang_google_grpc//status:go_default_library", "@org_uber_go_zap//:go_default_library", ], ) diff --git a/test/integration/submitqueue/gateway/suite_test.go b/test/integration/submitqueue/gateway/suite_test.go index 12e17296..35349954 100644 --- a/test/integration/submitqueue/gateway/suite_test.go +++ b/test/integration/submitqueue/gateway/suite_test.go @@ -28,6 +28,7 @@ package gateway import ( "context" "database/sql" + "fmt" "path/filepath" "testing" "time" @@ -48,6 +49,8 @@ import ( "github.com/uber/submitqueue/test/testutil" "go.uber.org/zap" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type GatewayIntegrationSuite struct { @@ -167,6 +170,40 @@ func (s *GatewayIntegrationSuite) TestLandAPI() { s.log.Logf("Land API test passed: request stored and message published") } +// TestStatusAPIErrorCodes verifies Status controller errors reach stable gRPC codes. +func (s *GatewayIntegrationSuite) TestStatusAPIErrorCodes() { + t := s.T() + + _, err := s.client.StatusByID(s.ctx, &pb.StatusByIDRequest{Sqid: "missing/1"}) + require.Error(t, err) + assert.Equal(t, codes.NotFound, status.Code(err)) + + store, err := mysqlstorage.NewStorage(s.db, tally.NoopScope) + require.NoError(t, err) + const overflowChangeID = "uri/read-api-overflow" + for i := 1; i <= 101; i++ { + require.NoError(t, store.GetRequestURIStore().Create(s.ctx, entity.RequestURI{ + ChangeURI: overflowChangeID, + ReceivedAtMs: int64(i), + RequestID: fmt.Sprintf("overflow/%d", i), + })) + } + + _, err = s.client.StatusByChangeID(s.ctx, &pb.StatusByChangeIDRequest{ChangeId: overflowChangeID}) + require.Error(t, err) + assert.Equal(t, codes.ResourceExhausted, status.Code(err)) + + const inconsistentChangeID = "uri/read-api-inconsistent" + require.NoError(t, store.GetRequestURIStore().Create(s.ctx, entity.RequestURI{ + ChangeURI: inconsistentChangeID, + ReceivedAtMs: 1, + RequestID: "missing-summary/1", + })) + _, err = s.client.StatusByChangeID(s.ctx, &pb.StatusByChangeIDRequest{ChangeId: inconsistentChangeID}) + require.Error(t, err) + assert.Equal(t, codes.Internal, status.Code(err)) +} + // TestRequestLogConsumer verifies the gateway's log-topic consumer in isolation: // no orchestrator runs in this stack, so the test itself publishes a request log // entry to the log topic exactly as the orchestrator does in production (via @@ -207,11 +244,11 @@ func (s *GatewayIntegrationSuite) TestRequestLogConsumer() { s.log.Logf("Published 'started' log for sqid=%s; waiting for gateway consumer to persist it", sqid) require.Eventually(t, func() bool { - resp, statusErr := s.client.Status(s.ctx, &pb.StatusRequest{Sqid: sqid}) + resp, statusErr := s.client.StatusByID(s.ctx, &pb.StatusByIDRequest{Sqid: sqid}) if statusErr != nil { return false } - return resp.Status == string(entity.RequestStatusStarted) + return resp.Request != nil && resp.Request.Status == string(entity.RequestStatusStarted) }, persistTimeout, persistPollInterval, "gateway log consumer should persist the published request log for sqid=%s", sqid)