Skip to content

Commit d0d048c

Browse files
committed
Mitigate API throttling issues in prod
* Run query only when target field in status or context is empty Signed-off-by: Yury Tsarev <[email protected]>
1 parent 3590461 commit d0d048c

File tree

3 files changed

+452
-31
lines changed

3 files changed

+452
-31
lines changed

.golangci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ linters-settings:
4242

4343
gocyclo:
4444
# minimal code complexity to report, 30 by default (but we recommend 10-20)
45-
min-complexity: 20
45+
min-complexity: 22
4646

4747
dupl:
4848
# tokens count to trigger issue, 150 by default

fn.go

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,72 @@ func (f *Function) RunFunction(ctx context.Context, req *fnv1.RunFunctionRequest
9595
return rsp, nil
9696
}
9797

98-
results, err := f.azureQuery.azQuery(ctx, azureCreds, in)
99-
if err != nil {
100-
response.Fatal(rsp, err)
101-
f.log.Info("FAILURE: ", "failure", fmt.Sprint(err))
98+
// Check if target already has data before executing the query
99+
var shouldExecuteQuery = true
100+
101+
switch {
102+
case strings.HasPrefix(in.Target, "status."):
103+
// Check if the target field in status already exists
104+
oxr, err := request.GetObservedCompositeResource(req)
105+
if err != nil {
106+
response.Fatal(rsp, errors.Wrap(err, "cannot get observed composite resource"))
107+
return rsp, nil
108+
}
109+
110+
xrStatus := make(map[string]interface{})
111+
err = oxr.Resource.GetValueInto("status", &xrStatus)
112+
if err == nil {
113+
// Check if the target field already has data
114+
statusField := strings.TrimPrefix(in.Target, "status.")
115+
if hasData, _ := targetHasData(xrStatus, statusField); hasData {
116+
f.log.Info("Target already has data, skipping query", "target", in.Target)
117+
118+
// Set success condition and return
119+
response.ConditionTrue(rsp, "FunctionSuccess", "SkippedQuery").
120+
WithMessage("Target already has data, skipped query to avoid throttling").
121+
TargetCompositeAndClaim()
122+
return rsp, nil
123+
}
124+
}
125+
case strings.HasPrefix(in.Target, "context."):
126+
// Check if the target field in context already exists
127+
contextMap := req.GetContext().AsMap()
128+
contextField := strings.TrimPrefix(in.Target, "context.")
129+
if hasData, _ := targetHasData(contextMap, contextField); hasData {
130+
f.log.Info("Target already has data, skipping query", "target", in.Target)
131+
132+
// Set success condition and return
133+
response.ConditionTrue(rsp, "FunctionSuccess", "SkippedQuery").
134+
WithMessage("Target already has data, skipped query to avoid throttling").
135+
TargetCompositeAndClaim()
136+
return rsp, nil
137+
}
138+
default:
139+
response.Fatal(rsp, errors.Errorf("Unrecognized target field: %s", in.Target))
102140
return rsp, nil
103141
}
104-
// Print the obtained query results
105-
f.log.Info("Query:", "query", in.Query)
106-
f.log.Info("Results:", "results", fmt.Sprint(results.Data))
107-
response.Normalf(rsp, "Query: %q", in.Query)
108142

143+
// Only execute the query if necessary
144+
var results armresourcegraph.ClientResourcesResponse
145+
if shouldExecuteQuery {
146+
var err error
147+
results, err = f.azureQuery.azQuery(ctx, azureCreds, in)
148+
if err != nil {
149+
response.Fatal(rsp, err)
150+
f.log.Info("FAILURE: ", "failure", fmt.Sprint(err))
151+
return rsp, nil
152+
}
153+
// Print the obtained query results
154+
f.log.Info("Query:", "query", in.Query)
155+
f.log.Info("Results:", "results", fmt.Sprint(results.Data))
156+
response.Normalf(rsp, "Query: %q", in.Query)
157+
} else {
158+
// We should never reach here due to early returns above, but just in case
159+
response.Normalf(rsp, "Skipped query: %q", in.Query)
160+
return rsp, nil
161+
}
162+
163+
// Now check if the target is valid and write to the target
109164
switch {
110165
case strings.HasPrefix(in.Target, "status."):
111166
err = putQueryResultToStatus(req, rsp, in, results, f)
@@ -370,3 +425,52 @@ func putQueryResultToContext(req *fnv1.RunFunctionRequest, rsp *fnv1.RunFunction
370425
rsp.Context = updatedContext
371426
return nil
372427
}
428+
429+
// targetHasData checks if a target field already has data
430+
func targetHasData(data map[string]interface{}, key string) (bool, error) {
431+
parts, err := ParseNestedKey(key)
432+
if err != nil {
433+
return false, err
434+
}
435+
436+
currentValue := interface{}(data)
437+
for _, k := range parts {
438+
// Check if the current value is a map
439+
if nestedMap, ok := currentValue.(map[string]interface{}); ok {
440+
// Get the next value in the nested map
441+
if nextValue, exists := nestedMap[k]; exists {
442+
currentValue = nextValue
443+
} else {
444+
// Key doesn't exist, so no data
445+
return false, nil
446+
}
447+
} else {
448+
// Not a map, so can't traverse further
449+
return false, nil
450+
}
451+
}
452+
453+
// If we've reached here, the key exists
454+
// Check if it has meaningful data (not nil and not empty)
455+
if currentValue == nil {
456+
return false, nil
457+
}
458+
459+
// Check for empty maps
460+
if nestedMap, ok := currentValue.(map[string]interface{}); ok {
461+
return len(nestedMap) > 0, nil
462+
}
463+
464+
// Check for empty slices
465+
if slice, ok := currentValue.([]interface{}); ok {
466+
return len(slice) > 0, nil
467+
}
468+
469+
// For strings, check if empty
470+
if str, ok := currentValue.(string); ok {
471+
return str != "", nil
472+
}
473+
474+
// For other types (numbers, booleans), consider them as having data
475+
return true, nil
476+
}

0 commit comments

Comments
 (0)