Skip to content

Commit b97978b

Browse files
authored
Merge pull request #128 from quickwit-oss/ddelemeny/refactor-timefield-init
Use goroutine/chan to init, avoid hard failure
2 parents ad87ff5 + 6ebe4d9 commit b97978b

File tree

2 files changed

+74
-43
lines changed

2 files changed

+74
-43
lines changed

pkg/quickwit/client/client.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,20 @@ import (
1616
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
1717
)
1818

19+
type ReadyStatus struct {
20+
IsReady bool
21+
Err error
22+
}
23+
1924
type DatasourceInfo struct {
2025
ID int64
2126
HTTPClient *http.Client
2227
URL string
2328
Database string
2429
ConfiguredFields ConfiguredFields
2530
MaxConcurrentShardRequests int64
26-
IsReady bool
31+
ReadyStatus chan ReadyStatus
32+
ShouldInit bool
2733
}
2834

2935
type ConfiguredFields struct {

pkg/quickwit/quickwit.go

Lines changed: 67 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -104,46 +104,62 @@ func NewQuickwitDatasource(settings backend.DataSourceInstanceSettings) (instanc
104104
Database: index,
105105
MaxConcurrentShardRequests: int64(maxConcurrentShardRequests),
106106
ConfiguredFields: configuredFields,
107-
IsReady: false,
108-
}
109-
return &QuickwitDatasource{dsInfo: model}, nil
110-
}
111-
112-
// Network dependent datasource initialization.
113-
// This is not done in the "constructor" function to allow saving the ds
114-
// even if the server is not responsive.
115-
func (ds *QuickwitDatasource) initDatasource(force bool) error {
116-
if ds.dsInfo.IsReady && !force {
117-
return nil
118-
}
119-
120-
indexMetadataList, err := GetIndexesMetadata(ds.dsInfo.Database, ds.dsInfo.URL, ds.dsInfo.HTTPClient)
121-
if err != nil {
122-
return fmt.Errorf("failed to get index metadata : %w", err)
123-
}
124-
125-
if len(indexMetadataList) == 0 {
126-
return fmt.Errorf("no index found for %s", ds.dsInfo.Database)
127-
}
128-
129-
timeField, timeOutputFormat, err := GetTimestampFieldInfos(indexMetadataList)
130-
if nil != err {
131-
return err
107+
ReadyStatus: make(chan es.ReadyStatus, 1),
108+
ShouldInit: true,
132109
}
133110

134-
ds.dsInfo.ConfiguredFields.TimeField = timeField
135-
ds.dsInfo.ConfiguredFields.TimeOutputFormat = timeOutputFormat
111+
ds := &QuickwitDatasource{dsInfo: model}
136112

137-
ds.dsInfo.IsReady = true
138-
return nil
113+
// Create an initialization goroutine
114+
go func(ds *QuickwitDatasource, readyStatus chan<- es.ReadyStatus) {
115+
var status es.ReadyStatus = es.ReadyStatus{
116+
IsReady: false,
117+
Err: nil,
118+
}
119+
for {
120+
// Will retry init everytime the channel is consumed until ready
121+
if !status.IsReady || ds.dsInfo.ShouldInit {
122+
qwlog.Debug("Initializing Datasource")
123+
status.IsReady = true
124+
status.Err = nil
125+
126+
indexMetadataList, err := GetIndexesMetadata(ds.dsInfo.Database, ds.dsInfo.URL, ds.dsInfo.HTTPClient)
127+
if err != nil {
128+
status.IsReady = false
129+
status.Err = fmt.Errorf("failed to get index metadata : %w", err)
130+
} else if len(indexMetadataList) == 0 {
131+
status.IsReady = false
132+
status.Err = fmt.Errorf("no index found for %s", ds.dsInfo.Database)
133+
} else {
134+
timeField, timeOutputFormat, err := GetTimestampFieldInfos(indexMetadataList)
135+
if nil != err {
136+
status.IsReady = false
137+
status.Err = err
138+
} else if "" == timeField {
139+
status.IsReady = false
140+
status.Err = fmt.Errorf("timefield is empty for %s", ds.dsInfo.Database)
141+
} else if "" == timeOutputFormat {
142+
status.Err = fmt.Errorf("timefield's output_format is empty, logs timestamps will not be parsed correctly for %s", ds.dsInfo.Database)
143+
}
144+
145+
ds.dsInfo.ConfiguredFields.TimeField = timeField
146+
ds.dsInfo.ConfiguredFields.TimeOutputFormat = timeOutputFormat
147+
ds.dsInfo.ShouldInit = false
148+
}
149+
}
150+
readyStatus <- status
151+
}
152+
}(ds, model.ReadyStatus)
153+
return ds, nil
139154
}
140155

141156
// Dispose here tells plugin SDK that plugin wants to clean up resources when a new instance
142157
// created. As soon as datasource settings change detected by SDK old datasource instance will
143158
// be disposed and a new one will be created using NewSampleDatasource factory function.
144159
func (ds *QuickwitDatasource) Dispose() {
145-
// Clean up datasource instance resources.
146-
// TODO
160+
// FIXME: The ReadyStatus channel should probably be closed here, but doing it
161+
// causes odd calls to healthcheck to fail. Needs investigation
162+
// close(ds.dsInfo.ReadyStatus)
147163
}
148164

149165
// CheckHealth handles health checks sent from Grafana to the plugin.
@@ -152,28 +168,37 @@ func (ds *QuickwitDatasource) Dispose() {
152168
// a datasource is working as expected.
153169
func (ds *QuickwitDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
154170
res := &backend.CheckHealthResult{}
171+
res.Status = backend.HealthStatusOk
172+
res.Message = "plugin is running"
155173

156-
if err := ds.initDatasource(true); err != nil {
157-
res.Status = backend.HealthStatusError
158-
res.Message = fmt.Errorf("Failed to initialize datasource: %w", err).Error()
159-
return res, nil
160-
}
174+
ds.dsInfo.ShouldInit = true
175+
status := <-ds.dsInfo.ReadyStatus
161176

162-
if ds.dsInfo.ConfiguredFields.TimeField == "" || ds.dsInfo.ConfiguredFields.TimeOutputFormat == "" {
177+
if nil != status.Err {
178+
res.Status = backend.HealthStatusError
179+
res.Message = fmt.Errorf("Failed to initialize datasource: %w", status.Err).Error()
180+
} else if "" == ds.dsInfo.ConfiguredFields.TimeField {
163181
res.Status = backend.HealthStatusError
164182
res.Message = fmt.Sprintf("timefield is missing from index config \"%s\"", ds.dsInfo.Database)
165-
return res, nil
183+
} else if "" == ds.dsInfo.ConfiguredFields.TimeOutputFormat {
184+
res.Status = backend.HealthStatusError
185+
res.Message = fmt.Sprintf("timefield's output_format is missing from index config \"%s\"", ds.dsInfo.Database)
166186
}
187+
qwlog.Debug(res.Message)
167188

168-
res.Status = backend.HealthStatusOk
169-
res.Message = "plugin is running"
170189
return res, nil
171190
}
172191

173192
func (ds *QuickwitDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
174193
// Ensure ds is initialized, we need timestamp infos
175-
if err := ds.initDatasource(false); err != nil {
176-
return &backend.QueryDataResponse{}, fmt.Errorf("Failed to initialize datasource")
194+
status := <-ds.dsInfo.ReadyStatus
195+
if !status.IsReady {
196+
qwlog.Debug(fmt.Errorf("Datasource initialization failed: %w", status.Err).Error())
197+
response := &backend.QueryDataResponse{
198+
Responses: backend.Responses{},
199+
}
200+
response.Responses["__qwQueryDataError"] = backend.ErrDataResponse(backend.StatusInternal, "Datasource initialization failed")
201+
return response, nil
177202
}
178203

179204
return queryData(ctx, req.Queries, &ds.dsInfo)

0 commit comments

Comments
 (0)