Skip to content

Commit 2e77c97

Browse files
fix: redesign issues (#707)
* fix: empty upstream job id issue * fix: single static internal upstream resolver issue causing error in job inspect * feat: summarize all error messages when job replace all finished * fix: issue when job is not found when inspecting * fix: wrap job not found error in repository to also mention the subject job * fix: handle empty job schedule end times at scheduler job provider repo * fix: make end date optional when storing to job table * fix: omit endDate from schedule when it is null Co-authored-by: Yash Bhardwaj <[email protected]>
1 parent 794803c commit 2e77c97

File tree

10 files changed

+99
-41
lines changed

10 files changed

+99
-41
lines changed

core/job/handler/v1beta1/job.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func (jh *JobHandler) GetJobSpecification(ctx context.Context, req *pb.GetJobSpe
168168
}
169169

170170
jobSpec, err := jh.jobService.Get(ctx, jobTenant, jobName)
171-
if err != nil {
171+
if err != nil && !errors.IsErrorType(err, errors.ErrNotFound) {
172172
errorMsg := "failed to get job specification"
173173
jh.l.Error(fmt.Sprintf("%s: %s", err.Error(), errorMsg))
174174
return nil, errors.GRPCErr(err, errorMsg)
@@ -258,6 +258,7 @@ func (*JobHandler) GetWindow(_ context.Context, req *pb.GetWindowRequest) (*pb.G
258258
func (jh *JobHandler) ReplaceAllJobSpecifications(stream pb.JobSpecificationService_ReplaceAllJobSpecificationsServer) error {
259259
responseWriter := writer.NewReplaceAllJobSpecificationsResponseWriter(stream)
260260
var errNamespaces []string
261+
var errMessages []string
261262

262263
for {
263264
request, err := stream.Recv()
@@ -272,10 +273,11 @@ func (jh *JobHandler) ReplaceAllJobSpecifications(stream pb.JobSpecificationServ
272273

273274
jobTenant, err := tenant.NewTenant(request.ProjectName, request.NamespaceName)
274275
if err != nil {
275-
errMsg := fmt.Sprintf("invalid replace all job specifications request for %s: %s", request.GetNamespaceName(), err.Error())
276+
errMsg := fmt.Sprintf("[%s] invalid replace all job specifications request: %s", request.GetNamespaceName(), err.Error())
276277
jh.l.Error(errMsg)
277278
responseWriter.Write(writer.LogLevelError, errMsg)
278279
errNamespaces = append(errNamespaces, request.NamespaceName)
280+
errMessages = append(errMessages, errMsg)
279281
continue
280282
}
281283

@@ -285,16 +287,21 @@ func (jh *JobHandler) ReplaceAllJobSpecifications(stream pb.JobSpecificationServ
285287
jh.l.Error(errMsg)
286288
responseWriter.Write(writer.LogLevelError, errMsg)
287289
errNamespaces = append(errNamespaces, request.NamespaceName)
290+
errMessages = append(errMessages, errMsg)
288291
}
289292

290293
if err := jh.jobService.ReplaceAll(stream.Context(), jobTenant, jobSpecs, jobNamesWithValidationErrors, responseWriter); err != nil {
291-
errMsg := fmt.Sprintf("replace all job specifications failure for namespace %s: %s", request.NamespaceName, err.Error())
294+
errMsg := fmt.Sprintf("[%s] replace all job specifications failure: %s", request.NamespaceName, err.Error())
292295
jh.l.Error(errMsg)
293296
responseWriter.Write(writer.LogLevelError, errMsg)
294297
errNamespaces = append(errNamespaces, request.NamespaceName)
298+
errMessages = append(errMessages, errMsg)
295299
}
296300
}
297301
if len(errNamespaces) > 0 {
302+
errMessageSummary := strings.Join(errMessages, "\n")
303+
responseWriter.Write(writer.LogLevelError, fmt.Sprintf("\njob replace all finished with errors:\n%s", errMessageSummary))
304+
298305
namespacesWithError := strings.Join(errNamespaces, ", ")
299306
return fmt.Errorf("error when replacing job specifications: [%s]", namespacesWithError)
300307
}

core/job/handler/v1beta1/job_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ func TestNewJobHandler(t *testing.T) {
890890

891891
jobService.On("ReplaceAll", ctx, sampleTenant, mock.Anything, []job.Name{"job-A"}, mock.Anything).Return(nil)
892892

893-
stream.On("Send", mock.AnythingOfType("*optimus.ReplaceAllJobSpecificationsResponse")).Return(nil).Twice()
893+
stream.On("Send", mock.AnythingOfType("*optimus.ReplaceAllJobSpecificationsResponse")).Return(nil).Times(3)
894894

895895
err := jobHandler.ReplaceAllJobSpecifications(stream)
896896
assert.ErrorContains(t, err, "error when replacing job specifications")
@@ -941,7 +941,7 @@ func TestNewJobHandler(t *testing.T) {
941941

942942
jobService.On("ReplaceAll", ctx, sampleTenant, mock.Anything, jobNamesWithValidationError, mock.Anything).Return(nil)
943943

944-
stream.On("Send", mock.AnythingOfType("*optimus.ReplaceAllJobSpecificationsResponse")).Return(nil).Times(3)
944+
stream.On("Send", mock.AnythingOfType("*optimus.ReplaceAllJobSpecificationsResponse")).Return(nil).Times(4)
945945

946946
err := jobHandler.ReplaceAllJobSpecifications(stream)
947947
assert.Error(t, err)
@@ -990,7 +990,7 @@ func TestNewJobHandler(t *testing.T) {
990990

991991
jobService.On("ReplaceAll", ctx, sampleTenant, mock.Anything, jobNamesWithValidationError, mock.Anything).Return(errors.New("internal error"))
992992

993-
stream.On("Send", mock.AnythingOfType("*optimus.ReplaceAllJobSpecificationsResponse")).Return(nil).Twice()
993+
stream.On("Send", mock.AnythingOfType("*optimus.ReplaceAllJobSpecificationsResponse")).Return(nil).Times(3)
994994

995995
err := jobHandler.ReplaceAllJobSpecifications(stream)
996996
assert.Error(t, err)

core/job/resolver/internal_upstream_resolver.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@ func NewInternalUpstreamResolver(jobRepository JobRepository) *internalUpstreamR
2020

2121
func (i internalUpstreamResolver) Resolve(ctx context.Context, jobWithUnresolvedUpstream *job.WithUpstream) (*job.WithUpstream, error) {
2222
me := errors.NewMultiError("internal upstream resolution errors")
23+
2324
internalUpstreamInferred, err := i.resolveInferredUpstream(ctx, jobWithUnresolvedUpstream.Job().Sources())
2425
me.Append(err)
2526

26-
internalUpstreamStatic, err := i.resolveStaticUpstream(ctx, jobWithUnresolvedUpstream.Job().Tenant().ProjectName(), jobWithUnresolvedUpstream.Job().Spec().UpstreamSpec())
27-
me.Append(err)
27+
var internalUpstreamStatic []*job.Upstream
28+
if staticUpstreamSpec := jobWithUnresolvedUpstream.Job().Spec().UpstreamSpec(); staticUpstreamSpec != nil {
29+
internalUpstreamStatic, err = i.resolveStaticUpstream(ctx, jobWithUnresolvedUpstream.Job().Tenant().ProjectName(), staticUpstreamSpec)
30+
me.Append(err)
31+
}
2832

2933
internalUpstream := mergeUpstreams(internalUpstreamInferred, internalUpstreamStatic)
3034
fullNameUpstreamMap := job.Upstreams(internalUpstream).ToFullNameAndUpstreamMap()

core/job/resolver/internal_upstream_resolver_test.go

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,26 +46,61 @@ func TestInternalUpstreamResolver(t *testing.T) {
4646
unresolvedUpstreamD := job.NewUpstreamUnresolvedInferred("resource-D")
4747

4848
t.Run("Resolve", func(t *testing.T) {
49-
t.Run("resolves upstream internally", func(t *testing.T) {
49+
t.Run("resolves inferred and static upstream internally", func(t *testing.T) {
5050
jobRepo := new(JobRepository)
51-
5251
logWriter := new(mockWriter)
5352
defer logWriter.AssertExpectations(t)
5453

5554
jobRepo.On("GetAllByResourceDestination", ctx, jobASources[0]).Return([]*job.Job{jobB}, nil)
5655
jobRepo.On("GetAllByResourceDestination", ctx, jobASources[1]).Return([]*job.Job{}, nil)
57-
5856
jobRepo.On("GetByJobName", ctx, sampleTenant.ProjectName(), specC.Name()).Return(jobC, nil)
5957

6058
jobWithUnresolvedUpstream := job.NewWithUpstream(jobA, []*job.Upstream{unresolvedUpstreamB, unresolvedUpstreamC, unresolvedUpstreamD})
61-
6259
expectedJobWithUpstream := job.NewWithUpstream(jobA, []*job.Upstream{internalUpstreamB, internalUpstreamC, unresolvedUpstreamD})
6360

6461
internalUpstreamResolver := resolver.NewInternalUpstreamResolver(jobRepo)
6562
result, err := internalUpstreamResolver.Resolve(ctx, jobWithUnresolvedUpstream)
6663
assert.NoError(t, err)
6764
assert.EqualValues(t, expectedJobWithUpstream.Upstreams(), result.Upstreams())
6865
})
66+
t.Run("resolves inferred upstream internally", func(t *testing.T) {
67+
jobRepo := new(JobRepository)
68+
logWriter := new(mockWriter)
69+
defer logWriter.AssertExpectations(t)
70+
71+
specX, _ := job.NewSpecBuilder(jobVersion, "job-X", "sample-owner", jobSchedule, jobWindow, jobTask).Build()
72+
jobXDestination := job.ResourceURN("resource-X")
73+
jobX := job.NewJob(sampleTenant, specX, jobXDestination, []job.ResourceURN{"resource-B"})
74+
75+
jobRepo.On("GetAllByResourceDestination", ctx, jobX.Sources()[0]).Return([]*job.Job{jobB}, nil)
76+
77+
jobWithUnresolvedUpstream := job.NewWithUpstream(jobX, []*job.Upstream{unresolvedUpstreamB})
78+
expectedJobWithUpstream := job.NewWithUpstream(jobX, []*job.Upstream{internalUpstreamB})
79+
80+
internalUpstreamResolver := resolver.NewInternalUpstreamResolver(jobRepo)
81+
result, err := internalUpstreamResolver.Resolve(ctx, jobWithUnresolvedUpstream)
82+
assert.NoError(t, err)
83+
assert.EqualValues(t, expectedJobWithUpstream.Upstreams(), result.Upstreams())
84+
})
85+
t.Run("resolves static upstream internally", func(t *testing.T) {
86+
jobRepo := new(JobRepository)
87+
logWriter := new(mockWriter)
88+
defer logWriter.AssertExpectations(t)
89+
90+
specX, _ := job.NewSpecBuilder(jobVersion, "job-X", "sample-owner", jobSchedule, jobWindow, jobTask).WithSpecUpstream(upstreamSpec).Build()
91+
jobXDestination := job.ResourceURN("resource-X")
92+
jobX := job.NewJob(sampleTenant, specX, jobXDestination, nil)
93+
94+
jobRepo.On("GetByJobName", ctx, sampleTenant.ProjectName(), specC.Name()).Return(jobC, nil)
95+
96+
jobWithUnresolvedUpstream := job.NewWithUpstream(jobX, []*job.Upstream{unresolvedUpstreamC})
97+
expectedJobWithUpstream := job.NewWithUpstream(jobX, []*job.Upstream{internalUpstreamC})
98+
99+
internalUpstreamResolver := resolver.NewInternalUpstreamResolver(jobRepo)
100+
result, err := internalUpstreamResolver.Resolve(ctx, jobWithUnresolvedUpstream)
101+
assert.NoError(t, err)
102+
assert.EqualValues(t, expectedJobWithUpstream.Upstreams(), result.Upstreams())
103+
})
69104
t.Run("should not stop the process but keep appending error when unable to resolve inferred upstream", func(t *testing.T) {
70105
jobRepo := new(JobRepository)
71106

core/job/service/job_service.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,10 @@ func (j JobService) Get(ctx context.Context, jobTenant tenant.Tenant, jobName jo
150150
if err != nil {
151151
return nil, err
152152
}
153-
if len(jobs) > 0 {
154-
return jobs[0], nil
153+
if len(jobs) == 0 {
154+
return nil, errors.NotFound(job.EntityJob, fmt.Sprintf("job %s is not found", jobName))
155155
}
156-
157-
// TODO: should not return dummy job
158-
return &job.Job{}, nil
156+
return jobs[0], nil
159157
}
160158

161159
func (j JobService) GetTaskInfo(ctx context.Context, task job.Task) (*plugin.Info, error) {
@@ -599,10 +597,6 @@ func (j JobService) GetJobBasicInfo(ctx context.Context, jobTenant tenant.Tenant
599597
logger.Write(writer.LogLevelError, fmt.Sprintf("unable to get job, err: %v", err))
600598
return nil, logger
601599
}
602-
if subjectJob == nil {
603-
logger.Write(writer.LogLevelError, fmt.Sprintf("job %s not found in the server", jobName.String()))
604-
return nil, logger
605-
}
606600
}
607601

608602
if len(subjectJob.Sources()) == 0 {

core/job/service/job_service_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2156,11 +2156,11 @@ func TestJobService(t *testing.T) {
21562156

21572157
specA, _ := job.NewSpecBuilder(jobVersion, "job-A", "sample-owner", jobSchedule, jobWindow, jobTask).Build()
21582158

2159-
jobRepo.On("GetByJobName", ctx, project.Name(), specA.Name()).Return(nil, nil)
2159+
jobRepo.On("GetByJobName", ctx, project.Name(), specA.Name()).Return(nil, errors.New("job not found"))
21602160

21612161
jobService := service.NewJobService(jobRepo, pluginService, upstreamResolver, tenantDetailsGetter, log)
21622162
result, logger := jobService.GetJobBasicInfo(ctx, sampleTenant, specA.Name(), nil)
2163-
assert.Contains(t, logger.Messages[0].Message, "job job-A not found in the server")
2163+
assert.Contains(t, logger.Messages[0].Message, "job not found")
21642164
assert.Nil(t, result)
21652165
})
21662166
t.Run("should write log if found existing job with same resource destination", func(t *testing.T) {

internal/store/postgres/job/adapter.go

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type Spec struct {
5454

5555
type Schedule struct {
5656
StartDate time.Time
57-
EndDate *time.Time
57+
EndDate *time.Time `json:",omitempty"`
5858
Interval string
5959
DependsOnPast bool
6060
CatchUp bool
@@ -253,14 +253,6 @@ func toStorageSchedule(scheduleSpec *job.Schedule) ([]byte, error) {
253253
return nil, err
254254
}
255255

256-
var endDate time.Time
257-
if scheduleSpec.EndDate() != "" {
258-
endDate, err = time.Parse(jobDatetimeLayout, scheduleSpec.EndDate().String())
259-
if err != nil {
260-
return nil, err
261-
}
262-
}
263-
264256
var retry *Retry
265257
if scheduleSpec.Retry() != nil {
266258
retry = &Retry{
@@ -272,12 +264,18 @@ func toStorageSchedule(scheduleSpec *job.Schedule) ([]byte, error) {
272264

273265
schedule := Schedule{
274266
StartDate: startDate,
275-
EndDate: &endDate,
276267
Interval: scheduleSpec.Interval(),
277268
DependsOnPast: scheduleSpec.DependsOnPast(),
278269
CatchUp: scheduleSpec.CatchUp(),
279270
Retry: retry,
280271
}
272+
if scheduleSpec.EndDate() != "" {
273+
endDate, err := time.Parse(jobDatetimeLayout, scheduleSpec.EndDate().String())
274+
if err != nil {
275+
return nil, err
276+
}
277+
schedule.EndDate = &endDate
278+
}
281279
return json.Marshal(schedule)
282280
}
283281

@@ -468,7 +466,7 @@ func fromStorageSchedule(raw []byte) (*job.Schedule, error) {
468466
WithDependsOnPast(storageSchedule.DependsOnPast).
469467
WithInterval(storageSchedule.Interval)
470468

471-
if !storageSchedule.EndDate.IsZero() {
469+
if storageSchedule.EndDate != nil && !storageSchedule.EndDate.IsZero() {
472470
endDate, err := job.ScheduleDateFrom(storageSchedule.EndDate.Format(job.DateLayout))
473471
if err != nil {
474472
return nil, err

internal/store/postgres/job/job_repository.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,11 @@ func (j JobRepository) get(ctx context.Context, projectName tenant.ProjectName,
170170
getJobByNameAtProject += jobDeletedFilter
171171
}
172172

173-
return FromRow(j.db.QueryRow(ctx, getJobByNameAtProject, jobName.String(), projectName.String()))
173+
spec, err := FromRow(j.db.QueryRow(ctx, getJobByNameAtProject, jobName.String(), projectName.String()))
174+
if errors.IsErrorType(err, errors.ErrNotFound) {
175+
err = errors.NotFound(job.EntityJob, fmt.Sprintf("unable to get job %s", jobName))
176+
}
177+
return spec, err
174178
}
175179

176180
func (j JobRepository) ResolveUpstreams(ctx context.Context, projectName tenant.ProjectName, jobNames []job.Name) (map[job.Name][]*job.Upstream, error) {
@@ -516,7 +520,7 @@ INSERT INTO job_upstream (
516520
)
517521
VALUES (
518522
(select id FROM job WHERE name = $1 and project_name = $2), $1, $2,
519-
(select id FROM job WHERE name = $3 and project_name = $4), $3, $4,
523+
(select id FROM job WHERE name = $3 and project_name = $5), $3, $4,
520524
$5, $6, $7,
521525
$8, $9,
522526
$10, $11,

internal/store/postgres/job/job_repository_test.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -769,15 +769,26 @@ func TestPostgresJobRepository(t *testing.T) {
769769
assert.NoError(t, err)
770770
jobA := job.NewJob(sampleTenant, jobSpecA, "dev.resource.sample_a", []job.ResourceURN{"dev.resource.sample_b"})
771771

772+
jobSpecX, err := job.NewSpecBuilder(jobVersion, "sample-job-X", jobOwner, jobSchedule, jobWindow, jobTask).WithDescription(jobDescription).Build()
773+
assert.NoError(t, err)
774+
jobX := job.NewJob(sampleTenant, jobSpecX, "dev.resource.sample_x", []job.ResourceURN{"dev.resource.sample_a"})
775+
772776
jobRepo := postgres.NewJobRepository(db)
773777

774-
addedJob, err := jobRepo.Add(ctx, []*job.Job{jobA})
778+
addedJob, err := jobRepo.Add(ctx, []*job.Job{jobA, jobX})
775779
assert.NoError(t, err)
776780
assert.NotNil(t, addedJob)
777781

778-
upstreamCInferred := job.NewUpstreamResolved("sample-job-B", "host-1", "dev.resource.sample_b", sampleTenant, "inferred", taskName, false)
779-
jobAWithUpstream := job.NewWithUpstream(jobA, []*job.Upstream{upstreamCInferred})
780-
err = jobRepo.ReplaceUpstreams(ctx, []*job.WithUpstream{jobAWithUpstream})
782+
upstreamBInferred := job.NewUpstreamResolved("sample-job-B", "host-1", "dev.resource.sample_b", sampleTenant, "inferred", taskName, false)
783+
jobAWithUpstream := job.NewWithUpstream(jobA, []*job.Upstream{upstreamBInferred})
784+
upstreamAInferred := job.NewUpstreamResolved("sample-job-A", "host-1", "dev.resource.sample_a", sampleTenant, "inferred", taskName, false)
785+
jobXWithUpstream := job.NewWithUpstream(jobX, []*job.Upstream{upstreamAInferred})
786+
787+
err = jobRepo.ReplaceUpstreams(ctx, []*job.WithUpstream{jobAWithUpstream, jobXWithUpstream})
788+
assert.NoError(t, err)
789+
790+
upstreams, err := jobRepo.GetUpstreams(ctx, proj.Name(), jobSpecX.Name())
791+
assert.Equal(t, 1, len(upstreams))
781792
assert.NoError(t, err)
782793

783794
err = jobRepo.Delete(ctx, proj.Name(), jobSpecA.Name(), true)
@@ -787,6 +798,11 @@ func TestPostgresJobRepository(t *testing.T) {
787798
addedJob, err = jobRepo.Add(ctx, []*job.Job{jobA})
788799
assert.NoError(t, err)
789800
assert.NotNil(t, addedJob)
801+
802+
// data in upstream should already be deleted
803+
upstreams, err = jobRepo.GetUpstreams(ctx, proj.Name(), jobSpecX.Name())
804+
assert.Equal(t, 0, len(upstreams))
805+
assert.NoError(t, err)
790806
})
791807
})
792808

internal/store/postgres/scheduler/job_repository.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func (j *Job) toJobWithDetails() (*scheduler.JobWithDetails, error) {
198198
Interval: storageSchedule.Interval,
199199
},
200200
}
201-
if !storageSchedule.EndDate.IsZero() {
201+
if !(storageSchedule.EndDate == nil || storageSchedule.EndDate.IsZero()) {
202202
schedulerJobWithDetails.Schedule.EndDate = storageSchedule.EndDate
203203
}
204204

0 commit comments

Comments
 (0)