@@ -2,49 +2,164 @@ package builder
2
2
3
3
import (
4
4
"context"
5
+ "fmt"
6
+ "time"
5
7
6
8
"github.com/go-kit/log"
9
+ "github.com/go-kit/log/level"
10
+ "github.com/google/uuid"
7
11
"github.com/grafana/dskit/services"
12
+ "github.com/pkg/errors"
8
13
"github.com/prometheus/client_golang/prometheus"
14
+ "google.golang.org/grpc"
9
15
16
+ "github.com/grafana/loki/v3/pkg/bloombuild/protos"
10
17
utillog "github.com/grafana/loki/v3/pkg/util/log"
11
18
)
12
19
13
- type Worker struct {
20
+ type Builder struct {
14
21
services.Service
15
22
23
+ ID string
24
+
16
25
cfg Config
17
26
metrics * Metrics
18
27
logger log.Logger
28
+
29
+ client protos.PlannerForBuilderClient
19
30
}
20
31
21
32
func New (
22
33
cfg Config ,
23
34
logger log.Logger ,
24
35
r prometheus.Registerer ,
25
- ) (* Worker , error ) {
36
+ ) (* Builder , error ) {
26
37
utillog .WarnExperimentalUse ("Bloom Builder" , logger )
27
38
28
- w := & Worker {
39
+ b := & Builder {
40
+ ID : uuid .NewString (),
29
41
cfg : cfg ,
30
42
metrics : NewMetrics (r ),
31
43
logger : logger ,
32
44
}
33
45
34
- w .Service = services .NewBasicService (w .starting , w .running , w .stopping )
35
- return w , nil
46
+ b .Service = services .NewBasicService (b .starting , b .running , b .stopping )
47
+ return b , nil
48
+ }
49
+
50
+ func (b * Builder ) starting (_ context.Context ) error {
51
+ b .metrics .running .Set (1 )
52
+ return nil
36
53
}
37
54
38
- func (w * Worker ) starting (_ context.Context ) (err error ) {
39
- w .metrics .running .Set (1 )
40
- return err
55
+ func (b * Builder ) stopping (_ error ) error {
56
+ if b .client != nil {
57
+ req := & protos.NotifyBuilderShutdownRequest {
58
+ BuilderID : b .ID ,
59
+ }
60
+ if _ , err := b .client .NotifyBuilderShutdown (context .Background (), req ); err != nil {
61
+ level .Error (b .logger ).Log ("msg" , "failed to notify planner about builder shutdown" , "err" , err )
62
+ }
63
+ }
64
+
65
+ b .metrics .running .Set (0 )
66
+ return nil
41
67
}
42
68
43
- func (w * Worker ) stopping (_ error ) error {
44
- w .metrics .running .Set (0 )
69
+ func (b * Builder ) running (ctx context.Context ) error {
70
+ opts , err := b .cfg .GrpcConfig .DialOption (nil , nil )
71
+ if err != nil {
72
+ return fmt .Errorf ("failed to create grpc dial options: %w" , err )
73
+ }
74
+
75
+ // TODO: Wrap hereafter in retry logic
76
+ conn , err := grpc .DialContext (ctx , b .cfg .PlannerAddress , opts ... )
77
+ if err != nil {
78
+ return fmt .Errorf ("failed to dial bloom planner: %w" , err )
79
+ }
80
+
81
+ b .client = protos .NewPlannerForBuilderClient (conn )
82
+
83
+ c , err := b .client .BuilderLoop (ctx )
84
+ if err != nil {
85
+ return fmt .Errorf ("failed to start builder loop: %w" , err )
86
+ }
87
+
88
+ // Start processing tasks from planner
89
+ if err := b .builderLoop (c ); err != nil {
90
+ return fmt .Errorf ("builder loop failed: %w" , err )
91
+ }
92
+
45
93
return nil
46
94
}
47
95
48
- func (w * Worker ) running (_ context.Context ) error {
96
+ func (b * Builder ) builderLoop (c protos.PlannerForBuilder_BuilderLoopClient ) error {
97
+ // Send ready message to planner
98
+ if err := c .Send (& protos.BuilderToPlanner {BuilderID : b .ID }); err != nil {
99
+ return fmt .Errorf ("failed to send ready message to planner: %w" , err )
100
+ }
101
+
102
+ for b .State () == services .Running {
103
+ // When the planner connection closes or the builder stops, the context
104
+ // will be canceled and the loop will exit.
105
+ protoTask , err := c .Recv ()
106
+ if err != nil {
107
+ if errors .Is (c .Context ().Err (), context .Canceled ) {
108
+ level .Debug (b .logger ).Log ("msg" , "builder loop context canceled" )
109
+ return nil
110
+ }
111
+
112
+ return fmt .Errorf ("failed to receive task from planner: %w" , err )
113
+ }
114
+
115
+ b .metrics .taskStarted .Inc ()
116
+ start := time .Now ()
117
+ status := statusSuccess
118
+
119
+ err = b .processTask (c .Context (), protoTask .Task )
120
+ if err != nil {
121
+ status = statusFailure
122
+ level .Error (b .logger ).Log ("msg" , "failed to process task" , "err" , err )
123
+ }
124
+
125
+ b .metrics .taskCompleted .WithLabelValues (status ).Inc ()
126
+ b .metrics .taskDuration .WithLabelValues (status ).Observe (time .Since (start ).Seconds ())
127
+
128
+ // Acknowledge task completion to planner
129
+ if err = b .notifyTaskCompletedToPlanner (c , err ); err != nil {
130
+ return fmt .Errorf ("failed to notify task completion to planner: %w" , err )
131
+ }
132
+ }
133
+
134
+ level .Debug (b .logger ).Log ("msg" , "builder loop stopped" )
135
+ return nil
136
+ }
137
+
138
+ func (b * Builder ) notifyTaskCompletedToPlanner (c protos.PlannerForBuilder_BuilderLoopClient , err error ) error {
139
+ var errMsg string
140
+ if err != nil {
141
+ errMsg = err .Error ()
142
+ }
143
+
144
+ // TODO: Implement retry
145
+ if err := c .Send (& protos.BuilderToPlanner {
146
+ BuilderID : b .ID ,
147
+ Error : errMsg ,
148
+ }); err != nil {
149
+ return fmt .Errorf ("failed to acknowledge task completion to planner: %w" , err )
150
+ }
151
+ return nil
152
+ }
153
+
154
+ func (b * Builder ) processTask (_ context.Context , protoTask * protos.ProtoTask ) error {
155
+ task , err := protos .FromProtoTask (protoTask )
156
+ if err != nil {
157
+ return fmt .Errorf ("failed to convert proto task to task: %w" , err )
158
+ }
159
+
160
+ level .Debug (b .logger ).Log ("msg" , "received task" , "task" , task .ID )
161
+
162
+ // TODO: Implement task processing
163
+
49
164
return nil
50
165
}
0 commit comments