From d814bd69c547418294ba684d49900264189a48fe Mon Sep 17 00:00:00 2001 From: Albert Wu Date: Mon, 13 Jul 2026 15:08:09 -0700 Subject: [PATCH] feat(gateway): add request history lookups --- api/submitqueue/gateway/proto/gateway.proto | 49 ++ api/submitqueue/gateway/protopb/gateway.pb.go | 434 ++++++++++++++++-- .../gateway/protopb/gateway.pb.yarpc.go | 226 ++++++--- .../gateway/protopb/gateway_grpc.pb.go | 80 ++++ service/README.md | 10 +- service/submitqueue/gateway/server/main.go | 36 +- .../gateway/server/mapper/BUILD.bazel | 2 + .../gateway/server/mapper/request_history.go | 56 +++ .../server/mapper/request_history_test.go | 60 +++ submitqueue/entity/BUILD.bazel | 1 + submitqueue/entity/request_history.go | 35 ++ submitqueue/gateway/README.md | 35 +- submitqueue/gateway/controller/BUILD.bazel | 2 + .../gateway/controller/request_history.go | 152 ++++++ .../controller/request_history_test.go | 209 +++++++++ test/e2e/submitqueue/harness_test.go | 23 +- test/e2e/submitqueue/suite_test.go | 119 ++++- .../submitqueue/gateway/suite_test.go | 4 + 18 files changed, 1371 insertions(+), 162 deletions(-) create mode 100644 service/submitqueue/gateway/server/mapper/request_history.go create mode 100644 service/submitqueue/gateway/server/mapper/request_history_test.go create mode 100644 submitqueue/entity/request_history.go create mode 100644 submitqueue/gateway/controller/request_history.go create mode 100644 submitqueue/gateway/controller/request_history_test.go diff --git a/api/submitqueue/gateway/proto/gateway.proto b/api/submitqueue/gateway/proto/gateway.proto index 40793059..ad751602 100644 --- a/api/submitqueue/gateway/proto/gateway.proto +++ b/api/submitqueue/gateway/proto/gateway.proto @@ -146,6 +146,50 @@ message ListResponse { string next_page_token = 2; } +// GetRequestHistoryByIDRequest selects one retained request history by sqid. +message GetRequestHistoryByIDRequest { + // Globally unique identifier for the request. + string sqid = 1; +} + +// HistoryEvent is one retained append-only request-log event. +message HistoryEvent { + // Time the request-log entry was created, in Unix milliseconds. + int64 timestamp_ms = 1; + // Customer-friendly request status recorded by the event. + string status = 2; + // Error message associated with the event. Empty when absent. + string last_error = 3; + // Display and debugging metadata associated with this event. Each lifecycle event carries its own values. + map metadata = 4; +} + +// GetRequestHistoryByIDResponse contains all retained events for one request. +message GetRequestHistoryByIDResponse { + // Events ordered by timestamp ascending with a stable storage tie-breaker. + repeated HistoryEvent events = 1; +} + +// GetRequestHistoryByChangeURIRequest selects retained histories by an exact pinned change URI. +message GetRequestHistoryByChangeURIRequest { + // Exact change URI supplied in a Land request. + string change_uri = 1; +} + +// RequestHistory groups retained events for one request. +message RequestHistory { + // Globally unique request identifier. + string sqid = 1; + // Retained events ordered chronologically. + repeated HistoryEvent events = 2; +} + +// GetRequestHistoryByChangeURIResponse contains retained histories ordered by numeric sqid counter ascending. +message GetRequestHistoryByChangeURIResponse { + // Matching request histories. + repeated RequestHistory histories = 1; +} + // *************** // Error messages, returned as `google.rpc.Status` messages. // *************** @@ -209,4 +253,9 @@ service SubmitQueueGateway { // List returns requests received for one queue during a bounded receipt-time range. rpc List(ListRequest) returns (ListResponse) {} + // GetRequestHistoryByID returns every retained request-log event for one request. + rpc GetRequestHistoryByID(GetRequestHistoryByIDRequest) returns (GetRequestHistoryByIDResponse) {} + + // GetRequestHistoryByChangeURI returns retained request histories for an exact pinned change URI. + rpc GetRequestHistoryByChangeURI(GetRequestHistoryByChangeURIRequest) returns (GetRequestHistoryByChangeURIResponse) {} } diff --git a/api/submitqueue/gateway/protopb/gateway.pb.go b/api/submitqueue/gateway/protopb/gateway.pb.go index 603afd66..9988e24b 100644 --- a/api/submitqueue/gateway/protopb/gateway.pb.go +++ b/api/submitqueue/gateway/protopb/gateway.pb.go @@ -788,6 +788,318 @@ func (x *ListResponse) GetNextPageToken() string { return "" } +// GetRequestHistoryByIDRequest selects one retained request history by sqid. +type GetRequestHistoryByIDRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // 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 *GetRequestHistoryByIDRequest) Reset() { + *x = GetRequestHistoryByIDRequest{} + mi := &file_gateway_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRequestHistoryByIDRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRequestHistoryByIDRequest) ProtoMessage() {} + +func (x *GetRequestHistoryByIDRequest) ProtoReflect() protoreflect.Message { + mi := &file_gateway_proto_msgTypes[13] + 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 GetRequestHistoryByIDRequest.ProtoReflect.Descriptor instead. +func (*GetRequestHistoryByIDRequest) Descriptor() ([]byte, []int) { + return file_gateway_proto_rawDescGZIP(), []int{13} +} + +func (x *GetRequestHistoryByIDRequest) GetSqid() string { + if x != nil { + return x.Sqid + } + return "" +} + +// HistoryEvent is one retained append-only request-log event. +type HistoryEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Time the request-log entry was created, in Unix milliseconds. + TimestampMs int64 `protobuf:"varint,1,opt,name=timestamp_ms,json=timestampMs,proto3" json:"timestamp_ms,omitempty"` + // Customer-friendly request status recorded by the event. + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + // Error message associated with the event. Empty when absent. + LastError string `protobuf:"bytes,3,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` + // Display and debugging metadata associated with this event. Each lifecycle event carries its own values. + Metadata map[string]string `protobuf:"bytes,4,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 *HistoryEvent) Reset() { + *x = HistoryEvent{} + mi := &file_gateway_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HistoryEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HistoryEvent) ProtoMessage() {} + +func (x *HistoryEvent) ProtoReflect() protoreflect.Message { + mi := &file_gateway_proto_msgTypes[14] + 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 HistoryEvent.ProtoReflect.Descriptor instead. +func (*HistoryEvent) Descriptor() ([]byte, []int) { + return file_gateway_proto_rawDescGZIP(), []int{14} +} + +func (x *HistoryEvent) GetTimestampMs() int64 { + if x != nil { + return x.TimestampMs + } + return 0 +} + +func (x *HistoryEvent) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *HistoryEvent) GetLastError() string { + if x != nil { + return x.LastError + } + return "" +} + +func (x *HistoryEvent) GetMetadata() map[string]string { + if x != nil { + return x.Metadata + } + return nil +} + +// GetRequestHistoryByIDResponse contains all retained events for one request. +type GetRequestHistoryByIDResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Events ordered by timestamp ascending with a stable storage tie-breaker. + Events []*HistoryEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRequestHistoryByIDResponse) Reset() { + *x = GetRequestHistoryByIDResponse{} + mi := &file_gateway_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRequestHistoryByIDResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRequestHistoryByIDResponse) ProtoMessage() {} + +func (x *GetRequestHistoryByIDResponse) ProtoReflect() protoreflect.Message { + mi := &file_gateway_proto_msgTypes[15] + 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 GetRequestHistoryByIDResponse.ProtoReflect.Descriptor instead. +func (*GetRequestHistoryByIDResponse) Descriptor() ([]byte, []int) { + return file_gateway_proto_rawDescGZIP(), []int{15} +} + +func (x *GetRequestHistoryByIDResponse) GetEvents() []*HistoryEvent { + if x != nil { + return x.Events + } + return nil +} + +// GetRequestHistoryByChangeURIRequest selects retained histories by an exact pinned change URI. +type GetRequestHistoryByChangeURIRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Exact change URI supplied in a Land request. + ChangeUri string `protobuf:"bytes,1,opt,name=change_uri,json=changeUri,proto3" json:"change_uri,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRequestHistoryByChangeURIRequest) Reset() { + *x = GetRequestHistoryByChangeURIRequest{} + mi := &file_gateway_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRequestHistoryByChangeURIRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRequestHistoryByChangeURIRequest) ProtoMessage() {} + +func (x *GetRequestHistoryByChangeURIRequest) ProtoReflect() protoreflect.Message { + mi := &file_gateway_proto_msgTypes[16] + 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 GetRequestHistoryByChangeURIRequest.ProtoReflect.Descriptor instead. +func (*GetRequestHistoryByChangeURIRequest) Descriptor() ([]byte, []int) { + return file_gateway_proto_rawDescGZIP(), []int{16} +} + +func (x *GetRequestHistoryByChangeURIRequest) GetChangeUri() string { + if x != nil { + return x.ChangeUri + } + return "" +} + +// RequestHistory groups retained events for one request. +type RequestHistory struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Globally unique request identifier. + Sqid string `protobuf:"bytes,1,opt,name=sqid,proto3" json:"sqid,omitempty"` + // Retained events ordered chronologically. + Events []*HistoryEvent `protobuf:"bytes,2,rep,name=events,proto3" json:"events,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RequestHistory) Reset() { + *x = RequestHistory{} + mi := &file_gateway_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RequestHistory) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestHistory) ProtoMessage() {} + +func (x *RequestHistory) ProtoReflect() protoreflect.Message { + mi := &file_gateway_proto_msgTypes[17] + 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 RequestHistory.ProtoReflect.Descriptor instead. +func (*RequestHistory) Descriptor() ([]byte, []int) { + return file_gateway_proto_rawDescGZIP(), []int{17} +} + +func (x *RequestHistory) GetSqid() string { + if x != nil { + return x.Sqid + } + return "" +} + +func (x *RequestHistory) GetEvents() []*HistoryEvent { + if x != nil { + return x.Events + } + return nil +} + +// GetRequestHistoryByChangeURIResponse contains retained histories ordered by numeric sqid counter ascending. +type GetRequestHistoryByChangeURIResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Matching request histories. + Histories []*RequestHistory `protobuf:"bytes,1,rep,name=histories,proto3" json:"histories,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRequestHistoryByChangeURIResponse) Reset() { + *x = GetRequestHistoryByChangeURIResponse{} + mi := &file_gateway_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRequestHistoryByChangeURIResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRequestHistoryByChangeURIResponse) ProtoMessage() {} + +func (x *GetRequestHistoryByChangeURIResponse) ProtoReflect() protoreflect.Message { + mi := &file_gateway_proto_msgTypes[18] + 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 GetRequestHistoryByChangeURIResponse.ProtoReflect.Descriptor instead. +func (*GetRequestHistoryByChangeURIResponse) Descriptor() ([]byte, []int) { + return file_gateway_proto_rawDescGZIP(), []int{18} +} + +func (x *GetRequestHistoryByChangeURIResponse) GetHistories() []*RequestHistory { + if x != nil { + return x.Histories + } + return nil +} + // Generic error with metadata. Each custom error type should extend this message. type Error struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -799,7 +1111,7 @@ type Error struct { func (x *Error) Reset() { *x = Error{} - mi := &file_gateway_proto_msgTypes[13] + mi := &file_gateway_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -811,7 +1123,7 @@ func (x *Error) String() string { func (*Error) ProtoMessage() {} func (x *Error) ProtoReflect() protoreflect.Message { - mi := &file_gateway_proto_msgTypes[13] + mi := &file_gateway_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -824,7 +1136,7 @@ func (x *Error) ProtoReflect() protoreflect.Message { // Deprecated: Use Error.ProtoReflect.Descriptor instead. func (*Error) Descriptor() ([]byte, []int) { - return file_gateway_proto_rawDescGZIP(), []int{13} + return file_gateway_proto_rawDescGZIP(), []int{19} } func (x *Error) GetMessage() string { @@ -847,7 +1159,7 @@ type UnrecognizedQueueError struct { func (x *UnrecognizedQueueError) Reset() { *x = UnrecognizedQueueError{} - mi := &file_gateway_proto_msgTypes[14] + mi := &file_gateway_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -859,7 +1171,7 @@ func (x *UnrecognizedQueueError) String() string { func (*UnrecognizedQueueError) ProtoMessage() {} func (x *UnrecognizedQueueError) ProtoReflect() protoreflect.Message { - mi := &file_gateway_proto_msgTypes[14] + mi := &file_gateway_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -872,7 +1184,7 @@ func (x *UnrecognizedQueueError) ProtoReflect() protoreflect.Message { // Deprecated: Use UnrecognizedQueueError.ProtoReflect.Descriptor instead. func (*UnrecognizedQueueError) Descriptor() ([]byte, []int) { - return file_gateway_proto_rawDescGZIP(), []int{14} + return file_gateway_proto_rawDescGZIP(), []int{20} } func (x *UnrecognizedQueueError) GetError() *Error { @@ -904,7 +1216,7 @@ type RequestNotFoundError struct { func (x *RequestNotFoundError) Reset() { *x = RequestNotFoundError{} - mi := &file_gateway_proto_msgTypes[15] + mi := &file_gateway_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -916,7 +1228,7 @@ func (x *RequestNotFoundError) String() string { func (*RequestNotFoundError) ProtoMessage() {} func (x *RequestNotFoundError) ProtoReflect() protoreflect.Message { - mi := &file_gateway_proto_msgTypes[15] + mi := &file_gateway_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -929,7 +1241,7 @@ func (x *RequestNotFoundError) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestNotFoundError.ProtoReflect.Descriptor instead. func (*RequestNotFoundError) Descriptor() ([]byte, []int) { - return file_gateway_proto_rawDescGZIP(), []int{15} + return file_gateway_proto_rawDescGZIP(), []int{21} } func (x *RequestNotFoundError) GetError() *Error { @@ -1006,7 +1318,28 @@ const file_gateway_proto_rawDesc = "" + "page_token\x18\x05 \x01(\tR\tpageToken\"|\n" + "\fListResponse\x12D\n" + "\brequests\x18\x01 \x03(\v2(.uber.submitqueue.gateway.RequestSummaryR\brequests\x12&\n" + - "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"!\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"2\n" + + "\x1cGetRequestHistoryByIDRequest\x12\x12\n" + + "\x04sqid\x18\x01 \x01(\tR\x04sqid\"\xf7\x01\n" + + "\fHistoryEvent\x12!\n" + + "\ftimestamp_ms\x18\x01 \x01(\x03R\vtimestampMs\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12\x1d\n" + + "\n" + + "last_error\x18\x03 \x01(\tR\tlastError\x12P\n" + + "\bmetadata\x18\x04 \x03(\v24.uber.submitqueue.gateway.HistoryEvent.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" + + "\x1dGetRequestHistoryByIDResponse\x12>\n" + + "\x06events\x18\x01 \x03(\v2&.uber.submitqueue.gateway.HistoryEventR\x06events\"D\n" + + "#GetRequestHistoryByChangeURIRequest\x12\x1d\n" + + "\n" + + "change_uri\x18\x01 \x01(\tR\tchangeUri\"d\n" + + "\x0eRequestHistory\x12\x12\n" + + "\x04sqid\x18\x01 \x01(\tR\x04sqid\x12>\n" + + "\x06events\x18\x02 \x03(\v2&.uber.submitqueue.gateway.HistoryEventR\x06events\"n\n" + + "$GetRequestHistoryByChangeURIResponse\x12F\n" + + "\thistories\x18\x01 \x03(\v2(.uber.submitqueue.gateway.RequestHistoryR\thistories\"!\n" + "\x05Error\x12\x18\n" + "\amessage\x18\x01 \x01(\tR\amessage\"e\n" + "\x16UnrecognizedQueueError\x125\n" + @@ -1016,14 +1349,16 @@ const file_gateway_proto_rawDesc = "" + "\x05error\x18\x01 \x01(\v2\x1f.uber.submitqueue.gateway.ErrorR\x05error\x12\x12\n" + "\x04sqid\x18\x02 \x01(\tR\x04sqid\x12\x1d\n" + "\n" + - "change_uri\x18\x03 \x01(\tR\tchangeUri2\xad\x05\n" + + "change_uri\x18\x03 \x01(\tR\tchangeUri2\xdc\a\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\x8a\x01\n" + "\x15GetRequestSummaryByID\x126.uber.submitqueue.gateway.GetRequestSummaryByIDRequest\x1a7.uber.submitqueue.gateway.GetRequestSummaryByIDResponse\"\x00\x12\x9f\x01\n" + "\x1cGetRequestSummaryByChangeURI\x12=.uber.submitqueue.gateway.GetRequestSummaryByChangeURIRequest\x1a>.uber.submitqueue.gateway.GetRequestSummaryByChangeURIResponse\"\x00\x12W\n" + - "\x04List\x12%.uber.submitqueue.gateway.ListRequest\x1a&.uber.submitqueue.gateway.ListResponse\"\x00Bk\n" + + "\x04List\x12%.uber.submitqueue.gateway.ListRequest\x1a&.uber.submitqueue.gateway.ListResponse\"\x00\x12\x8a\x01\n" + + "\x15GetRequestHistoryByID\x126.uber.submitqueue.gateway.GetRequestHistoryByIDRequest\x1a7.uber.submitqueue.gateway.GetRequestHistoryByIDResponse\"\x00\x12\x9f\x01\n" + + "\x1cGetRequestHistoryByChangeURI\x12=.uber.submitqueue.gateway.GetRequestHistoryByChangeURIRequest\x1a>.uber.submitqueue.gateway.GetRequestHistoryByChangeURIResponse\"\x00Bk\n" + "\x1ccom.uber.submitqueue.gatewayB\fGatewayProtoP\x01Z;github.com/uber/submitqueue/api/submitqueue/gateway/protopbb\x06proto3" var ( @@ -1038,7 +1373,7 @@ func file_gateway_proto_rawDescGZIP() []byte { return file_gateway_proto_rawDescData } -var file_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 24) var file_gateway_proto_goTypes = []any{ (*PingRequest)(nil), // 0: uber.submitqueue.gateway.PingRequest (*PingResponse)(nil), // 1: uber.submitqueue.gateway.PingResponse @@ -1053,39 +1388,54 @@ var file_gateway_proto_goTypes = []any{ (*GetRequestSummaryByChangeURIResponse)(nil), // 10: uber.submitqueue.gateway.GetRequestSummaryByChangeURIResponse (*ListRequest)(nil), // 11: uber.submitqueue.gateway.ListRequest (*ListResponse)(nil), // 12: uber.submitqueue.gateway.ListResponse - (*Error)(nil), // 13: uber.submitqueue.gateway.Error - (*UnrecognizedQueueError)(nil), // 14: uber.submitqueue.gateway.UnrecognizedQueueError - (*RequestNotFoundError)(nil), // 15: uber.submitqueue.gateway.RequestNotFoundError - nil, // 16: uber.submitqueue.gateway.RequestSummary.MetadataEntry - (*protopb.Change)(nil), // 17: uber.base.change.Change - (protopb1.Strategy)(0), // 18: uber.base.mergestrategy.Strategy + (*GetRequestHistoryByIDRequest)(nil), // 13: uber.submitqueue.gateway.GetRequestHistoryByIDRequest + (*HistoryEvent)(nil), // 14: uber.submitqueue.gateway.HistoryEvent + (*GetRequestHistoryByIDResponse)(nil), // 15: uber.submitqueue.gateway.GetRequestHistoryByIDResponse + (*GetRequestHistoryByChangeURIRequest)(nil), // 16: uber.submitqueue.gateway.GetRequestHistoryByChangeURIRequest + (*RequestHistory)(nil), // 17: uber.submitqueue.gateway.RequestHistory + (*GetRequestHistoryByChangeURIResponse)(nil), // 18: uber.submitqueue.gateway.GetRequestHistoryByChangeURIResponse + (*Error)(nil), // 19: uber.submitqueue.gateway.Error + (*UnrecognizedQueueError)(nil), // 20: uber.submitqueue.gateway.UnrecognizedQueueError + (*RequestNotFoundError)(nil), // 21: uber.submitqueue.gateway.RequestNotFoundError + nil, // 22: uber.submitqueue.gateway.RequestSummary.MetadataEntry + nil, // 23: uber.submitqueue.gateway.HistoryEvent.MetadataEntry + (*protopb.Change)(nil), // 24: uber.base.change.Change + (protopb1.Strategy)(0), // 25: uber.base.mergestrategy.Strategy } var file_gateway_proto_depIdxs = []int32{ - 17, // 0: uber.submitqueue.gateway.LandRequest.change:type_name -> uber.base.change.Change - 18, // 1: uber.submitqueue.gateway.LandRequest.strategy:type_name -> uber.base.mergestrategy.Strategy - 16, // 2: uber.submitqueue.gateway.RequestSummary.metadata:type_name -> uber.submitqueue.gateway.RequestSummary.MetadataEntry + 24, // 0: uber.submitqueue.gateway.LandRequest.change:type_name -> uber.base.change.Change + 25, // 1: uber.submitqueue.gateway.LandRequest.strategy:type_name -> uber.base.mergestrategy.Strategy + 22, // 2: uber.submitqueue.gateway.RequestSummary.metadata:type_name -> uber.submitqueue.gateway.RequestSummary.MetadataEntry 6, // 3: uber.submitqueue.gateway.GetRequestSummaryByIDResponse.request:type_name -> uber.submitqueue.gateway.RequestSummary 6, // 4: uber.submitqueue.gateway.GetRequestSummaryByChangeURIResponse.requests:type_name -> uber.submitqueue.gateway.RequestSummary 6, // 5: uber.submitqueue.gateway.ListResponse.requests:type_name -> uber.submitqueue.gateway.RequestSummary - 13, // 6: uber.submitqueue.gateway.UnrecognizedQueueError.error:type_name -> uber.submitqueue.gateway.Error - 13, // 7: uber.submitqueue.gateway.RequestNotFoundError.error:type_name -> uber.submitqueue.gateway.Error - 0, // 8: uber.submitqueue.gateway.SubmitQueueGateway.Ping:input_type -> uber.submitqueue.gateway.PingRequest - 2, // 9: uber.submitqueue.gateway.SubmitQueueGateway.Land:input_type -> uber.submitqueue.gateway.LandRequest - 4, // 10: uber.submitqueue.gateway.SubmitQueueGateway.Cancel:input_type -> uber.submitqueue.gateway.CancelRequest - 7, // 11: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestSummaryByID:input_type -> uber.submitqueue.gateway.GetRequestSummaryByIDRequest - 9, // 12: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestSummaryByChangeURI:input_type -> uber.submitqueue.gateway.GetRequestSummaryByChangeURIRequest - 11, // 13: uber.submitqueue.gateway.SubmitQueueGateway.List:input_type -> uber.submitqueue.gateway.ListRequest - 1, // 14: uber.submitqueue.gateway.SubmitQueueGateway.Ping:output_type -> uber.submitqueue.gateway.PingResponse - 3, // 15: uber.submitqueue.gateway.SubmitQueueGateway.Land:output_type -> uber.submitqueue.gateway.LandResponse - 5, // 16: uber.submitqueue.gateway.SubmitQueueGateway.Cancel:output_type -> uber.submitqueue.gateway.CancelResponse - 8, // 17: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestSummaryByID:output_type -> uber.submitqueue.gateway.GetRequestSummaryByIDResponse - 10, // 18: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestSummaryByChangeURI:output_type -> uber.submitqueue.gateway.GetRequestSummaryByChangeURIResponse - 12, // 19: uber.submitqueue.gateway.SubmitQueueGateway.List:output_type -> uber.submitqueue.gateway.ListResponse - 14, // [14:20] is the sub-list for method output_type - 8, // [8:14] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 23, // 6: uber.submitqueue.gateway.HistoryEvent.metadata:type_name -> uber.submitqueue.gateway.HistoryEvent.MetadataEntry + 14, // 7: uber.submitqueue.gateway.GetRequestHistoryByIDResponse.events:type_name -> uber.submitqueue.gateway.HistoryEvent + 14, // 8: uber.submitqueue.gateway.RequestHistory.events:type_name -> uber.submitqueue.gateway.HistoryEvent + 17, // 9: uber.submitqueue.gateway.GetRequestHistoryByChangeURIResponse.histories:type_name -> uber.submitqueue.gateway.RequestHistory + 19, // 10: uber.submitqueue.gateway.UnrecognizedQueueError.error:type_name -> uber.submitqueue.gateway.Error + 19, // 11: uber.submitqueue.gateway.RequestNotFoundError.error:type_name -> uber.submitqueue.gateway.Error + 0, // 12: uber.submitqueue.gateway.SubmitQueueGateway.Ping:input_type -> uber.submitqueue.gateway.PingRequest + 2, // 13: uber.submitqueue.gateway.SubmitQueueGateway.Land:input_type -> uber.submitqueue.gateway.LandRequest + 4, // 14: uber.submitqueue.gateway.SubmitQueueGateway.Cancel:input_type -> uber.submitqueue.gateway.CancelRequest + 7, // 15: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestSummaryByID:input_type -> uber.submitqueue.gateway.GetRequestSummaryByIDRequest + 9, // 16: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestSummaryByChangeURI:input_type -> uber.submitqueue.gateway.GetRequestSummaryByChangeURIRequest + 11, // 17: uber.submitqueue.gateway.SubmitQueueGateway.List:input_type -> uber.submitqueue.gateway.ListRequest + 13, // 18: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestHistoryByID:input_type -> uber.submitqueue.gateway.GetRequestHistoryByIDRequest + 16, // 19: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestHistoryByChangeURI:input_type -> uber.submitqueue.gateway.GetRequestHistoryByChangeURIRequest + 1, // 20: uber.submitqueue.gateway.SubmitQueueGateway.Ping:output_type -> uber.submitqueue.gateway.PingResponse + 3, // 21: uber.submitqueue.gateway.SubmitQueueGateway.Land:output_type -> uber.submitqueue.gateway.LandResponse + 5, // 22: uber.submitqueue.gateway.SubmitQueueGateway.Cancel:output_type -> uber.submitqueue.gateway.CancelResponse + 8, // 23: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestSummaryByID:output_type -> uber.submitqueue.gateway.GetRequestSummaryByIDResponse + 10, // 24: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestSummaryByChangeURI:output_type -> uber.submitqueue.gateway.GetRequestSummaryByChangeURIResponse + 12, // 25: uber.submitqueue.gateway.SubmitQueueGateway.List:output_type -> uber.submitqueue.gateway.ListResponse + 15, // 26: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestHistoryByID:output_type -> uber.submitqueue.gateway.GetRequestHistoryByIDResponse + 18, // 27: uber.submitqueue.gateway.SubmitQueueGateway.GetRequestHistoryByChangeURI:output_type -> uber.submitqueue.gateway.GetRequestHistoryByChangeURIResponse + 20, // [20:28] is the sub-list for method output_type + 12, // [12:20] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_gateway_proto_init() } @@ -1099,7 +1449,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: 17, + NumMessages: 24, 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 0b808dbe..cb87c234 100644 --- a/api/submitqueue/gateway/protopb/gateway.pb.yarpc.go +++ b/api/submitqueue/gateway/protopb/gateway.pb.yarpc.go @@ -27,6 +27,8 @@ type SubmitQueueGatewayYARPCClient interface { GetRequestSummaryByID(context.Context, *GetRequestSummaryByIDRequest, ...yarpc.CallOption) (*GetRequestSummaryByIDResponse, error) GetRequestSummaryByChangeURI(context.Context, *GetRequestSummaryByChangeURIRequest, ...yarpc.CallOption) (*GetRequestSummaryByChangeURIResponse, error) List(context.Context, *ListRequest, ...yarpc.CallOption) (*ListResponse, error) + GetRequestHistoryByID(context.Context, *GetRequestHistoryByIDRequest, ...yarpc.CallOption) (*GetRequestHistoryByIDResponse, error) + GetRequestHistoryByChangeURI(context.Context, *GetRequestHistoryByChangeURIRequest, ...yarpc.CallOption) (*GetRequestHistoryByChangeURIResponse, error) } func newSubmitQueueGatewayYARPCClient(clientConfig transport.ClientConfig, anyResolver v2.AnyResolver, options ...v2.ClientOption) SubmitQueueGatewayYARPCClient { @@ -53,6 +55,8 @@ type SubmitQueueGatewayYARPCServer interface { GetRequestSummaryByID(context.Context, *GetRequestSummaryByIDRequest) (*GetRequestSummaryByIDResponse, error) GetRequestSummaryByChangeURI(context.Context, *GetRequestSummaryByChangeURIRequest) (*GetRequestSummaryByChangeURIResponse, error) List(context.Context, *ListRequest) (*ListResponse, error) + GetRequestHistoryByID(context.Context, *GetRequestHistoryByIDRequest) (*GetRequestHistoryByIDResponse, error) + GetRequestHistoryByChangeURI(context.Context, *GetRequestHistoryByChangeURIRequest) (*GetRequestHistoryByChangeURIResponse, error) } type buildSubmitQueueGatewayYARPCProceduresParams struct { @@ -126,6 +130,26 @@ func buildSubmitQueueGatewayYARPCProcedures(params buildSubmitQueueGatewayYARPCP }, ), }, + { + MethodName: "GetRequestHistoryByID", + Handler: v2.NewUnaryHandler( + v2.UnaryHandlerParams{ + Handle: handler.GetRequestHistoryByID, + NewRequest: newSubmitQueueGatewayServiceGetRequestHistoryByIDYARPCRequest, + AnyResolver: params.AnyResolver, + }, + ), + }, + { + MethodName: "GetRequestHistoryByChangeURI", + Handler: v2.NewUnaryHandler( + v2.UnaryHandlerParams{ + Handle: handler.GetRequestHistoryByChangeURI, + NewRequest: newSubmitQueueGatewayServiceGetRequestHistoryByChangeURIYARPCRequest, + AnyResolver: params.AnyResolver, + }, + ), + }, }, OnewayHandlerParams: []v2.BuildProceduresOnewayHandlerParams{}, StreamHandlerParams: []v2.BuildProceduresStreamHandlerParams{}, @@ -310,6 +334,30 @@ func (c *_SubmitQueueGatewayYARPCCaller) List(ctx context.Context, request *List return response, err } +func (c *_SubmitQueueGatewayYARPCCaller) GetRequestHistoryByID(ctx context.Context, request *GetRequestHistoryByIDRequest, options ...yarpc.CallOption) (*GetRequestHistoryByIDResponse, error) { + responseMessage, err := c.streamClient.Call(ctx, "GetRequestHistoryByID", request, newSubmitQueueGatewayServiceGetRequestHistoryByIDYARPCResponse, options...) + if responseMessage == nil { + return nil, err + } + response, ok := responseMessage.(*GetRequestHistoryByIDResponse) + if !ok { + return nil, v2.CastError(emptySubmitQueueGatewayServiceGetRequestHistoryByIDYARPCResponse, responseMessage) + } + return response, err +} + +func (c *_SubmitQueueGatewayYARPCCaller) GetRequestHistoryByChangeURI(ctx context.Context, request *GetRequestHistoryByChangeURIRequest, options ...yarpc.CallOption) (*GetRequestHistoryByChangeURIResponse, error) { + responseMessage, err := c.streamClient.Call(ctx, "GetRequestHistoryByChangeURI", request, newSubmitQueueGatewayServiceGetRequestHistoryByChangeURIYARPCResponse, options...) + if responseMessage == nil { + return nil, err + } + response, ok := responseMessage.(*GetRequestHistoryByChangeURIResponse) + if !ok { + return nil, v2.CastError(emptySubmitQueueGatewayServiceGetRequestHistoryByChangeURIYARPCResponse, responseMessage) + } + return response, err +} + type _SubmitQueueGatewayYARPCHandler struct { server SubmitQueueGatewayYARPCServer } @@ -410,6 +458,38 @@ func (h *_SubmitQueueGatewayYARPCHandler) List(ctx context.Context, requestMessa return response, err } +func (h *_SubmitQueueGatewayYARPCHandler) GetRequestHistoryByID(ctx context.Context, requestMessage proto.Message) (proto.Message, error) { + var request *GetRequestHistoryByIDRequest + var ok bool + if requestMessage != nil { + request, ok = requestMessage.(*GetRequestHistoryByIDRequest) + if !ok { + return nil, v2.CastError(emptySubmitQueueGatewayServiceGetRequestHistoryByIDYARPCRequest, requestMessage) + } + } + response, err := h.server.GetRequestHistoryByID(ctx, request) + if response == nil { + return nil, err + } + return response, err +} + +func (h *_SubmitQueueGatewayYARPCHandler) GetRequestHistoryByChangeURI(ctx context.Context, requestMessage proto.Message) (proto.Message, error) { + var request *GetRequestHistoryByChangeURIRequest + var ok bool + if requestMessage != nil { + request, ok = requestMessage.(*GetRequestHistoryByChangeURIRequest) + if !ok { + return nil, v2.CastError(emptySubmitQueueGatewayServiceGetRequestHistoryByChangeURIYARPCRequest, requestMessage) + } + } + response, err := h.server.GetRequestHistoryByChangeURI(ctx, request) + if response == nil { + return nil, err + } + return response, err +} + func newSubmitQueueGatewayServicePingYARPCRequest() proto.Message { return &PingRequest{} } @@ -458,6 +538,22 @@ func newSubmitQueueGatewayServiceListYARPCResponse() proto.Message { return &ListResponse{} } +func newSubmitQueueGatewayServiceGetRequestHistoryByIDYARPCRequest() proto.Message { + return &GetRequestHistoryByIDRequest{} +} + +func newSubmitQueueGatewayServiceGetRequestHistoryByIDYARPCResponse() proto.Message { + return &GetRequestHistoryByIDResponse{} +} + +func newSubmitQueueGatewayServiceGetRequestHistoryByChangeURIYARPCRequest() proto.Message { + return &GetRequestHistoryByChangeURIRequest{} +} + +func newSubmitQueueGatewayServiceGetRequestHistoryByChangeURIYARPCResponse() proto.Message { + return &GetRequestHistoryByChangeURIResponse{} +} + var ( emptySubmitQueueGatewayServicePingYARPCRequest = &PingRequest{} emptySubmitQueueGatewayServicePingYARPCResponse = &PingResponse{} @@ -471,69 +567,83 @@ var ( emptySubmitQueueGatewayServiceGetRequestSummaryByChangeURIYARPCResponse = &GetRequestSummaryByChangeURIResponse{} emptySubmitQueueGatewayServiceListYARPCRequest = &ListRequest{} emptySubmitQueueGatewayServiceListYARPCResponse = &ListResponse{} + emptySubmitQueueGatewayServiceGetRequestHistoryByIDYARPCRequest = &GetRequestHistoryByIDRequest{} + emptySubmitQueueGatewayServiceGetRequestHistoryByIDYARPCResponse = &GetRequestHistoryByIDResponse{} + emptySubmitQueueGatewayServiceGetRequestHistoryByChangeURIYARPCRequest = &GetRequestHistoryByChangeURIRequest{} + emptySubmitQueueGatewayServiceGetRequestHistoryByChangeURIYARPCResponse = &GetRequestHistoryByChangeURIResponse{} ) var yarpcFileDescriptorClosuref1a937782ebbded5 = [][]byte{ // gateway.proto []byte{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x5f, 0x6f, 0x1b, 0x45, - 0x10, 0xcf, 0xd9, 0xb1, 0x63, 0x8f, 0x9d, 0x10, 0x2d, 0xa5, 0x9c, 0x4c, 0xa2, 0x26, 0x4b, 0x69, - 0xfd, 0x80, 0xce, 0x95, 0xf9, 0x2b, 0xa2, 0x22, 0xd5, 0x4d, 0xa9, 0x2a, 0x91, 0x12, 0x2e, 0x44, - 0x48, 0x48, 0xc8, 0x5a, 0xdb, 0xd3, 0xcb, 0x29, 0xb9, 0x3b, 0x67, 0x77, 0x2f, 0xe0, 0x88, 0x07, - 0x5e, 0x78, 0xe1, 0x99, 0x77, 0x3e, 0x01, 0x9f, 0x82, 0x27, 0xbe, 0x15, 0xda, 0x3f, 0x77, 0x3e, - 0x57, 0xf6, 0xb5, 0x81, 0x3e, 0xf9, 0x76, 0x66, 0x7e, 0x33, 0xb3, 0xbf, 0x9d, 0xfd, 0xad, 0x61, - 0x33, 0x60, 0x12, 0x7f, 0x62, 0x33, 0x6f, 0xca, 0x13, 0x99, 0x10, 0x37, 0x1d, 0x21, 0xf7, 0x44, - 0x3a, 0x8a, 0x42, 0x79, 0x99, 0x62, 0x8a, 0x9e, 0xf5, 0x77, 0x28, 0x9b, 0x86, 0xbd, 0x11, 0x13, - 0xd8, 0x1b, 0x9f, 0xb1, 0x38, 0xc0, 0x9e, 0x06, 0xd8, 0x85, 0x41, 0x77, 0x1e, 0xe4, 0x31, 0x11, - 0xf2, 0x00, 0x85, 0xe4, 0x4c, 0x62, 0x30, 0xb3, 0xa1, 0x0b, 0x36, 0x83, 0xa0, 0xf7, 0xa1, 0x75, - 0x1c, 0xc6, 0x81, 0x8f, 0x97, 0x29, 0x0a, 0x49, 0x5c, 0xd8, 0x88, 0x50, 0x08, 0x16, 0xa0, 0xeb, - 0xec, 0x39, 0xdd, 0xa6, 0x9f, 0x2d, 0xe9, 0x6f, 0x0e, 0xb4, 0x4d, 0xa4, 0x98, 0x26, 0xb1, 0xc0, - 0xd5, 0xa1, 0x64, 0x1f, 0xda, 0x02, 0xf9, 0x55, 0x38, 0xc6, 0x61, 0xcc, 0x22, 0x74, 0x2b, 0xda, - 0xdd, 0xb2, 0xb6, 0xe7, 0x2c, 0x42, 0xb2, 0x03, 0x4d, 0x19, 0x46, 0x28, 0x24, 0x8b, 0xa6, 0x6e, - 0x75, 0xcf, 0xe9, 0x56, 0xfd, 0xb9, 0x81, 0x74, 0xa0, 0x71, 0x96, 0x08, 0xa9, 0xc1, 0xeb, 0x1a, - 0x9c, 0xaf, 0xe9, 0x1f, 0x0e, 0xb4, 0xbe, 0x66, 0xf1, 0x24, 0xeb, 0xf8, 0x16, 0xd4, 0x34, 0x4f, - 0xb6, 0x09, 0xb3, 0x20, 0x0f, 0xa0, 0x6e, 0x88, 0xd1, 0xc5, 0x5b, 0x7d, 0xd7, 0xd3, 0xbc, 0x2a, - 0x6a, 0x3c, 0xcb, 0xd8, 0x63, 0xfd, 0xe3, 0xdb, 0x38, 0xf2, 0x10, 0x1a, 0x19, 0x35, 0xba, 0xe6, - 0x56, 0x7f, 0xbf, 0x80, 0x59, 0xa4, 0xee, 0xc4, 0x7e, 0xf8, 0x39, 0x84, 0x52, 0x68, 0x9b, 0xae, - 0x2c, 0x3b, 0x04, 0xd6, 0xc5, 0x65, 0x38, 0xb1, 0x5d, 0xe9, 0x6f, 0x7a, 0x00, 0x9b, 0x8f, 0x59, - 0x3c, 0xc6, 0x8b, 0xac, 0xf7, 0x25, 0x41, 0xe4, 0x36, 0xd4, 0x39, 0x32, 0x91, 0xc4, 0x96, 0x36, - 0xbb, 0xa2, 0xdb, 0xb0, 0x95, 0x81, 0x4d, 0x09, 0xfa, 0x4f, 0x05, 0xb6, 0x6c, 0xa6, 0x93, 0x34, - 0x8a, 0x18, 0x9f, 0x2d, 0x4d, 0x98, 0x13, 0x54, 0x29, 0x12, 0x74, 0x07, 0x5a, 0x66, 0xe3, 0xc3, - 0x94, 0x87, 0xc2, 0xad, 0xee, 0x55, 0xbb, 0x4d, 0x1f, 0x8c, 0xe9, 0x94, 0x87, 0x82, 0xdc, 0x85, - 0x2d, 0x8e, 0x63, 0x0c, 0xaf, 0x70, 0x32, 0x64, 0x72, 0x18, 0x09, 0xcd, 0x4a, 0xd5, 0x6f, 0x67, - 0xd6, 0x47, 0xf2, 0x48, 0xa8, 0x6e, 0x85, 0x64, 0x32, 0x15, 0x6e, 0xcd, 0x74, 0x6b, 0x56, 0x64, - 0x17, 0xe0, 0x82, 0x09, 0x39, 0x44, 0xce, 0x13, 0xee, 0xd6, 0xb5, 0xaf, 0xa9, 0x2c, 0x4f, 0x94, - 0x81, 0xf8, 0xd0, 0x88, 0x50, 0xb2, 0x09, 0x93, 0xcc, 0xdd, 0xd8, 0xab, 0x76, 0x5b, 0xfd, 0x4f, - 0xbd, 0x55, 0x83, 0xef, 0x2d, 0xee, 0xd1, 0x3b, 0xb2, 0xc0, 0x27, 0xb1, 0xe4, 0x33, 0x3f, 0xcf, - 0xd3, 0x39, 0x80, 0xcd, 0x05, 0x17, 0xd9, 0x86, 0xea, 0x39, 0xce, 0x2c, 0x17, 0xea, 0x53, 0x51, - 0x71, 0xc5, 0x2e, 0xe6, 0x54, 0xe8, 0xc5, 0x17, 0x95, 0xcf, 0x1d, 0xda, 0x87, 0x9d, 0xa7, 0x28, - 0x17, 0x2b, 0x0d, 0x66, 0xcf, 0x0e, 0x4b, 0x4e, 0x8a, 0x8e, 0x61, 0x77, 0x05, 0xc6, 0xce, 0xc0, - 0x00, 0x36, 0xb8, 0xf1, 0x6a, 0x5c, 0xab, 0xdf, 0x7d, 0xdd, 0x4d, 0xfa, 0x19, 0x90, 0x1e, 0xc2, - 0xfb, 0x4b, 0x8a, 0x98, 0xd9, 0x3d, 0xf5, 0x9f, 0x65, 0xfd, 0xed, 0x02, 0xcc, 0x8f, 0xd3, 0x76, - 0xd9, 0xcc, 0x4f, 0x93, 0x5e, 0xc0, 0xdd, 0xf2, 0x2c, 0xb6, 0xe3, 0x43, 0x68, 0xd8, 0xc2, 0xc2, - 0x75, 0xf4, 0xb9, 0xbc, 0x7e, 0xcb, 0x39, 0x92, 0xfe, 0xad, 0xae, 0x68, 0x28, 0x64, 0xf9, 0x15, - 0xfd, 0x18, 0xde, 0x2d, 0x0e, 0x58, 0xc2, 0x87, 0xec, 0x85, 0x44, 0xae, 0x26, 0xad, 0xa2, 0x27, - 0xed, 0xed, 0xf9, 0xa4, 0x7d, 0xc3, 0x1f, 0x29, 0xdf, 0x91, 0x20, 0x1f, 0x02, 0xc9, 0x51, 0x23, - 0x7c, 0x91, 0x70, 0x54, 0x00, 0xa3, 0x20, 0xdb, 0x99, 0x67, 0xa0, 0x1d, 0x47, 0x82, 0xbc, 0x07, - 0xcd, 0x29, 0x0b, 0x70, 0x28, 0xc2, 0x6b, 0xa3, 0x24, 0x35, 0xbf, 0xa1, 0x0c, 0x27, 0xe1, 0x35, - 0x2a, 0xce, 0xb4, 0x53, 0x26, 0xe7, 0x18, 0xdb, 0xf9, 0xd5, 0xe1, 0xdf, 0x29, 0x03, 0xfd, 0x05, - 0xda, 0x66, 0x13, 0x6f, 0x92, 0x1b, 0x72, 0x0f, 0xde, 0x8a, 0xf1, 0x67, 0x39, 0x2c, 0x54, 0x36, - 0xc3, 0xb8, 0xa9, 0xcc, 0xc7, 0x79, 0xf5, 0x7d, 0xa8, 0x99, 0xab, 0xb2, 0x5a, 0x91, 0x11, 0x6e, - 0x9f, 0xc6, 0x1c, 0xc7, 0x49, 0x10, 0x87, 0xd7, 0x38, 0xf9, 0x56, 0x35, 0x60, 0x30, 0x9f, 0x40, - 0xcd, 0x5c, 0x3c, 0x33, 0x76, 0x77, 0x56, 0xf7, 0xa9, 0xe3, 0x7d, 0x13, 0xbd, 0x5c, 0x29, 0xe8, - 0xaf, 0x0e, 0xdc, 0xb2, 0xdb, 0x79, 0x9e, 0xc8, 0xaf, 0x92, 0x34, 0x9e, 0xfc, 0xaf, 0x2a, 0xd9, - 0x55, 0xaa, 0x14, 0x34, 0x6a, 0x71, 0x7c, 0xab, 0x2f, 0x8d, 0x6f, 0xff, 0xaf, 0x1a, 0x90, 0x13, - 0x9d, 0x57, 0x6f, 0xf2, 0xa9, 0x49, 0x4b, 0xbe, 0x87, 0x75, 0xf5, 0x22, 0x91, 0x0f, 0x56, 0x57, - 0x2e, 0xbc, 0x6d, 0x9d, 0x7b, 0xaf, 0x0a, 0xb3, 0xba, 0xba, 0xa6, 0x12, 0x2b, 0x31, 0x2f, 0x4b, - 0x5c, 0x78, 0x82, 0xca, 0x12, 0x17, 0xdf, 0x04, 0xba, 0x46, 0x7e, 0x84, 0xba, 0x11, 0x71, 0x72, - 0x7f, 0x35, 0x66, 0xe1, 0x8d, 0xe8, 0x74, 0x5f, 0x1d, 0x98, 0xa7, 0xff, 0xdd, 0x81, 0x77, 0x96, - 0x4a, 0x12, 0x29, 0x91, 0xd7, 0x32, 0xdd, 0xeb, 0x7c, 0x76, 0x63, 0x5c, 0xde, 0xcc, 0x9f, 0xce, - 0x52, 0x4d, 0xcd, 0x45, 0x87, 0x3c, 0xbc, 0x51, 0xee, 0x97, 0x25, 0xaf, 0xf3, 0xe5, 0x7f, 0x85, - 0x2f, 0x1c, 0x73, 0x28, 0x64, 0xe9, 0x31, 0xcf, 0x65, 0xac, 0xf4, 0x98, 0x0b, 0x42, 0x41, 0xd7, - 0x06, 0xe7, 0xb0, 0x33, 0x4e, 0xa2, 0x95, 0xe1, 0x83, 0xb6, 0x9d, 0xe0, 0x63, 0xf5, 0x17, 0xec, - 0xd8, 0xf9, 0xe1, 0x20, 0x08, 0xe5, 0x59, 0x3a, 0xf2, 0xc6, 0x49, 0xd4, 0x53, 0xa0, 0x5e, 0x01, - 0xd4, 0x53, 0x7f, 0xe9, 0x8a, 0x6b, 0x9b, 0xc4, 0xfc, 0xa7, 0x9b, 0x8e, 0x46, 0x75, 0xfd, 0xf1, - 0xd1, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x93, 0xae, 0xe0, 0xd4, 0x49, 0x0a, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xcf, 0xda, 0x89, 0x13, 0x3f, 0x3b, 0x21, 0x1a, 0x4a, 0x59, 0x99, 0x44, 0x4d, 0x96, 0x92, + 0xfa, 0x80, 0xec, 0xca, 0x94, 0x3f, 0x22, 0x6a, 0xa5, 0xa6, 0x49, 0x4b, 0x25, 0x52, 0xcc, 0x86, + 0x08, 0x09, 0x09, 0x59, 0x63, 0xfb, 0xd5, 0x59, 0x25, 0xbb, 0xeb, 0xcc, 0xcc, 0x06, 0x1c, 0x71, + 0xe0, 0xc2, 0x85, 0x33, 0x77, 0x3e, 0x0c, 0x27, 0xbe, 0x03, 0xdf, 0x83, 0x2b, 0x9a, 0x3f, 0xbb, + 0xde, 0x4d, 0xbc, 0x1b, 0xa7, 0xc0, 0xc9, 0x3b, 0x6f, 0xde, 0xef, 0xbd, 0x37, 0xbf, 0xf7, 0x67, + 0xc6, 0xb0, 0x3a, 0xa2, 0x02, 0x7f, 0xa0, 0x93, 0xd6, 0x98, 0x85, 0x22, 0x24, 0x76, 0xd4, 0x47, + 0xd6, 0xe2, 0x51, 0xdf, 0xf7, 0xc4, 0x79, 0x84, 0x11, 0xb6, 0xcc, 0x7e, 0xc3, 0xa1, 0x63, 0xaf, + 0xdd, 0xa7, 0x1c, 0xdb, 0x83, 0x13, 0x1a, 0x8c, 0xb0, 0xad, 0x00, 0x66, 0xa1, 0xd1, 0x8d, 0x87, + 0x89, 0x8e, 0x8f, 0x6c, 0x84, 0x5c, 0x30, 0x2a, 0x70, 0x34, 0x31, 0xaa, 0x19, 0x99, 0x46, 0x38, + 0x0f, 0xa0, 0xd6, 0xf5, 0x82, 0x91, 0x8b, 0xe7, 0x11, 0x72, 0x41, 0x6c, 0x58, 0xf6, 0x91, 0x73, + 0x3a, 0x42, 0xdb, 0xda, 0xb2, 0x9a, 0x55, 0x37, 0x5e, 0x3a, 0xbf, 0x58, 0x50, 0xd7, 0x9a, 0x7c, + 0x1c, 0x06, 0x1c, 0xf3, 0x55, 0xc9, 0x36, 0xd4, 0x39, 0xb2, 0x0b, 0x6f, 0x80, 0xbd, 0x80, 0xfa, + 0x68, 0x97, 0xd4, 0x76, 0xcd, 0xc8, 0x5e, 0x51, 0x1f, 0xc9, 0x06, 0x54, 0x85, 0xe7, 0x23, 0x17, + 0xd4, 0x1f, 0xdb, 0xe5, 0x2d, 0xab, 0x59, 0x76, 0xa7, 0x02, 0xd2, 0x80, 0x95, 0x93, 0x90, 0x0b, + 0x05, 0x5e, 0x54, 0xe0, 0x64, 0xed, 0xfc, 0x66, 0x41, 0xed, 0x4b, 0x1a, 0x0c, 0xe3, 0x88, 0xef, + 0xc0, 0x92, 0xe2, 0xc9, 0x04, 0xa1, 0x17, 0xe4, 0x21, 0x54, 0x34, 0x31, 0xca, 0x79, 0xad, 0x63, + 0xb7, 0x14, 0xaf, 0x92, 0x9a, 0x96, 0x61, 0xec, 0x99, 0xfa, 0x71, 0x8d, 0x1e, 0x79, 0x0c, 0x2b, + 0x31, 0x35, 0xca, 0xe7, 0x5a, 0x67, 0x3b, 0x85, 0xc9, 0x52, 0x77, 0x64, 0x3e, 0xdc, 0x04, 0xe2, + 0x38, 0x50, 0xd7, 0x51, 0x19, 0x76, 0x08, 0x2c, 0xf2, 0x73, 0x6f, 0x68, 0xa2, 0x52, 0xdf, 0xce, + 0x2e, 0xac, 0x3e, 0xa3, 0xc1, 0x00, 0xcf, 0xe2, 0xd8, 0x67, 0x28, 0x91, 0xbb, 0x50, 0x61, 0x48, + 0x79, 0x18, 0x18, 0xda, 0xcc, 0xca, 0x59, 0x87, 0xb5, 0x18, 0xac, 0x5d, 0x38, 0x7f, 0x96, 0x60, + 0xcd, 0x58, 0x3a, 0x8a, 0x7c, 0x9f, 0xb2, 0xc9, 0x4c, 0x83, 0x09, 0x41, 0xa5, 0x34, 0x41, 0xf7, + 0xa0, 0xa6, 0x0f, 0xde, 0x8b, 0x98, 0xc7, 0xed, 0xf2, 0x56, 0xb9, 0x59, 0x75, 0x41, 0x8b, 0x8e, + 0x99, 0xc7, 0xc9, 0x7d, 0x58, 0x63, 0x38, 0x40, 0xef, 0x02, 0x87, 0x3d, 0x2a, 0x7a, 0x3e, 0x57, + 0xac, 0x94, 0xdd, 0x7a, 0x2c, 0x7d, 0x2a, 0x0e, 0xb9, 0x8c, 0x96, 0x0b, 0x2a, 0x22, 0x6e, 0x2f, + 0xe9, 0x68, 0xf5, 0x8a, 0x6c, 0x02, 0x9c, 0x51, 0x2e, 0x7a, 0xc8, 0x58, 0xc8, 0xec, 0x8a, 0xda, + 0xab, 0x4a, 0xc9, 0x81, 0x14, 0x10, 0x17, 0x56, 0x7c, 0x14, 0x74, 0x48, 0x05, 0xb5, 0x97, 0xb7, + 0xca, 0xcd, 0x5a, 0xe7, 0x93, 0x56, 0x5e, 0xe1, 0xb7, 0xb2, 0x67, 0x6c, 0x1d, 0x1a, 0xe0, 0x41, + 0x20, 0xd8, 0xc4, 0x4d, 0xec, 0x34, 0x76, 0x61, 0x35, 0xb3, 0x45, 0xd6, 0xa1, 0x7c, 0x8a, 0x13, + 0xc3, 0x85, 0xfc, 0x94, 0x54, 0x5c, 0xd0, 0xb3, 0x29, 0x15, 0x6a, 0xf1, 0x79, 0xe9, 0x33, 0xcb, + 0xe9, 0xc0, 0xc6, 0x0b, 0x14, 0x59, 0x4f, 0x7b, 0x93, 0x97, 0xfb, 0x05, 0x99, 0x72, 0x06, 0xb0, + 0x99, 0x83, 0x31, 0x35, 0xb0, 0x07, 0xcb, 0x4c, 0xef, 0x2a, 0x5c, 0xad, 0xd3, 0x9c, 0xf7, 0x90, + 0x6e, 0x0c, 0x74, 0xf6, 0xe1, 0xfd, 0x19, 0x4e, 0x74, 0xed, 0x1e, 0xbb, 0x2f, 0xe3, 0xf8, 0x36, + 0x01, 0xa6, 0xe9, 0x34, 0x51, 0x56, 0x93, 0x6c, 0x3a, 0x67, 0x70, 0xbf, 0xd8, 0x8a, 0x89, 0x78, + 0x1f, 0x56, 0x8c, 0x63, 0x6e, 0x5b, 0x2a, 0x2f, 0xf3, 0x87, 0x9c, 0x20, 0x9d, 0x3f, 0x64, 0x8b, + 0x7a, 0x5c, 0x14, 0xb7, 0xe8, 0x23, 0x78, 0x37, 0x5d, 0x60, 0x21, 0xeb, 0xd1, 0xd7, 0x02, 0x99, + 0xac, 0xb4, 0x92, 0xaa, 0xb4, 0xb7, 0xa7, 0x95, 0xf6, 0x15, 0x7b, 0x2a, 0xf7, 0x0e, 0x39, 0xf9, + 0x10, 0x48, 0x82, 0xea, 0xe3, 0xeb, 0x90, 0xa1, 0x04, 0xe8, 0x09, 0xb2, 0x1e, 0xef, 0xec, 0xa9, + 0x8d, 0x43, 0x4e, 0xde, 0x83, 0xea, 0x98, 0x8e, 0xb0, 0xc7, 0xbd, 0x4b, 0x3d, 0x49, 0x96, 0xdc, + 0x15, 0x29, 0x38, 0xf2, 0x2e, 0x51, 0x72, 0xa6, 0x36, 0x45, 0x78, 0x8a, 0x81, 0xa9, 0x5f, 0xa5, + 0xfe, 0x8d, 0x14, 0x38, 0x3f, 0x41, 0x5d, 0x1f, 0xe2, 0xbf, 0xe4, 0x86, 0xec, 0xc0, 0x5b, 0x01, + 0xfe, 0x28, 0x7a, 0x29, 0xcf, 0xba, 0x18, 0x57, 0xa5, 0xb8, 0x9b, 0x78, 0xcf, 0x14, 0xe4, 0x17, + 0x1e, 0x17, 0xe1, 0xcd, 0x05, 0xf9, 0xb7, 0x05, 0x75, 0xa3, 0x7a, 0x70, 0x81, 0x81, 0x90, 0x83, + 0x38, 0x19, 0xaa, 0x92, 0x26, 0x4b, 0xd1, 0x54, 0x4b, 0x64, 0x99, 0x06, 0x2e, 0x15, 0x34, 0x70, + 0xf9, 0x6a, 0x03, 0x77, 0x53, 0x0d, 0xbc, 0xa8, 0xc8, 0x78, 0x94, 0x4f, 0x46, 0x3a, 0xa6, 0xff, + 0xa7, 0x7d, 0x7b, 0xe9, 0x56, 0xcc, 0xb0, 0x65, 0x92, 0xf7, 0x04, 0x2a, 0x28, 0xdd, 0xc7, 0xa9, + 0xdb, 0x99, 0x2f, 0x5a, 0xd7, 0xa0, 0xb2, 0x6d, 0x98, 0x38, 0xb8, 0x6d, 0x1b, 0x0e, 0x93, 0x81, + 0x6d, 0x4c, 0xcc, 0x1c, 0xd8, 0xd3, 0x58, 0x4b, 0x6f, 0x14, 0x6b, 0x90, 0x6e, 0xf6, 0x59, 0xb1, + 0x1a, 0x4e, 0x9e, 0x43, 0xf5, 0x44, 0xed, 0x7a, 0x38, 0x7f, 0x45, 0x1b, 0x7b, 0xee, 0x14, 0xea, + 0x6c, 0xc3, 0x92, 0x2e, 0x8a, 0xfc, 0xc7, 0x03, 0xc2, 0xdd, 0xe3, 0x80, 0xe1, 0x20, 0x1c, 0x05, + 0xde, 0x25, 0x0e, 0xbf, 0x96, 0x96, 0x35, 0xe6, 0x63, 0x58, 0xd2, 0x25, 0xa6, 0x27, 0xe4, 0xbd, + 0xfc, 0x00, 0x94, 0xbe, 0xab, 0xb5, 0x67, 0x5f, 0x6a, 0xce, 0xcf, 0x16, 0xdc, 0x31, 0x71, 0xbe, + 0x0a, 0xc5, 0xf3, 0x30, 0x0a, 0x86, 0xff, 0xca, 0x4b, 0x9c, 0x9d, 0x52, 0x2a, 0x3b, 0xd9, 0x14, + 0x97, 0xaf, 0xa4, 0xb8, 0xf3, 0xd7, 0x32, 0x90, 0x23, 0x65, 0x57, 0x1d, 0xf2, 0x85, 0x36, 0x4b, + 0xbe, 0x85, 0x45, 0xf9, 0x78, 0x22, 0x1f, 0xe4, 0x7b, 0x4e, 0x3d, 0xc3, 0x1a, 0x3b, 0x37, 0xa9, + 0x99, 0x27, 0xc0, 0x82, 0x34, 0x2c, 0xdf, 0x1d, 0x45, 0x86, 0x53, 0xaf, 0xa5, 0x22, 0xc3, 0xe9, + 0xe7, 0x8b, 0xb3, 0x40, 0xbe, 0x87, 0x8a, 0x7e, 0x6f, 0x90, 0x07, 0xf9, 0x98, 0xcc, 0x73, 0xa6, + 0xd1, 0xbc, 0x59, 0x31, 0x31, 0xff, 0xab, 0x05, 0xef, 0xcc, 0xbc, 0x3d, 0x49, 0xc1, 0x4b, 0xa0, + 0xe8, 0x8a, 0x6e, 0x7c, 0x7a, 0x6b, 0x5c, 0x12, 0xcc, 0xef, 0xd6, 0xcc, 0xeb, 0x3f, 0x69, 0x19, + 0xf2, 0xf8, 0x56, 0xb6, 0xaf, 0x8e, 0x85, 0xc6, 0x93, 0x37, 0x85, 0x67, 0xd2, 0xec, 0x71, 0x51, + 0x98, 0xe6, 0xe9, 0x8d, 0x5b, 0x98, 0xe6, 0xd4, 0x9d, 0x76, 0x2d, 0x0f, 0xa9, 0xd1, 0x39, 0x5f, + 0x1e, 0xae, 0xdf, 0x4c, 0xf3, 0xe5, 0x61, 0xc6, 0x8c, 0xbe, 0x96, 0x87, 0xeb, 0xa3, 0x6b, 0xbe, + 0x3c, 0xe4, 0x8e, 0xe7, 0xf9, 0xf2, 0x90, 0x3f, 0x31, 0x9d, 0x85, 0xbd, 0x53, 0xd8, 0x18, 0x84, + 0x7e, 0xae, 0x99, 0xbd, 0xba, 0x69, 0xf8, 0xae, 0xfc, 0x73, 0xd5, 0xb5, 0xbe, 0xdb, 0x1d, 0x79, + 0xe2, 0x24, 0xea, 0xb7, 0x06, 0xa1, 0xdf, 0x96, 0xa0, 0x76, 0x0a, 0xd4, 0x96, 0x7f, 0xd6, 0xd2, + 0x6b, 0x63, 0x44, 0xff, 0x5b, 0x1b, 0xf7, 0xfb, 0x15, 0xf5, 0xf1, 0xd1, 0x3f, 0x01, 0x00, 0x00, + 0xff, 0xff, 0x28, 0xf4, 0xb0, 0x04, 0x23, 0x0e, 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 86c8f6ec..ac9a4a2f 100644 --- a/api/submitqueue/gateway/protopb/gateway_grpc.pb.go +++ b/api/submitqueue/gateway/protopb/gateway_grpc.pb.go @@ -40,6 +40,8 @@ const ( SubmitQueueGateway_GetRequestSummaryByID_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/GetRequestSummaryByID" SubmitQueueGateway_GetRequestSummaryByChangeURI_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/GetRequestSummaryByChangeURI" SubmitQueueGateway_List_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/List" + SubmitQueueGateway_GetRequestHistoryByID_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/GetRequestHistoryByID" + SubmitQueueGateway_GetRequestHistoryByChangeURI_FullMethodName = "/uber.submitqueue.gateway.SubmitQueueGateway/GetRequestHistoryByChangeURI" ) // SubmitQueueGatewayClient is the client API for SubmitQueueGateway service. @@ -71,6 +73,10 @@ type SubmitQueueGatewayClient interface { GetRequestSummaryByChangeURI(ctx context.Context, in *GetRequestSummaryByChangeURIRequest, opts ...grpc.CallOption) (*GetRequestSummaryByChangeURIResponse, error) // List returns requests received for one queue during a bounded receipt-time range. List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) + // GetRequestHistoryByID returns every retained request-log event for one request. + GetRequestHistoryByID(ctx context.Context, in *GetRequestHistoryByIDRequest, opts ...grpc.CallOption) (*GetRequestHistoryByIDResponse, error) + // GetRequestHistoryByChangeURI returns retained request histories for an exact pinned change URI. + GetRequestHistoryByChangeURI(ctx context.Context, in *GetRequestHistoryByChangeURIRequest, opts ...grpc.CallOption) (*GetRequestHistoryByChangeURIResponse, error) } type submitQueueGatewayClient struct { @@ -141,6 +147,26 @@ func (c *submitQueueGatewayClient) List(ctx context.Context, in *ListRequest, op return out, nil } +func (c *submitQueueGatewayClient) GetRequestHistoryByID(ctx context.Context, in *GetRequestHistoryByIDRequest, opts ...grpc.CallOption) (*GetRequestHistoryByIDResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetRequestHistoryByIDResponse) + err := c.cc.Invoke(ctx, SubmitQueueGateway_GetRequestHistoryByID_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *submitQueueGatewayClient) GetRequestHistoryByChangeURI(ctx context.Context, in *GetRequestHistoryByChangeURIRequest, opts ...grpc.CallOption) (*GetRequestHistoryByChangeURIResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetRequestHistoryByChangeURIResponse) + err := c.cc.Invoke(ctx, SubmitQueueGateway_GetRequestHistoryByChangeURI_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // SubmitQueueGatewayServer is the server API for SubmitQueueGateway service. // All implementations must embed UnimplementedSubmitQueueGatewayServer // for forward compatibility. @@ -170,6 +196,10 @@ type SubmitQueueGatewayServer interface { GetRequestSummaryByChangeURI(context.Context, *GetRequestSummaryByChangeURIRequest) (*GetRequestSummaryByChangeURIResponse, error) // List returns requests received for one queue during a bounded receipt-time range. List(context.Context, *ListRequest) (*ListResponse, error) + // GetRequestHistoryByID returns every retained request-log event for one request. + GetRequestHistoryByID(context.Context, *GetRequestHistoryByIDRequest) (*GetRequestHistoryByIDResponse, error) + // GetRequestHistoryByChangeURI returns retained request histories for an exact pinned change URI. + GetRequestHistoryByChangeURI(context.Context, *GetRequestHistoryByChangeURIRequest) (*GetRequestHistoryByChangeURIResponse, error) mustEmbedUnimplementedSubmitQueueGatewayServer() } @@ -198,6 +228,12 @@ func (UnimplementedSubmitQueueGatewayServer) GetRequestSummaryByChangeURI(contex func (UnimplementedSubmitQueueGatewayServer) List(context.Context, *ListRequest) (*ListResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method List not implemented") } +func (UnimplementedSubmitQueueGatewayServer) GetRequestHistoryByID(context.Context, *GetRequestHistoryByIDRequest) (*GetRequestHistoryByIDResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRequestHistoryByID not implemented") +} +func (UnimplementedSubmitQueueGatewayServer) GetRequestHistoryByChangeURI(context.Context, *GetRequestHistoryByChangeURIRequest) (*GetRequestHistoryByChangeURIResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRequestHistoryByChangeURI not implemented") +} func (UnimplementedSubmitQueueGatewayServer) mustEmbedUnimplementedSubmitQueueGatewayServer() {} func (UnimplementedSubmitQueueGatewayServer) testEmbeddedByValue() {} @@ -327,6 +363,42 @@ func _SubmitQueueGateway_List_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _SubmitQueueGateway_GetRequestHistoryByID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRequestHistoryByIDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SubmitQueueGatewayServer).GetRequestHistoryByID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SubmitQueueGateway_GetRequestHistoryByID_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SubmitQueueGatewayServer).GetRequestHistoryByID(ctx, req.(*GetRequestHistoryByIDRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SubmitQueueGateway_GetRequestHistoryByChangeURI_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRequestHistoryByChangeURIRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SubmitQueueGatewayServer).GetRequestHistoryByChangeURI(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SubmitQueueGateway_GetRequestHistoryByChangeURI_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SubmitQueueGatewayServer).GetRequestHistoryByChangeURI(ctx, req.(*GetRequestHistoryByChangeURIRequest)) + } + return interceptor(ctx, in, info, handler) +} + // SubmitQueueGateway_ServiceDesc is the grpc.ServiceDesc for SubmitQueueGateway service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -358,6 +430,14 @@ var SubmitQueueGateway_ServiceDesc = grpc.ServiceDesc{ MethodName: "List", Handler: _SubmitQueueGateway_List_Handler, }, + { + MethodName: "GetRequestHistoryByID", + Handler: _SubmitQueueGateway_GetRequestHistoryByID_Handler, + }, + { + MethodName: "GetRequestHistoryByChangeURI", + Handler: _SubmitQueueGateway_GetRequestHistoryByChangeURI_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "gateway.proto", diff --git a/service/README.md b/service/README.md index 35e9fe08..2e49af73 100644 --- a/service/README.md +++ b/service/README.md @@ -12,7 +12,7 @@ Each domain has its own subdirectory with a dedicated README: | Service | Port | Domain | RPCs | Backing stores | |---------|------|--------|------|----------------| -| **SubmitQueue Gateway** | 8081 | `submitqueue` | `Ping`, `Land` | MySQL app + queue | +| **SubmitQueue Gateway** | 8081 | `submitqueue` | `Ping`, `Land`, `Cancel`, `GetRequestSummaryByID`, `GetRequestSummaryByChangeURI`, `List`, `GetRequestHistoryByID`, `GetRequestHistoryByChangeURI` | MySQL app + queue | | **SubmitQueue Orchestrator** | 8082 | `submitqueue` | `Ping` (+ consumes 9 pipeline topics) | MySQL app + queue | | **Stovepipe** | 8083 | `stovepipe` | `Ping`, `Ingest` (+ consumes the process topic) | MySQL storage + queue | | **Runway** | 8086 | `runway` | `Ping` (+ consumes merge-conflict-check & merge topics) | MySQL queue | @@ -145,6 +145,14 @@ grpcurl -plaintext localhost:8081 describe uber.submitqueue.gateway.SubmitQueueG |--------|-------------| | `Ping` | Health check, returns service name and timestamp | | `Land` | Submit a land request for code changes | +| `Cancel` | Record and publish a best-effort cancellation intent | +| `GetRequestSummaryByID` | Read one authoritative current request summary by sqid | +| `GetRequestSummaryByChangeURI` | Read authoritative current summaries for one exact pinned change URI | +| `List` | Page through queue-scoped request receipt history | +| `GetRequestHistoryByID` | Read every retained request-log event for one sqid | +| `GetRequestHistoryByChangeURI` | Read retained request histories for one exact pinned change URI | + +The gateway owns request receipts, current-status projections, and request-log reads. `List` is ordered by immutable gateway receipt time. The queue projection may briefly lag the authoritative summaries returned by the request-summary methods while materialization converges. ### Orchestrator Service diff --git a/service/submitqueue/gateway/server/main.go b/service/submitqueue/gateway/server/main.go index 1bfeb252..38f3f9be 100644 --- a/service/submitqueue/gateway/server/main.go +++ b/service/submitqueue/gateway/server/main.go @@ -57,6 +57,7 @@ type GatewayServer struct { cancelController *controller.CancelController requestSummaryController *controller.RequestSummaryController listController *controller.ListController + requestHistoryController *controller.RequestHistoryController } // Ping delegates to the controller @@ -114,6 +115,24 @@ func (s *GatewayServer) List(ctx context.Context, req *pb.ListRequest) (*pb.List return mapper.ListResultToProto(result), nil } +// GetRequestHistoryByID maps the wire request to an entity, delegates to the controller, and maps the result back to the wire response. +func (s *GatewayServer) GetRequestHistoryByID(ctx context.Context, req *pb.GetRequestHistoryByIDRequest) (*pb.GetRequestHistoryByIDResponse, error) { + events, err := s.requestHistoryController.GetRequestHistoryByID(ctx, mapper.ProtoToGetRequestHistoryByIDRequest(req)) + if err != nil { + return nil, err + } + return &pb.GetRequestHistoryByIDResponse{Events: mapper.HistoryEventsToProto(events)}, nil +} + +// GetRequestHistoryByChangeURI maps the wire request to an entity, delegates to the controller, and maps the result back to the wire response. +func (s *GatewayServer) GetRequestHistoryByChangeURI(ctx context.Context, req *pb.GetRequestHistoryByChangeURIRequest) (*pb.GetRequestHistoryByChangeURIResponse, error) { + histories, err := s.requestHistoryController.GetRequestHistoryByChangeURI(ctx, mapper.ProtoToGetRequestHistoryByChangeURIRequest(req)) + if err != nil { + return nil, err + } + return &pb.GetRequestHistoryByChangeURIResponse{Histories: mapper.RequestHistoriesToProto(histories)}, nil +} + func gatewayStatusError(err error) error { switch { case controller.IsRequestNotFound(err): @@ -279,15 +298,15 @@ func run() error { }, )) - // Initialize storage from the shared app database connection. The land - // controller writes to this store directly; cancel and request-summary - // controllers use its request stores. The log consumer registered below - // persists entries published by the orchestrator's normal pipeline. + // Initialize gateway-owned storage from the shared app database connection. + // Land creates receipt projections, request-summary and List controllers + // read materialized views, and request-history controllers read retained + // logs. Normal log-topic persistence and terminal DLQ repair both use the + // materializer. store, err := mysqlstorage.NewStorage(appDB, scope.SubScope("storage")) if err != nil { return fmt.Errorf("failed to create storage: %w", err) } - // Load queue configurations from YAML. Path is required so the gateway // can reject requests for unknown queues at the edge. queueConfigPath := os.Getenv("QUEUE_CONFIG_PATH") @@ -310,12 +329,19 @@ func run() error { store.GetRequestURIStore(), ) listController := controller.NewListController(logger.Sugar(), scope, store.GetRequestQueueSummaryStore(), queueConfigs) + requestHistoryController := controller.NewRequestHistoryController( + logger.Sugar(), + scope, + store.GetRequestLogStore(), + store.GetRequestURIStore(), + ) gatewayServer := &GatewayServer{ pingController: pingController, landController: landController, cancelController: cancelController, requestSummaryController: requestSummaryController, listController: listController, + requestHistoryController: requestHistoryController, } pb.RegisterSubmitQueueGatewayServer(grpcServer, gatewayServer) diff --git a/service/submitqueue/gateway/server/mapper/BUILD.bazel b/service/submitqueue/gateway/server/mapper/BUILD.bazel index 3ef3401b..76271ee8 100644 --- a/service/submitqueue/gateway/server/mapper/BUILD.bazel +++ b/service/submitqueue/gateway/server/mapper/BUILD.bazel @@ -6,6 +6,7 @@ go_library( "cancel.go", "land.go", "list.go", + "request_history.go", "request_summary.go", ], importpath = "github.com/uber/submitqueue/service/submitqueue/gateway/server/mapper", @@ -25,6 +26,7 @@ go_test( "cancel_test.go", "land_test.go", "list_test.go", + "request_history_test.go", "request_summary_test.go", ], embed = [":go_default_library"], diff --git a/service/submitqueue/gateway/server/mapper/request_history.go b/service/submitqueue/gateway/server/mapper/request_history.go new file mode 100644 index 00000000..c50864b3 --- /dev/null +++ b/service/submitqueue/gateway/server/mapper/request_history.go @@ -0,0 +1,56 @@ +// Copyright (c) 2025 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mapper + +import ( + pb "github.com/uber/submitqueue/api/submitqueue/gateway/protopb" + "github.com/uber/submitqueue/submitqueue/entity" +) + +// ProtoToGetRequestHistoryByIDRequest maps the wire request to the entity request the controller operates on. +func ProtoToGetRequestHistoryByIDRequest(req *pb.GetRequestHistoryByIDRequest) entity.GetRequestHistoryByIDRequest { + return entity.GetRequestHistoryByIDRequest{ID: req.GetSqid()} +} + +// ProtoToGetRequestHistoryByChangeURIRequest maps the wire request to the entity request the controller operates on. +func ProtoToGetRequestHistoryByChangeURIRequest(req *pb.GetRequestHistoryByChangeURIRequest) entity.GetRequestHistoryByChangeURIRequest { + return entity.GetRequestHistoryByChangeURIRequest{ChangeURI: req.GetChangeUri()} +} + +// HistoryEventsToProto maps retained request-log events to wire history events. +func HistoryEventsToProto(logs []entity.RequestLog) []*pb.HistoryEvent { + events := make([]*pb.HistoryEvent, len(logs)) + for i, log := range logs { + events[i] = &pb.HistoryEvent{ + TimestampMs: log.TimestampMs, + Status: string(log.Status), + LastError: log.LastError, + Metadata: cloneStringMap(log.Metadata), + } + } + return events +} + +// RequestHistoriesToProto maps grouped retained histories to the wire representation. +func RequestHistoriesToProto(histories []entity.RequestHistory) []*pb.RequestHistory { + result := make([]*pb.RequestHistory, len(histories)) + for i, history := range histories { + result[i] = &pb.RequestHistory{ + Sqid: history.RequestID, + Events: HistoryEventsToProto(history.Events), + } + } + return result +} diff --git a/service/submitqueue/gateway/server/mapper/request_history_test.go b/service/submitqueue/gateway/server/mapper/request_history_test.go new file mode 100644 index 00000000..79b565d5 --- /dev/null +++ b/service/submitqueue/gateway/server/mapper/request_history_test.go @@ -0,0 +1,60 @@ +// Copyright (c) 2025 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mapper + +import ( + "testing" + + "github.com/stretchr/testify/assert" + pb "github.com/uber/submitqueue/api/submitqueue/gateway/protopb" + "github.com/uber/submitqueue/submitqueue/entity" +) + +func TestProtoToGetRequestHistoryRequests(t *testing.T) { + assert.Equal(t, + entity.GetRequestHistoryByIDRequest{ID: "q/1"}, + ProtoToGetRequestHistoryByIDRequest(&pb.GetRequestHistoryByIDRequest{Sqid: "q/1"}), + ) + assert.Equal(t, + entity.GetRequestHistoryByChangeURIRequest{ChangeURI: "uri"}, + ProtoToGetRequestHistoryByChangeURIRequest(&pb.GetRequestHistoryByChangeURIRequest{ChangeUri: "uri"}), + ) +} + +func TestHistoryEventsToProto(t *testing.T) { + events := HistoryEventsToProto([]entity.RequestLog{{ + TimestampMs: 10, + Status: entity.RequestStatusError, + LastError: "failed", + Metadata: map[string]string{"step": "build"}, + }}) + + assert.Equal(t, &pb.HistoryEvent{ + TimestampMs: 10, + Status: string(entity.RequestStatusError), + LastError: "failed", + Metadata: map[string]string{"step": "build"}, + }, events[0]) +} + +func TestRequestHistoriesToProto(t *testing.T) { + histories := RequestHistoriesToProto([]entity.RequestHistory{{ + RequestID: "q/1", + Events: []entity.RequestLog{{TimestampMs: 10, Status: entity.RequestStatusAccepted}}, + }}) + + assert.Equal(t, "q/1", histories[0].Sqid) + assert.Equal(t, string(entity.RequestStatusAccepted), histories[0].Events[0].Status) +} diff --git a/submitqueue/entity/BUILD.bazel b/submitqueue/entity/BUILD.bazel index 6f6a9190..b39a26ef 100644 --- a/submitqueue/entity/BUILD.bazel +++ b/submitqueue/entity/BUILD.bazel @@ -18,6 +18,7 @@ go_library( "queue.go", "queue_config.go", "request.go", + "request_history.go", "request_log.go", "request_summary.go", "speculation_path_build.go", diff --git a/submitqueue/entity/request_history.go b/submitqueue/entity/request_history.go new file mode 100644 index 00000000..9ca585fb --- /dev/null +++ b/submitqueue/entity/request_history.go @@ -0,0 +1,35 @@ +// Copyright (c) 2025 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package entity + +// GetRequestHistoryByIDRequest identifies one retained request history by request ID. +type GetRequestHistoryByIDRequest struct { + // ID is the globally unique identifier of the request. + ID string +} + +// GetRequestHistoryByChangeURIRequest identifies retained histories by an exact pinned change URI. +type GetRequestHistoryByChangeURIRequest struct { + // ChangeURI is the exact change URI supplied in a Land request. + ChangeURI string +} + +// RequestHistory groups retained events for one request. +type RequestHistory struct { + // RequestID is the globally unique identifier of the request. + RequestID string + // Events are retained request-log events ordered chronologically. + Events []RequestLog +} diff --git a/submitqueue/gateway/README.md b/submitqueue/gateway/README.md index 5903f009..f1f64062 100644 --- a/submitqueue/gateway/README.md +++ b/submitqueue/gateway/README.md @@ -1,25 +1,24 @@ # SubmitQueue Gateway -The gateway is the RPC entry point to SubmitQueue. It accepts `Land`, `Cancel`, -`Status`, and `Ping` calls, validates them at the edge, and hands work off to the -orchestrator pipeline asynchronously via the message queue. +The gateway is the RPC entry point to SubmitQueue. It accepts `Land`, `Cancel`, `GetRequestSummaryByID`, `GetRequestSummaryByChangeURI`, `List`, `GetRequestHistoryByID`, `GetRequestHistoryByChangeURI`, and `Ping` calls. It validates edge-owned input and hands asynchronous work to the orchestrator through the message queue. -## Request log ownership +## Request receipts and current summaries + +`Land` creates gateway-owned receipt projections before publishing the request: + +- An authoritative request summary keyed by sqid. +- One exact change-URI mapping per submitted URI. +- A queue-ordered receipt projection used by `List`. -The gateway is the **sole owner of the request log** — the only service that -both writes and reads it. No other service persists or reads request log -entries: +`GetRequestSummaryByID` and `GetRequestSummaryByChangeURI` read authoritative summaries. `List` reads the queue projection and may briefly lag those summaries while eventual repair converges. + +## Request log ownership -- For statuses it produces synchronously (`accepted` on `Land`, `cancelling` on - `Cancel`), the gateway writes directly to storage so the entry is visible the - moment the RPC returns. -- For statuses produced downstream, the orchestrator only *publishes* entries to - the `log` topic via `submitqueue/core/request.PublishLog`. The gateway runs a - consumer that drains the `log` topic and persists each entry to storage. +The gateway owns the request log read model and is the only service that reads it. -Reads are likewise gateway-only: the `Status` and `Cancel` RPCs read the request -log directly from storage. The orchestrator only *publishes* log entries and -never touches the request log store. +- For statuses produced synchronously by the gateway, such as `accepted` on `Land` and `cancelling` on `Cancel`, the gateway persists the event through the shared request-log materializer before returning or publishing. +- For statuses produced downstream, the orchestrator publishes entries to the `log` topic through `submitqueue/core/request.PublishLog`. The gateway consumes that topic and persists each entry through the same materializer. +- Orchestrator DLQ reconciliation materializes terminal repairs directly so the DLQ delivery remains unacknowledged until the log and public projections converge. +- `GetRequestHistoryByID` and `GetRequestHistoryByChangeURI` read retained request-log rows directly. -This keeps a single service responsible for the request log while letting the -orchestrator remain free of storage writes for it. +The materializer appends every audit event, selects the current authoritative winner, and repairs the queue projection. The normal orchestrator pipeline does not read or write the request-log store directly. diff --git a/submitqueue/gateway/controller/BUILD.bazel b/submitqueue/gateway/controller/BUILD.bazel index c116f0ce..282d8950 100644 --- a/submitqueue/gateway/controller/BUILD.bazel +++ b/submitqueue/gateway/controller/BUILD.bazel @@ -8,6 +8,7 @@ go_library( "list.go", "ping.go", "read_errors.go", + "request_history.go", "request_summary.go", ], importpath = "github.com/uber/submitqueue/submitqueue/gateway/controller", @@ -36,6 +37,7 @@ go_test( "land_test.go", "list_test.go", "ping_test.go", + "request_history_test.go", "request_summary_test.go", "storage_fixture_test.go", ], diff --git a/submitqueue/gateway/controller/request_history.go b/submitqueue/gateway/controller/request_history.go new file mode 100644 index 00000000..d306d4a5 --- /dev/null +++ b/submitqueue/gateway/controller/request_history.go @@ -0,0 +1,152 @@ +// Copyright (c) 2025 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controller + +import ( + "context" + "fmt" + "sort" + "strconv" + "strings" + + "github.com/uber-go/tally" + "github.com/uber/submitqueue/platform/errs" + "github.com/uber/submitqueue/platform/metrics" + "github.com/uber/submitqueue/submitqueue/entity" + "github.com/uber/submitqueue/submitqueue/extension/storage" + "go.uber.org/zap" +) + +// RequestHistoryController handles retained request-log history lookups. +type RequestHistoryController struct { + logger *zap.SugaredLogger + metricsScope tally.Scope + requestLogStore storage.RequestLogStore + requestURIStore storage.RequestURIStore +} + +// NewRequestHistoryController creates a gateway request-history controller. +func NewRequestHistoryController(logger *zap.SugaredLogger, scope tally.Scope, requestLogStore storage.RequestLogStore, requestURIStore storage.RequestURIStore) *RequestHistoryController { + return &RequestHistoryController{ + logger: logger, + metricsScope: scope.SubScope("request_history_controller"), + requestLogStore: requestLogStore, + requestURIStore: requestURIStore, + } +} + +// GetRequestHistoryByID returns every retained request-log event for one sqid. +func (c *RequestHistoryController) GetRequestHistoryByID(ctx context.Context, req entity.GetRequestHistoryByIDRequest) (logs []entity.RequestLog, retErr error) { + op := metrics.Begin(c.metricsScope, "get_by_id") + defer func() { op.Complete(retErr) }() + + if err := validateStoredIdentifier("sqid", req.ID); err != nil { + return nil, fmt.Errorf("GetRequestHistoryByID invalid request: %w", err) + } + + logs, err := c.requestLogStore.List(ctx, req.ID) + if err != nil { + if storage.IsNotFound(err) { + return nil, errs.NewUserError(&RequestNotFoundError{Sqid: req.ID}) + } + return nil, fmt.Errorf("GetRequestHistoryByID failed to list request logs sqid=%s: %w", req.ID, err) + } + + c.logger.Debugw("request history retrieved", + "sqid", req.ID, + "event_count", len(logs), + ) + return logs, nil +} + +// GetRequestHistoryByChangeURI returns retained histories for an exact pinned change URI. +func (c *RequestHistoryController) GetRequestHistoryByChangeURI(ctx context.Context, req entity.GetRequestHistoryByChangeURIRequest) (result []entity.RequestHistory, retErr error) { + op := metrics.Begin(c.metricsScope, "get_by_change_uri") + defer func() { op.Complete(retErr) }() + + if err := validateStoredIdentifier("change URI", req.ChangeURI); err != nil { + return nil, fmt.Errorf("GetRequestHistoryByChangeURI invalid request: %w", err) + } + + mappings, err := c.requestURIStore.ListByURI(ctx, req.ChangeURI, maxChangeRequestResults+1) + if err != nil { + return nil, fmt.Errorf("GetRequestHistoryByChangeURI failed to list request mappings change_uri=%s: %w", req.ChangeURI, err) + } + if len(mappings) == 0 { + return nil, errs.NewUserError(&RequestNotFoundError{ChangeURI: req.ChangeURI}) + } + if len(mappings) > maxChangeRequestResults { + return nil, errs.NewUserError(&TooManyChangeRequestsError{ChangeURI: req.ChangeURI, Limit: maxChangeRequestResults}) + } + + histories := make([]requestHistoryWithCounter, 0, len(mappings)) + for _, mapping := range mappings { + logs, err := c.requestLogStore.List(ctx, mapping.RequestID) + if err != nil { + if storage.IsNotFound(err) { + continue + } + return nil, fmt.Errorf("GetRequestHistoryByChangeURI failed to list request logs change_uri=%s sqid=%s: %w", req.ChangeURI, mapping.RequestID, err) + } + counter, err := sqidCounter(mapping.RequestID) + if err != nil { + return nil, &InternalConsistencyError{Message: fmt.Sprintf("invalid mapped sqid %q: %v", mapping.RequestID, err)} + } + histories = append(histories, requestHistoryWithCounter{ + counter: counter, + history: entity.RequestHistory{ + RequestID: mapping.RequestID, + Events: append([]entity.RequestLog{}, logs...), + }, + }) + } + if len(histories) == 0 { + return nil, errs.NewUserError(&RequestNotFoundError{ChangeURI: req.ChangeURI}) + } + + sort.Slice(histories, func(i, j int) bool { + if histories[i].counter != histories[j].counter { + return histories[i].counter < histories[j].counter + } + return histories[i].history.RequestID < histories[j].history.RequestID + }) + + result = make([]entity.RequestHistory, len(histories)) + for i, history := range histories { + result[i] = history.history + } + c.logger.Debugw("request histories retrieved", + "change_uri", req.ChangeURI, + "request_count", len(result), + ) + return result, nil +} + +type requestHistoryWithCounter struct { + counter int64 + history entity.RequestHistory +} + +func sqidCounter(sqid string) (int64, error) { + separator := strings.LastIndexByte(sqid, '/') + if separator < 0 || separator == len(sqid)-1 { + return 0, fmt.Errorf("missing numeric counter") + } + counter, err := strconv.ParseInt(sqid[separator+1:], 10, 64) + if err != nil || counter <= 0 { + return 0, fmt.Errorf("invalid numeric counter") + } + return counter, nil +} diff --git a/submitqueue/gateway/controller/request_history_test.go b/submitqueue/gateway/controller/request_history_test.go new file mode 100644 index 00000000..2fd814ae --- /dev/null +++ b/submitqueue/gateway/controller/request_history_test.go @@ -0,0 +1,209 @@ +// Copyright (c) 2025 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controller + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/uber-go/tally" + "github.com/uber/submitqueue/platform/errs" + "github.com/uber/submitqueue/submitqueue/entity" + "github.com/uber/submitqueue/submitqueue/extension/storage" + storagemock "github.com/uber/submitqueue/submitqueue/extension/storage/mock" + "go.uber.org/mock/gomock" + "go.uber.org/zap" +) + +func TestGetRequestHistoryByID(t *testing.T) { + ctrl := gomock.NewController(t) + logStore := storagemock.NewMockRequestLogStore(ctrl) + uriStore := storagemock.NewMockRequestURIStore(ctrl) + logStore.EXPECT().List(gomock.Any(), "queue/1").Return([]entity.RequestLog{ + {RequestID: "queue/1", TimestampMs: 10, Status: entity.RequestStatusAccepted, Metadata: map[string]string{}}, + {RequestID: "queue/1", TimestampMs: 20, Status: entity.RequestStatusStarted, LastError: "retry", Metadata: map[string]string{"attempt": "1"}}, + {RequestID: "queue/1", TimestampMs: 20, Status: entity.RequestStatusStarted, LastError: "retry", Metadata: map[string]string{"attempt": "1"}}, + }, nil) + + controller := NewRequestHistoryController(zap.NewNop().Sugar(), tally.NoopScope, logStore, uriStore) + events, err := controller.GetRequestHistoryByID(context.Background(), entity.GetRequestHistoryByIDRequest{ID: "queue/1"}) + + require.NoError(t, err) + require.Len(t, events, 3) + assert.Equal(t, []entity.RequestStatus{entity.RequestStatusAccepted, entity.RequestStatusStarted, entity.RequestStatusStarted}, []entity.RequestStatus{events[0].Status, events[1].Status, events[2].Status}) + assert.Equal(t, int64(20), events[1].TimestampMs) + assert.Equal(t, "retry", events[1].LastError) + assert.Equal(t, map[string]string{"attempt": "1"}, events[1].Metadata) +} + +func TestGetRequestHistoryByChangeURI(t *testing.T) { + ctrl := gomock.NewController(t) + logStore := storagemock.NewMockRequestLogStore(ctrl) + uriStore := storagemock.NewMockRequestURIStore(ctrl) + uriStore.EXPECT().ListByURI(gomock.Any(), "uri", 101).Return([]entity.RequestURI{ + {ChangeURI: "uri", RequestID: "queue/10"}, + {ChangeURI: "uri", RequestID: "b/2"}, + {ChangeURI: "uri", RequestID: "missing/3"}, + {ChangeURI: "uri", RequestID: "queue/1"}, + {ChangeURI: "uri", RequestID: "a/2"}, + }, nil) + logStore.EXPECT().List(gomock.Any(), "queue/10").Return([]entity.RequestLog{{RequestID: "queue/10", TimestampMs: 10, Status: entity.RequestStatusLanded}}, nil) + logStore.EXPECT().List(gomock.Any(), "b/2").Return([]entity.RequestLog{{RequestID: "b/2", TimestampMs: 2, Status: entity.RequestStatusStarted}}, nil) + logStore.EXPECT().List(gomock.Any(), "missing/3").Return(nil, storage.ErrNotFound) + logStore.EXPECT().List(gomock.Any(), "queue/1").Return([]entity.RequestLog{{RequestID: "queue/1", TimestampMs: 1, Status: entity.RequestStatusAccepted}}, nil) + logStore.EXPECT().List(gomock.Any(), "a/2").Return([]entity.RequestLog{{RequestID: "a/2", TimestampMs: 2, Status: entity.RequestStatusError}}, nil) + + controller := NewRequestHistoryController(zap.NewNop().Sugar(), tally.NoopScope, logStore, uriStore) + histories, err := controller.GetRequestHistoryByChangeURI(context.Background(), entity.GetRequestHistoryByChangeURIRequest{ChangeURI: "uri"}) + + require.NoError(t, err) + require.Len(t, histories, 4) + assert.Equal(t, []string{"queue/1", "a/2", "b/2", "queue/10"}, []string{ + histories[0].RequestID, + histories[1].RequestID, + histories[2].RequestID, + histories[3].RequestID, + }) +} + +func TestHistoryErrors(t *testing.T) { + backendErr := fmt.Errorf("backend down") + tests := []struct { + name string + call func(*RequestHistoryController) error + setup func(*storagemock.MockRequestLogStore, *storagemock.MockRequestURIStore) + wantInvalid bool + wantNotFound bool + wantTooMany bool + wantInternal bool + wantUser bool + }{ + { + name: "empty sqid", + call: func(c *RequestHistoryController) error { + _, err := c.GetRequestHistoryByID(context.Background(), entity.GetRequestHistoryByIDRequest{}) + return err + }, + wantInvalid: true, + wantUser: true, + }, + { + name: "empty change URI", + call: func(c *RequestHistoryController) error { + _, err := c.GetRequestHistoryByChangeURI(context.Background(), entity.GetRequestHistoryByChangeURIRequest{}) + return err + }, + wantInvalid: true, + wantUser: true, + }, + { + name: "sqid not found", + setup: func(logStore *storagemock.MockRequestLogStore, _ *storagemock.MockRequestURIStore) { + logStore.EXPECT().List(gomock.Any(), "missing/1").Return(nil, storage.ErrNotFound) + }, + call: func(c *RequestHistoryController) error { + _, err := c.GetRequestHistoryByID(context.Background(), entity.GetRequestHistoryByIDRequest{ID: "missing/1"}) + return err + }, + wantNotFound: true, + wantUser: true, + }, + { + name: "sqid storage failure", + setup: func(logStore *storagemock.MockRequestLogStore, _ *storagemock.MockRequestURIStore) { + logStore.EXPECT().List(gomock.Any(), "queue/1").Return(nil, backendErr) + }, + call: func(c *RequestHistoryController) error { + _, err := c.GetRequestHistoryByID(context.Background(), entity.GetRequestHistoryByIDRequest{ID: "queue/1"}) + return err + }, + }, + { + name: "change URI not found", + setup: func(_ *storagemock.MockRequestLogStore, uriStore *storagemock.MockRequestURIStore) { + uriStore.EXPECT().ListByURI(gomock.Any(), "uri", 101).Return(nil, nil) + }, + call: func(c *RequestHistoryController) error { + _, err := c.GetRequestHistoryByChangeURI(context.Background(), entity.GetRequestHistoryByChangeURIRequest{ChangeURI: "uri"}) + return err + }, + wantNotFound: true, + wantUser: true, + }, + { + name: "too many change matches", + setup: func(_ *storagemock.MockRequestLogStore, uriStore *storagemock.MockRequestURIStore) { + uriStore.EXPECT().ListByURI(gomock.Any(), "uri", 101).Return(make([]entity.RequestURI, 101), nil) + }, + call: func(c *RequestHistoryController) error { + _, err := c.GetRequestHistoryByChangeURI(context.Background(), entity.GetRequestHistoryByChangeURIRequest{ChangeURI: "uri"}) + return err + }, + wantTooMany: true, + wantUser: true, + }, + { + name: "all mapped logs absent", + setup: func(logStore *storagemock.MockRequestLogStore, uriStore *storagemock.MockRequestURIStore) { + uriStore.EXPECT().ListByURI(gomock.Any(), "uri", 101).Return([]entity.RequestURI{{RequestID: "queue/1"}}, nil) + logStore.EXPECT().List(gomock.Any(), "queue/1").Return(nil, storage.ErrNotFound) + }, + call: func(c *RequestHistoryController) error { + _, err := c.GetRequestHistoryByChangeURI(context.Background(), entity.GetRequestHistoryByChangeURIRequest{ChangeURI: "uri"}) + return err + }, + wantNotFound: true, + wantUser: true, + }, + { + name: "mapped sqid malformed", + setup: func(logStore *storagemock.MockRequestLogStore, uriStore *storagemock.MockRequestURIStore) { + uriStore.EXPECT().ListByURI(gomock.Any(), "uri", 101).Return([]entity.RequestURI{{RequestID: "malformed"}}, nil) + logStore.EXPECT().List(gomock.Any(), "malformed").Return([]entity.RequestLog{{RequestID: "malformed"}}, nil) + }, + call: func(c *RequestHistoryController) error { + _, err := c.GetRequestHistoryByChangeURI(context.Background(), entity.GetRequestHistoryByChangeURIRequest{ChangeURI: "uri"}) + return err + }, + wantInternal: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + logStore := storagemock.NewMockRequestLogStore(ctrl) + uriStore := storagemock.NewMockRequestURIStore(ctrl) + if tt.setup != nil { + tt.setup(logStore, uriStore) + } + controller := NewRequestHistoryController(zap.NewNop().Sugar(), tally.NoopScope, logStore, uriStore) + + err := tt.call(controller) + + require.Error(t, err) + if tt.wantInvalid { + assert.True(t, IsInvalidRequest(err)) + } + 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 81094004..8a163e32 100644 --- a/test/e2e/submitqueue/harness_test.go +++ b/test/e2e/submitqueue/harness_test.go @@ -19,8 +19,7 @@ package e2e_test // observe outcomes two ways: // // - black-box, by polling the GetRequestSummaryByID 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. +// - black-box, by reading the ordered stage progression through GetRequestHistoryByID. // // Convergence is bounded by require.Eventually (persistTimeout / // persistPollInterval) rather than time.Sleep: the pipeline consumers run inside @@ -100,23 +99,21 @@ func (s *E2EIntegrationSuite) awaitTerminal(sqid string) entity.RequestStatus { return last } -// 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 -// (the internal RequestState is point-in-time, see terminalState). +// timeline returns the ordered customer-facing status history through +// GetRequestHistoryByID. func (s *E2EIntegrationSuite) timeline(sqid string) []entity.RequestStatus { t := s.T() - logs, err := s.requestLog.List(s.ctx, sqid) - require.NoError(t, err, "failed to list request_log for %s", sqid) - statuses := make([]entity.RequestStatus, len(logs)) - for i, l := range logs { - statuses[i] = l.Status + resp, err := s.gatewayClient.GetRequestHistoryByID(s.ctx, &gatewaypb.GetRequestHistoryByIDRequest{Sqid: sqid}) + require.NoError(t, err, "GetRequestHistoryByID failed for %s", sqid) + statuses := make([]entity.RequestStatus, len(resp.Events)) + for i, event := range resp.Events { + statuses[i] = entity.RequestStatus(event.Status) } return statuses } // assertStatusesInOrder asserts that want appears as an ordered subsequence of -// the request_log status timeline. It tolerates intermediate statuses (so it is +// the GetRequestHistoryByID status timeline. It tolerates intermediate statuses (so it is // not a change-detector), asserting only the relative order of the statuses that // matter. func (s *E2EIntegrationSuite) assertStatusesInOrder(sqid string, want ...entity.RequestStatus) { @@ -129,7 +126,7 @@ func (s *E2EIntegrationSuite) assertStatusesInOrder(sqid string, want ...entity. } } assert.Equalf(t, len(want), matched, - "request_log for %s should contain %v as an ordered subsequence; got %v", + "GetRequestHistoryByID for %s should contain %v as an ordered subsequence; got %v", sqid, want, got) } diff --git a/test/e2e/submitqueue/suite_test.go b/test/e2e/submitqueue/suite_test.go index 19d27bc3..f23cb7d0 100644 --- a/test/e2e/submitqueue/suite_test.go +++ b/test/e2e/submitqueue/suite_test.go @@ -51,22 +51,19 @@ type E2EIntegrationSuite struct { stack *testutil.ComposeStack gatewayClient gatewaypb.SubmitQueueGatewayClient orchestratorClient orchestratorpb.SubmitQueueOrchestratorClient - db *sql.DB // App database - queueDB *sql.DB // Queue database - requestLog storage.RequestLogStore // White-box view of the request_log status timeline (app DB) - requestStore storage.RequestStore // White-box view of the internal RequestState (app DB) + db *sql.DB // App database + queueDB *sql.DB // Queue database + requestStore storage.RequestStore // White-box view of the internal RequestState (app DB) } func TestE2EIntegration(t *testing.T) { suite.Run(t, new(E2EIntegrationSuite)) } -// The gateway log consumer runs inside the gateway-service container, so this -// suite can only observe persistence black-box through the Status RPC — there is -// no in-process channel/HookSignal to wait on across the container boundary. A -// bounded poll is therefore the deterministic-enough analog: persistTimeout is a -// safety net (a failure here means something is genuinely stuck, not a timing -// race), and persistPollInterval bounds how often we re-query. +// The gateway log consumer runs inside the gateway-service container, so there +// is no in-process signal to wait on across the container boundary. A bounded +// GetRequestSummaryByID poll is therefore the deterministic-enough analog: persistTimeout +// is a safety net, and persistPollInterval bounds how often we re-query. const ( persistTimeout = 30 * time.Second persistPollInterval = 500 * time.Millisecond @@ -111,9 +108,7 @@ func (s *E2EIntegrationSuite) SetupSuite() { s.log.Logf("Schemas applied successfully") - // White-box handles on the app DB: the request_log audit trail (ordered - // status history) and the operating store (point-in-time RequestState). - s.requestLog = storagemysql.NewRequestLogStore(s.db, tally.NoopScope) + // White-box handle on the operating store for point-in-time RequestState. s.requestStore = storagemysql.NewRequestStore(s.db, tally.NoopScope) // Connect to Gateway gRPC service @@ -181,15 +176,15 @@ func (s *E2EIntegrationSuite) TestPingOrchestrator() { // pipeline to terminal success on the fully-hermetic e2e-test-queue (no // conflicts, fake build succeeds, noop runway signals SUCCEEDED for both the // merge-conflict check and the merge). It asserts three views: the black-box -// terminal Status, the ordered request_log status history, and the internal -// RequestState in the operating store. +// terminal request summary, the public GetRequestHistoryByID timeline, and the internal RequestState +// in the operating store. // // This also exercises the request-log ownership invariant end-to-end: the // orchestrator only *publishes* log entries to the log topic (it never writes // the request log itself), and the gateway's log consumer drains that topic and // persists them. Every status below except the synchronous "accepted" reaches // storage only via that cross-service publish→consume→persist path, so its -// presence in the timeline proves the path works. +// presence in GetRequestHistoryByID proves the path works. func (s *E2EIntegrationSuite) TestLand_HappyPath_ReachesLanded() { sqid := s.land("e2e-test-queue", "github://github.example.com/uber/e2e-service/pull/123/abcdef0123456789abcdef0123456789abcdef01") s.log.Logf("Land (happy path) succeeded: sqid=%s; waiting for landed", sqid) @@ -197,12 +192,11 @@ func (s *E2EIntegrationSuite) TestLand_HappyPath_ReachesLanded() { // Black-box: the customer-facing status reaches landed. s.awaitStatus(sqid, entity.RequestStatusLanded) - // White-box (status history): the request_log is the only ordered trail. All - // status entries for a request share its request_id partition on the log - // topic (ordered delivery) and the terminal "landed" is published last, so - // once "landed" is observed the earlier statuses are already persisted. This - // is a tolerant ordered-subsequence match — display statuses the pipeline - // does not emit (e.g. validating, speculating, building) are omitted. + // Black-box history: all status entries for a request share its request_id + // partition on the log topic, and the terminal "landed" is published last. + // Once "landed" is observed, GetRequestHistoryByID must expose the earlier statuses. + // This is a tolerant ordered-subsequence match because the pipeline does not + // emit every possible display status. s.assertStatusesInOrder(sqid, entity.RequestStatusAccepted, entity.RequestStatusStarted, @@ -218,6 +212,82 @@ func (s *E2EIntegrationSuite) TestLand_HappyPath_ReachesLanded() { "operating store should show request %s in terminal state landed", sqid) } +// TestReadAPIs validates all five request read endpoints against receipts +// created through the public Land API. +func (s *E2EIntegrationSuite) TestReadAPIs() { + t := s.T() + const ( + queue = "e2e-test-queue" + changeURI = "github://uber/e2e-read-apis/pull/456/abcdef0123456789abcdef0123456789abcdef01" + ) + firstSqid := s.land(queue, changeURI) + secondSqid := s.land(queue, changeURI) + s.awaitStatus(firstSqid, entity.RequestStatusLanded) + s.awaitStatus(secondSqid, entity.RequestStatusError) + + firstSummary, err := s.gatewayClient.GetRequestSummaryByID(s.ctx, &gatewaypb.GetRequestSummaryByIDRequest{Sqid: firstSqid}) + require.NoError(t, err) + require.NotNil(t, firstSummary.Request) + assert.Equal(t, firstSqid, firstSummary.Request.Sqid) + assert.Equal(t, queue, firstSummary.Request.Queue) + assert.Equal(t, []string{changeURI}, firstSummary.Request.ChangeUris) + + secondSummary, err := s.gatewayClient.GetRequestSummaryByID(s.ctx, &gatewaypb.GetRequestSummaryByIDRequest{Sqid: secondSqid}) + require.NoError(t, err) + require.NotNil(t, secondSummary.Request) + assert.Contains(t, secondSummary.Request.LastError, firstSqid) + + summariesByChange, err := s.gatewayClient.GetRequestSummaryByChangeURI(s.ctx, &gatewaypb.GetRequestSummaryByChangeURIRequest{ChangeUri: changeURI}) + require.NoError(t, err) + require.Len(t, summariesByChange.Requests, 2) + expectedNewestFirst := []string{firstSqid, secondSqid} + if secondSummary.Request.ReceivedAtMs > firstSummary.Request.ReceivedAtMs || + (secondSummary.Request.ReceivedAtMs == firstSummary.Request.ReceivedAtMs && secondSqid > firstSqid) { + expectedNewestFirst[0], expectedNewestFirst[1] = expectedNewestFirst[1], expectedNewestFirst[0] + } + assert.Equal(t, expectedNewestFirst, []string{summariesByChange.Requests[0].Sqid, summariesByChange.Requests[1].Sqid}) + + receivedAtOrAfterMs := min(firstSummary.Request.ReceivedAtMs, secondSummary.Request.ReceivedAtMs) + receivedBeforeMs := max(firstSummary.Request.ReceivedAtMs, secondSummary.Request.ReceivedAtMs) + 1 + var listedSqids []string + var pageToken string + for { + listResponse, listErr := s.gatewayClient.List(s.ctx, &gatewaypb.ListRequest{ + Queue: queue, + ReceivedAtOrAfterMs: receivedAtOrAfterMs, + ReceivedBeforeMs: receivedBeforeMs, + PageSize: 1, + PageToken: pageToken, + }) + require.NoError(t, listErr) + for _, request := range listResponse.Requests { + if request.Sqid == firstSqid || request.Sqid == secondSqid { + listedSqids = append(listedSqids, request.Sqid) + } + } + pageToken = listResponse.NextPageToken + if pageToken == "" { + break + } + } + assert.Equal(t, expectedNewestFirst, listedSqids) + + historyByID, err := s.gatewayClient.GetRequestHistoryByID(s.ctx, &gatewaypb.GetRequestHistoryByIDRequest{Sqid: firstSqid}) + require.NoError(t, err) + require.NotEmpty(t, historyByID.Events) + assert.Equal(t, string(entity.RequestStatusAccepted), historyByID.Events[0].Status) + + historyByChange, err := s.gatewayClient.GetRequestHistoryByChangeURI(s.ctx, &gatewaypb.GetRequestHistoryByChangeURIRequest{ChangeUri: changeURI}) + require.NoError(t, err) + require.Len(t, historyByChange.Histories, 2) + assert.Equal(t, []string{firstSqid, secondSqid}, []string{historyByChange.Histories[0].Sqid, historyByChange.Histories[1].Sqid}) + require.NotEmpty(t, historyByChange.Histories[0].Events) + require.NotEmpty(t, historyByChange.Histories[1].Events) + secondEvents := historyByChange.Histories[1].Events + assert.Equal(t, string(entity.RequestStatusError), secondEvents[len(secondEvents)-1].Status) + assert.Equal(t, secondSummary.Request.LastError, secondEvents[len(secondEvents)-1].LastError) +} + // TestCancelRequest_InvalidSqid verifies the gateway rejects an empty sqid // synchronously before publishing anything to the cancel queue. func (s *E2EIntegrationSuite) TestCancelRequest_InvalidSqid() { @@ -252,9 +322,8 @@ func (s *E2EIntegrationSuite) TestCancel_RecordsIntent() { _, err := s.gatewayClient.Cancel(s.ctx, &gatewaypb.CancelRequest{Sqid: sqid, Reason: "e2e cancel test"}) require.NoError(t, err, "Cancel failed") - // The gateway writes "accepted" (on Land) and "cancelling" (on Cancel) - // synchronously to the same store, so both are present the moment Cancel - // returns — no polling needed. + // The gateway writes "accepted" on Land and "cancelling" on Cancel + // synchronously, so GetRequestHistoryByID exposes both when Cancel returns. s.assertStatusesInOrder(sqid, entity.RequestStatusAccepted, entity.RequestStatusCancelling, diff --git a/test/integration/submitqueue/gateway/suite_test.go b/test/integration/submitqueue/gateway/suite_test.go index d52fea65..c3882c5d 100644 --- a/test/integration/submitqueue/gateway/suite_test.go +++ b/test/integration/submitqueue/gateway/suite_test.go @@ -235,6 +235,10 @@ func (s *GatewayIntegrationSuite) TestReadAPIErrorCodes() { require.Error(t, err) assert.Equal(t, codes.ResourceExhausted, status.Code(err)) + _, err = s.client.GetRequestHistoryByChangeURI(s.ctx, &pb.GetRequestHistoryByChangeURIRequest{ChangeUri: overflowChangeURI}) + require.Error(t, err) + assert.Equal(t, codes.ResourceExhausted, status.Code(err)) + const inconsistentChangeURI = "uri/read-api-inconsistent" require.NoError(t, store.GetRequestURIStore().Create(s.ctx, entity.RequestURI{ ChangeURI: inconsistentChangeURI,