Skip to content

Commit da07616

Browse files
committed
Implement code generator for handling backoff-retry during late initialization.
1 parent 2568c25 commit da07616

File tree

6 files changed

+350
-86
lines changed

6 files changed

+350
-86
lines changed

pkg/generate/ack/controller.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,15 @@ var (
111111
"GoCodeSetResourceIdentifiers": func(r *ackmodel.CRD, sourceVarName string, targetVarName string, indentLevel int) string {
112112
return code.SetResourceIdentifiers(r.Config(), r, sourceVarName, targetVarName, indentLevel)
113113
},
114-
"GoCodeFindLateInitializedFields": func(r *ackmodel.CRD, indentLevel int) string {
115-
return code.FindLateInitializedFieldsWithDelay(r.Config(), r, indentLevel)
114+
"GoCodeFindLateInitializedFieldNames": func(r *ackmodel.CRD, resVarName string, indentLevel int) string {
115+
return code.FindLateInitializedFieldNames(r.Config(), r, resVarName, indentLevel)
116116
},
117117
"GoCodeLateInitializeFromReadOne": func(r *ackmodel.CRD, koSourceVarName string, koTargetVarName string, indentLevel int) string {
118118
return code.LateInitializeFromReadOne(r.Config(), r, koSourceVarName, koTargetVarName, indentLevel)
119119
},
120+
"GoCodeCalculateRequeueDelay": func(r *ackmodel.CRD, koVarName string, numLateInitAttemptVarName string, requeueDelayVarName string, incompleteInitializationVarName string, indentLevel int) string {
121+
return code.CalculateRequeueDelay(r.Config(), r, koVarName, numLateInitAttemptVarName, requeueDelayVarName, incompleteInitializationVarName, indentLevel)
122+
},
120123
}
121124
)
122125

pkg/generate/code/late_initialize.go

Lines changed: 171 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,55 +22,51 @@ import (
2222
"github.com/aws-controllers-k8s/code-generator/pkg/model"
2323
)
2424

25-
// FindLateInitializedFieldsWithDelay outputs the code to create a map of fieldName to
26-
// late intialization delay in seconds.
27-
func FindLateInitializedFieldsWithDelay(
25+
// FindLateInitializedFieldNames outputs the code to create a sorted slice of fieldNames to
26+
// late initialize. This slice helps with short circuiting the AWSResourceManager.LateInitialize()
27+
// method if there are no fields to late initialize.
28+
//
29+
// Sample Output:
30+
// var lateInitializeFieldNames = []string{"Name"}
31+
func FindLateInitializedFieldNames(
2832
cfg *ackgenconfig.Config,
2933
r *model.CRD,
34+
resVarName string,
3035
// Number of levels of indentation to use
3136
indentLevel int,
3237
) string {
33-
//Sample output
34-
//var lateInitializeFieldToDelaySeconds = map[string]int{"Name": 0}
3538
out := ""
3639
indent := strings.Repeat("\t", indentLevel)
37-
fieldNameToConfig := cfg.ResourceFields(r.Names.Original)
38-
if len(fieldNameToConfig) > 0 {
39-
fieldNameToDelaySeconds := make(map[string]int)
40-
sortedLateInitFieldNames := make([]string, 0)
41-
for fName, fConfig := range fieldNameToConfig {
42-
if fConfig != nil && fConfig.LateInitialize != nil {
43-
fieldNameToDelaySeconds[fName] = fConfig.LateInitialize.DelaySeconds
44-
sortedLateInitFieldNames = append(sortedLateInitFieldNames, fName)
45-
}
46-
}
47-
sort.Strings(sortedLateInitFieldNames)
48-
lateInitFieldToDelayValues := ""
49-
if len(sortedLateInitFieldNames) > 0 {
50-
for _, fName := range sortedLateInitFieldNames {
51-
lateInitFieldToDelayValues += fmt.Sprintf("%q:%d,", fName, fieldNameToDelaySeconds[fName])
52-
}
53-
out += fmt.Sprintf("%svar lateInitializeFieldToDelaySeconds = map[string]int{%s}\n", indent, lateInitFieldToDelayValues)
40+
sortedFieldNames, _ := getSortedLateInitFieldsAndConfig(cfg, r)
41+
if len(sortedFieldNames) > 0 {
42+
out += fmt.Sprintf("%svar %s = []string{", indent, resVarName)
43+
for _, fName := range sortedFieldNames {
44+
out += fmt.Sprintf("%q,", fName)
5445
}
46+
out += "}\n"
5547
}
5648
return out
5749
}
5850

59-
// findLateInitializedFieldNames returns the field names which have LateInitialization configuration inside generator config
60-
func findLateInitializedFieldNames(
51+
// getSortedLateInitFieldsAndConfig returns the field names in alphabetically sorted order which have LateInitialization
52+
// configuration inside generator config and also a map from fieldName to LateInitializationConfig.
53+
func getSortedLateInitFieldsAndConfig(
6154
cfg *ackgenconfig.Config,
6255
r *model.CRD,
63-
) []string {
64-
fieldNames := make([]string, 0)
56+
) ([]string, map[string]*ackgenconfig.LateInitializeConfig) {
6557
fieldNameToConfig := cfg.ResourceFields(r.Names.Original)
58+
fieldNameToLateInitConfig := make(map[string]*ackgenconfig.LateInitializeConfig)
59+
sortedLateInitFieldNames := make([]string, 0)
6660
if len(fieldNameToConfig) > 0 {
6761
for fName, fConfig := range fieldNameToConfig {
6862
if fConfig != nil && fConfig.LateInitialize != nil {
69-
fieldNames = append(fieldNames, fName)
63+
fieldNameToLateInitConfig[fName] = fConfig.LateInitialize
64+
sortedLateInitFieldNames = append(sortedLateInitFieldNames, fName)
7065
}
7166
}
67+
sort.Strings(sortedLateInitFieldNames)
7268
}
73-
return fieldNames
69+
return sortedLateInitFieldNames, fieldNameToLateInitConfig
7470
}
7571

7672
// LateInitializeFromReadOne returns the gocode to set LateInitialization fields from the ReadOne output
@@ -132,13 +128,11 @@ func LateInitializeFromReadOne(
132128
indentLevel int,
133129
) string {
134130
out := ""
135-
lateInitializedFieldNames := findLateInitializedFieldNames(cfg, r)
136-
// sorting helps produce consistent output for unit test reliability
137-
sort.Strings(lateInitializedFieldNames)
131+
lateInitializedFieldNames, _ := getSortedLateInitFieldsAndConfig(cfg, r)
138132
// TODO(vijat@): Add validation for correct field path in lateInitializedFieldNames
139133
for _, fName := range lateInitializedFieldNames {
140134
// split the field name by period
141-
// each substring represents a field. No support for '..' currently
135+
// each substring represents a field.
142136
fNameParts := strings.Split(fName, ".")
143137
// fNameIndentLevel tracks the indentation level for every new line added
144138
// This variable is incremented when building nested if blocks and decremented when closing those if blocks.
@@ -156,14 +150,14 @@ func LateInitializeFromReadOne(
156150
indent := strings.Repeat("\t", fNameIndentLevel)
157151
fNamePartAccesor := fmt.Sprintf("Spec%s.%s", fParentPath, fNamePart)
158152
if mapShapedParent {
159-
fNamePartAccesor = fmt.Sprintf("Spec%s[%s]", fParentPath, fNamePart)
153+
fNamePartAccesor = fmt.Sprintf("Spec%s[%q]", fParentPath, fNamePart)
160154
}
161155
// Handling for all parts except last one
162156
if i != len(fNameParts)-1 {
163157
out += fmt.Sprintf("%sif %s.%s != nil && %s.%s != nil {\n", indent, sourceKoVarName, fNamePartAccesor, targetKoVarName, fNamePartAccesor)
164158
// update fParentPath and fNameIndentLevel for next iteration
165159
if mapShapedParent {
166-
fParentPath = fmt.Sprintf("%s[%s]", fParentPath, fNamePart)
160+
fParentPath = fmt.Sprintf("%s[%q]", fParentPath, fNamePart)
167161
mapShapedParent = false
168162
} else {
169163
fParentPath = fmt.Sprintf("%s.%s", fParentPath, fNamePart)
@@ -188,3 +182,146 @@ func LateInitializeFromReadOne(
188182
}
189183
return out
190184
}
185+
186+
// CalculateRequeueDelay returns the go code which
187+
// a) checks whether all the fields are late initialized and
188+
// b) if any fields are not initialized, updates the 'delayVarNameInt' and 'incompleteInitializationVarNameBool', which
189+
// are used to requeue the requests based on the delay configured in LateInitializationConfig.
190+
//
191+
// Sample GeneratorConfig:
192+
// fields:
193+
// Name:
194+
// late_initialize: {}
195+
// ImageScanningConfiguration.ScanOnPush:
196+
// late_initialize:
197+
// min_backoff_seconds: 5
198+
// max_backoff_seconds: 15
199+
// map..subfield.x:
200+
// late_initialize:
201+
// min_backoff_seconds: 5
202+
// another.map..lastfield:
203+
// late_initialize:
204+
// min_backoff_seconds: 5
205+
// some.list:
206+
// late_initialize:
207+
// min_backoff_seconds: 10
208+
// structA.mapB..structC.valueD:
209+
// late_initialize:
210+
// min_backoff_seconds: 20
211+
//
212+
//
213+
// Sample Output:
214+
// if koWithDefaults.Spec.ImageScanningConfiguration != nil {
215+
// if koWithDefaults.Spec.ImageScanningConfiguration.ScanOnPush == nil {
216+
// delay := (&acktypes.LateInitializationRetryConfig{MinBackoffSeconds:5, MaxBackoffSeconds: 15,}).GetExponentialBackoffSeconds(numInitAttempt)
217+
// requeueDelay = int(math.Max(float64(requeueDelay), float64(delay)))
218+
// incompleteInitialization = true
219+
// }
220+
// }
221+
// if koWithDefaults.Spec.Name == nil {
222+
// delay := (&acktypes.LateInitializationRetryConfig{MinBackoffSeconds:0, MaxBackoffSeconds: 0,}).GetExponentialBackoffSeconds(numInitAttempt)
223+
// requeueDelay = int(math.Max(float64(requeueDelay), float64(delay)))
224+
// incompleteInitialization = true
225+
// }
226+
// if koWithDefaults.Spec.another != nil {
227+
// if koWithDefaults.Spec.another.map != nil {
228+
// if koWithDefaults.Spec.another.map["lastfield"] == nil {
229+
// delay := (&acktypes.LateInitializationRetryConfig{MinBackoffSeconds:5, MaxBackoffSeconds: 0,}).GetExponentialBackoffSeconds(numInitAttempt)
230+
// requeueDelay = int(math.Max(float64(requeueDelay), float64(delay)))
231+
// incompleteInitialization = true
232+
// }
233+
// }
234+
// }
235+
// if koWithDefaults.Spec.map != nil {
236+
// if koWithDefaults.Spec.map["subfield"] != nil {
237+
// if koWithDefaults.Spec.map["subfield"].x == nil {
238+
// delay := (&acktypes.LateInitializationRetryConfig{MinBackoffSeconds:5, MaxBackoffSeconds: 0,}).GetExponentialBackoffSeconds(numInitAttempt)
239+
// requeueDelay = int(math.Max(float64(requeueDelay), float64(delay)))
240+
// incompleteInitialization = true
241+
// }
242+
// }
243+
// }
244+
// if koWithDefaults.Spec.some != nil {
245+
// if koWithDefaults.Spec.some.list == nil {
246+
// delay := (&acktypes.LateInitializationRetryConfig{MinBackoffSeconds:10, MaxBackoffSeconds: 0,}).GetExponentialBackoffSeconds(numInitAttempt)
247+
// requeueDelay = int(math.Max(float64(requeueDelay), float64(delay)))
248+
// incompleteInitialization = true
249+
// }
250+
// }
251+
// if koWithDefaults.Spec.structA != nil {
252+
// if koWithDefaults.Spec.structA.mapB != nil {
253+
// if koWithDefaults.Spec.structA.mapB["structC"] != nil {
254+
// if koWithDefaults.Spec.structA.mapB["structC"].valueD == nil {
255+
// delay := (&acktypes.LateInitializationRetryConfig{MinBackoffSeconds:20, MaxBackoffSeconds: 0,}).GetExponentialBackoffSeconds(numInitAttempt)
256+
// requeueDelay = int(math.Max(float64(requeueDelay), float64(delay)))
257+
// incompleteInitialization = true
258+
// }
259+
// }
260+
// }
261+
// }
262+
func CalculateRequeueDelay(
263+
cfg *ackgenconfig.Config,
264+
r *model.CRD,
265+
koVarName string,
266+
initAttemptVarName string,
267+
delayVarNameInt string,
268+
incompleteInitializationVarNameBool string,
269+
// Number of levels of indentation to use
270+
indentLevel int,
271+
) string {
272+
out := ""
273+
sortedLateInitFieldNames, fieldNameToLateInitConfig := getSortedLateInitFieldsAndConfig(cfg, r)
274+
for _, fName := range sortedLateInitFieldNames {
275+
// split the field name by period
276+
// each substring represents a field.
277+
fNameParts := strings.Split(fName, ".")
278+
// fNameIndentLevel tracks the indentation level for every new line added
279+
// This variable is incremented when building nested if blocks and decremented when closing those if blocks.
280+
fNameIndentLevel := indentLevel
281+
// fParentPath keeps track of parent path for any fNamePart
282+
fParentPath := ""
283+
mapShapedParent := false
284+
for i, fNamePart := range fNameParts {
285+
if fNamePart == "" {
286+
mapShapedParent = true
287+
continue
288+
}
289+
indent := strings.Repeat("\t", fNameIndentLevel)
290+
fNamePartAccesor := fmt.Sprintf("Spec%s.%s", fParentPath, fNamePart)
291+
if mapShapedParent {
292+
fNamePartAccesor = fmt.Sprintf("Spec%s[%q]", fParentPath, fNamePart)
293+
}
294+
// Handling for all parts except last one
295+
if i != len(fNameParts)-1 {
296+
out += fmt.Sprintf("%sif %s.%s != nil {\n", indent, koVarName, fNamePartAccesor)
297+
// update fParentPath and fNameIndentLevel for next iteration
298+
if mapShapedParent {
299+
fParentPath = fmt.Sprintf("%s[%q]", fParentPath, fNamePart)
300+
mapShapedParent = false
301+
} else {
302+
fParentPath = fmt.Sprintf("%s.%s", fParentPath, fNamePart)
303+
}
304+
fNameIndentLevel = fNameIndentLevel + 1
305+
} else {
306+
// handle last part here
307+
// for last part, if the late initialized field is still nil, calculate the retry backoff using
308+
// acktypes.LateInitializationRetryConfig abstraction and set the incompleteInitialization flag to true
309+
out += fmt.Sprintf("%sif %s.%s == nil {\n", indent, koVarName, fNamePartAccesor)
310+
fNameIndentLevel = fNameIndentLevel + 1
311+
indent = strings.Repeat("\t", fNameIndentLevel)
312+
minBackoffSeconds := fieldNameToLateInitConfig[fName].MinBackoffSeconds
313+
maxBackoffSeconds := fieldNameToLateInitConfig[fName].MaxBackoffSeconds
314+
out += fmt.Sprintf("%sdelay := (&acktypes.LateInitializationRetryConfig{MinBackoffSeconds:%d, MaxBackoffSeconds: %d,}).GetExponentialBackoffSeconds(%s)\n", indent, minBackoffSeconds, maxBackoffSeconds, initAttemptVarName)
315+
out += fmt.Sprintf("%s%s = int(math.Max(float64(%s), float64(delay)))\n", indent, delayVarNameInt, delayVarNameInt)
316+
out += fmt.Sprintf("%s%s = true\n", indent, incompleteInitializationVarNameBool)
317+
}
318+
}
319+
// Close all if blocks with proper indentation
320+
fNameIndentLevel = fNameIndentLevel - 1
321+
for fNameIndentLevel >= indentLevel {
322+
out += fmt.Sprintf("%s}\n", strings.Repeat("\t", fNameIndentLevel))
323+
fNameIndentLevel = fNameIndentLevel - 1
324+
}
325+
}
326+
return out
327+
}

0 commit comments

Comments
 (0)