@@ -31,6 +31,8 @@ import (
3131 "github.com/uber/submitqueue/platform/metrics"
3232 "github.com/uber/submitqueue/stovepipe/core/hookevent"
3333 stovepipemq "github.com/uber/submitqueue/stovepipe/core/messagequeue"
34+ "github.com/uber/submitqueue/stovepipe/core/requestlog"
35+ requestlogmock "github.com/uber/submitqueue/stovepipe/core/requestlog/mock"
3436 "github.com/uber/submitqueue/stovepipe/entity"
3537 queueconfigdefault "github.com/uber/submitqueue/stovepipe/extension/queueconfig/default"
3638 "github.com/uber/submitqueue/stovepipe/extension/sourcecontrol"
@@ -58,6 +60,8 @@ type processMocks struct {
5860 queueStore * storagemock.MockQueueStore
5961 sourceFactory * sourcecontrolmock.MockFactory
6062 sourceControl * sourcecontrolmock.MockSourceControl
63+ store * storagemock.MockStorage
64+ materializer * requestlogmock.MockMaterializer
6165 publisher * mqmock.MockPublisher
6266}
6367
@@ -79,12 +83,13 @@ func newControllerWithScope(t *testing.T, ctrl *gomock.Controller, scope tally.S
7983 queueStore : storagemock .NewMockQueueStore (ctrl ),
8084 sourceFactory : sourcecontrolmock .NewMockFactory (ctrl ),
8185 sourceControl : sourcecontrolmock .NewMockSourceControl (ctrl ),
86+ store : storagemock .NewMockStorage (ctrl ),
87+ materializer : requestlogmock .NewMockMaterializer (ctrl ),
8288 publisher : mqmock .NewMockPublisher (ctrl ),
8389 }
8490
85- store := storagemock .NewMockStorage (ctrl )
86- store .EXPECT ().GetRequestStore ().Return (m .reqStore ).AnyTimes ()
87- store .EXPECT ().GetQueueStore ().Return (m .queueStore ).AnyTimes ()
91+ m .store .EXPECT ().GetRequestStore ().Return (m .reqStore ).AnyTimes ()
92+ m .store .EXPECT ().GetQueueStore ().Return (m .queueStore ).AnyTimes ()
8893
8994 queue := mqmock .NewMockQueue (ctrl )
9095 queue .EXPECT ().Publisher ().Return (m .publisher ).AnyTimes ()
@@ -98,7 +103,8 @@ func newControllerWithScope(t *testing.T, ctrl *gomock.Controller, scope tally.S
98103 c := NewController (
99104 zap .NewNop ().Sugar (),
100105 scope ,
101- staticStorageFactory {store : store },
106+ staticStorageFactory {store : m .store },
107+ m .materializer ,
102108 queueconfigdefault .NewStore (),
103109 m .sourceFactory ,
104110 registry ,
@@ -141,6 +147,7 @@ func TestProcessBuildPublishRequiresRegisteredTopic(t *testing.T) {
141147 m .reqStore .EXPECT ().Get (gomock .Any (), testID ).Return (entity.Request {
142148 ID : testID , Queue : testQueue , State : entity .RequestStateProcessing , Version : 2 ,
143149 }, nil )
150+ expectProcessingLog (t , m , testID , 2 )
144151
145152 err := c .Process (queueContext (testQueue ), delivery (t , ctrl , processPayload (t , testID )))
146153
@@ -167,19 +174,41 @@ func expectAdmit(t *testing.T, m processMocks, id string) {
167174 expectStartAnnounceAndBuildPublish (t , m , id )
168175}
169176
170- // expectStartAnnounceAndBuildPublish expects the pair of publishes every admitted
171- // request produces, in the order the controller performs them.
172177func expectStartAnnounceAndBuildPublish (t * testing.T , m processMocks , id string ) {
173178 t .Helper ()
179+ expectProcessingLogAndHandoff (t , m , id , 2 )
180+ }
181+
182+ func expectProcessingLogAndHandoff (t * testing.T , m processMocks , id string , version int32 ) {
183+ t .Helper ()
174184
175- expectStartValidationAnnounce (t , m , id )
176- expectBuildPublish (t , m , id )
185+ logCall := expectProcessingLog (t , m , id , version )
186+ hookCall := expectStartValidationAnnounce (t , m , id )
187+ buildCall := expectBuildPublish (t , m , id )
188+ hookCall .After (logCall )
189+ buildCall .After (hookCall )
177190}
178191
179- func expectStartValidationAnnounce (t * testing.T , m processMocks , id string ) {
192+ func expectProcessingLog (t * testing.T , m processMocks , id string , version int32 ) * gomock. Call {
180193 t .Helper ()
181194
182- m .publisher .EXPECT ().
195+ request := entity.Request {
196+ ID : id ,
197+ Queue : testQueue ,
198+ State : entity .RequestStateProcessing ,
199+ Version : version ,
200+ }
201+ return m .materializer .EXPECT ().PersistLog (
202+ gomock .Any (),
203+ m .store ,
204+ requestlog .NewRequestStateLog (request , entity .RequestOutcomeReasonUnknown ),
205+ ).Return (nil )
206+ }
207+
208+ func expectStartValidationAnnounce (t * testing.T , m processMocks , id string ) * gomock.Call {
209+ t .Helper ()
210+
211+ return m .publisher .EXPECT ().
183212 Publish (gomock .Any (), "stovepipe-hook" , gomock .AssignableToTypeOf (entityqueue.Message {})).
184213 DoAndReturn (func (_ context.Context , _ string , msg entityqueue.Message ) error {
185214 assert .Equal (t , id , msg .PartitionKey )
@@ -194,10 +223,10 @@ func expectStartValidationAnnounce(t *testing.T, m processMocks, id string) {
194223 })
195224}
196225
197- func expectBuildPublish (t * testing.T , m processMocks , id string ) {
226+ func expectBuildPublish (t * testing.T , m processMocks , id string ) * gomock. Call {
198227 t .Helper ()
199228
200- m .publisher .EXPECT ().
229+ return m .publisher .EXPECT ().
201230 Publish (gomock .Any (), "build" , gomock .AssignableToTypeOf (entityqueue.Message {})).
202231 DoAndReturn (func (_ context.Context , _ string , msg entityqueue.Message ) error {
203232 assert .Equal (t , id , msg .ID )
@@ -498,6 +527,21 @@ func TestProcess(t *testing.T) {
498527 expectStartAnnounceAndBuildPublish (t , m , testID )
499528 },
500529 },
530+ {
531+ name : "processing redelivery stops before handoff when log persistence fails" ,
532+ wantErr : true ,
533+ setup : func (m processMocks ) {
534+ request := entity.Request {
535+ ID : testID , Queue : testQueue , State : entity .RequestStateProcessing , Version : 2 ,
536+ }
537+ m .reqStore .EXPECT ().Get (gomock .Any (), testID ).Return (request , nil )
538+ m .materializer .EXPECT ().PersistLog (
539+ gomock .Any (),
540+ m .store ,
541+ requestlog .NewRequestStateLog (request , entity .RequestOutcomeReasonUnknown ),
542+ ).Return (errors .New ("db down" ))
543+ },
544+ },
501545 {
502546 name : "unknown state is acked without retry" ,
503547 setup : func (m processMocks ) {
@@ -539,9 +583,11 @@ func TestProcess(t *testing.T) {
539583 updatedRequest .State = entity .RequestStateProcessing
540584 updatedRequest .BuildStrategy = entity .BuildStrategyFull
541585 m .reqStore .EXPECT ().Update (gomock .Any (), updatedRequest , int32 (1 ), int32 (2 )).Return (nil )
586+ logCall := expectProcessingLog (t , m , testID , 2 )
542587 m .publisher .EXPECT ().
543588 Publish (gomock .Any (), "stovepipe-hook" , gomock .AssignableToTypeOf (entityqueue.Message {})).
544- Return (errors .New ("queue unavailable" ))
589+ Return (errors .New ("queue unavailable" )).
590+ After (logCall )
545591 },
546592 },
547593 {
@@ -565,10 +611,44 @@ func TestProcess(t *testing.T) {
565611 updatedRequest .State = entity .RequestStateProcessing
566612 updatedRequest .BuildStrategy = entity .BuildStrategyFull
567613 m .reqStore .EXPECT ().Update (gomock .Any (), updatedRequest , int32 (1 ), int32 (2 )).Return (nil )
568- expectStartValidationAnnounce (t , m , testID )
614+ logCall := expectProcessingLog (t , m , testID , 2 )
615+ hookCall := expectStartValidationAnnounce (t , m , testID )
569616 m .publisher .EXPECT ().
570617 Publish (gomock .Any (), "build" , gomock .AssignableToTypeOf (entityqueue.Message {})).
571- Return (errors .New ("queue unavailable" ))
618+ Return (errors .New ("queue unavailable" )).
619+ After (hookCall )
620+ hookCall .After (logCall )
621+ },
622+ },
623+ {
624+ name : "processing log failure retains admitted request and claimed slot" ,
625+ wantErr : true ,
626+ setup : func (m processMocks ) {
627+ m .reqStore .EXPECT ().Get (gomock .Any (), testID ).Return (acceptedRequest (testID ), nil )
628+ m .queueStore .EXPECT ().Get (gomock .Any (), testQueue ).Return (entity.Queue {
629+ Name : testQueue ,
630+ LatestRequestID : testID ,
631+ Version : 1 ,
632+ }, nil )
633+ updatedQueue := entity.Queue {
634+ Name : testQueue ,
635+ LatestRequestID : testID ,
636+ InFlightCount : 1 ,
637+ Version : 1 ,
638+ }
639+ m .queueStore .EXPECT ().Update (gomock .Any (), updatedQueue , int32 (1 ), int32 (2 )).Return (nil )
640+ updatedRequest := acceptedRequest (testID )
641+ updatedRequest .State = entity .RequestStateProcessing
642+ updatedRequest .BuildStrategy = entity .BuildStrategyFull
643+ m .reqStore .EXPECT ().Update (gomock .Any (), updatedRequest , int32 (1 ), int32 (2 )).Return (nil )
644+ request := entity.Request {
645+ ID : testID , Queue : testQueue , State : entity .RequestStateProcessing , Version : 2 ,
646+ }
647+ m .materializer .EXPECT ().PersistLog (
648+ gomock .Any (),
649+ m .store ,
650+ requestlog .NewRequestStateLog (request , entity .RequestOutcomeReasonUnknown ),
651+ ).Return (errors .New ("db down" ))
572652 },
573653 },
574654 {
@@ -710,7 +790,7 @@ func TestProcess(t *testing.T) {
710790 retry .BuildStrategy = entity .BuildStrategyIncrementalSinceGreen
711791 retry .BaseURI = lastGreenURI
712792 m .reqStore .EXPECT ().Update (gomock .Any (), retry , int32 (2 ), int32 (3 )).Return (nil )
713- expectStartAnnounceAndBuildPublish (t , m , testID )
793+ expectProcessingLogAndHandoff (t , m , testID , 3 )
714794 },
715795 },
716796 {
0 commit comments