Skip to content

Commit bb065a7

Browse files
jaypipesa-hilaly
authored andcommitted
add parameter metadata lookup to param handling
Adds a cache to the DB parameter group hooks.go file that stores metadata about engine family parameter defaults. This metadata informs the resource manager whether a particular parameter is modifiable and whether changes to it can be applied immediately or require a reboot of the DB instance. The cache is a simple one, with no support for TTL or expiry and because there is nowhere to "attach" the cache that has access to the SDK API object (in order to call RDS `DescribeEngineDefaultParameters` API), the cache's `get` method has to have a "fetcher" function pointer passed into it. This isn't super clean but there isn't currently a way to, say, add the cache as a member of the `AWSResourceManager` struct since that is all code-generated and this is a specific to the DB Parameter Group resource manager. Signed-off-by: Jay Pipes <[email protected]>
1 parent ada76ca commit bb065a7

File tree

3 files changed

+192
-7
lines changed

3 files changed

+192
-7
lines changed

apis/v1alpha1/ack-generate-metadata.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ ack_generate_info:
33
build_hash: fe61d04673fd4d9848d5f726b01e0689a16d3733
44
go_version: go1.18.2
55
version: v0.19.3-1-gfe61d04
6-
api_directory_checksum: 420612ef2bc7cf0b19ebe41c44626560e060c4a1
6+
api_directory_checksum: 83bde1f77a49ebd8acd36a1c5e231138b67aa883
77
api_version: v1alpha1
88
aws_sdk_go_version: v1.44.27
99
generator_config_info:
10-
file_checksum: 90a2d0d868a32e6474738ad70a557035f490780a
10+
file_checksum: 5d126ead4715f729c2fda976c422eb14a948f3da
1111
original_file_name: generator.yaml
1212
last_modification:
1313
reason: API generation

pkg/resource/db_parameter_group/hooks.go

Lines changed: 181 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ package db_parameter_group
1515

1616
import (
1717
"context"
18+
"fmt"
19+
"sync"
1820

1921
ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare"
22+
ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors"
2023
ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log"
2124

2225
svcapitypes "github.com/aws-controllers-k8s/rds-controller/apis/v1alpha1"
@@ -27,10 +30,34 @@ import (
2730
)
2831

2932
const (
33+
applyTypeStatic = "static"
3034
sourceUser = "user"
3135
maxResetParametersSize = 20
3236
)
3337

38+
var (
39+
errUnknownParameter = fmt.Errorf("unknown parameter")
40+
errUnmodifiableParameter = fmt.Errorf("parameter is not modifiable")
41+
)
42+
43+
func newErrUnknownParameter(name string) error {
44+
// This is a terminal error because unless the user removes this parameter
45+
// from their list of parameter overrides, we will not be able to get the
46+
// resource into a synced state.
47+
return ackerr.NewTerminalError(
48+
fmt.Errorf("%w: %s", errUnknownParameter, name),
49+
)
50+
}
51+
52+
func newErrUnmodifiableParameter(name string) error {
53+
// This is a terminal error because unless the user removes this parameter
54+
// from their list of parameter overrides, we will not be able to get the
55+
// resource into a synced state.
56+
return ackerr.NewTerminalError(
57+
fmt.Errorf("%w: %s", errUnmodifiableParameter, name),
58+
)
59+
}
60+
3461
// customUpdate is required to fix
3562
// https://github.com/aws-controllers-k8s/community/issues/869.
3663
//
@@ -206,7 +233,8 @@ func (rm *resourceManager) syncParameters(
206233
exit := rlog.Trace("rm.syncParameters")
207234
defer func() { exit(err) }()
208235

209-
groupName := (*string)(latest.ko.Spec.Name)
236+
groupName := latest.ko.Spec.Name
237+
family := latest.ko.Spec.Family
210238

211239
toModify, toDelete := computeParametersDelta(
212240
desired.ko.Spec.ParameterOverrides, latest.ko.Spec.ParameterOverrides,
@@ -219,7 +247,8 @@ func (rm *resourceManager) syncParameters(
219247
if len(toDelete) > 0 {
220248
chunks := sliceStringChunks(toDelete, maxResetParametersSize)
221249
for _, chunk := range chunks {
222-
if err = rm.resetParameters(ctx, groupName, chunk); err != nil {
250+
err = rm.resetParameters(ctx, family, groupName, chunk)
251+
if err != nil {
223252
return err
224253
}
225254
}
@@ -228,7 +257,8 @@ func (rm *resourceManager) syncParameters(
228257
if len(toModify) > 0 {
229258
chunks := mapStringChunks(toModify, maxResetParametersSize)
230259
for _, chunk := range chunks {
231-
if err = rm.modifyParameters(ctx, groupName, chunk); err != nil {
260+
err = rm.modifyParameters(ctx, family, groupName, chunk)
261+
if err != nil {
232262
return err
233263
}
234264
}
@@ -283,20 +313,37 @@ func (rm *resourceManager) getParameters(
283313
// no more than 20 parameters to reset.
284314
func (rm *resourceManager) resetParameters(
285315
ctx context.Context,
316+
family *string,
286317
groupName *string,
287318
toDelete []string,
288319
) (err error) {
289320
rlog := ackrtlog.FromContext(ctx)
290321
exit := rlog.Trace("rm.resetParameters")
291322
defer func() { exit(err) }()
292323

324+
var pMeta *paramMeta
293325
inputParams := []*svcsdk.Parameter{}
294326
for _, paramName := range toDelete {
327+
// default to this if something goes wrong looking up parameter
328+
// defaults
329+
applyMethod := svcsdk.ApplyMethodImmediate
330+
pMeta, err = cachedParamMeta.get(
331+
ctx, *family, paramName, rm.getFamilyParameters,
332+
)
333+
if err != nil {
334+
return err
335+
}
336+
if !pMeta.isModifiable {
337+
return newErrUnmodifiableParameter(paramName)
338+
}
339+
if !pMeta.isDynamic {
340+
applyMethod = svcsdk.ApplyMethodPendingReboot
341+
}
295342
p := &svcsdk.Parameter{
296343
ParameterName: aws.String(paramName),
297344
// TODO(jaypipes): Look up appropriate apply method for this
298345
// parameter...
299-
ApplyMethod: aws.String(svcsdk.ApplyMethodImmediate),
346+
ApplyMethod: aws.String(applyMethod),
300347
}
301348
inputParams = append(inputParams, p)
302349
}
@@ -323,21 +370,38 @@ func (rm *resourceManager) resetParameters(
323370
// no more than 20 parameters to modify.
324371
func (rm *resourceManager) modifyParameters(
325372
ctx context.Context,
373+
family *string,
326374
groupName *string,
327375
toModify map[string]*string,
328376
) (err error) {
329377
rlog := ackrtlog.FromContext(ctx)
330378
exit := rlog.Trace("rm.resetParameters")
331379
defer func() { exit(err) }()
332380

381+
var pMeta *paramMeta
333382
inputParams := []*svcsdk.Parameter{}
334383
for paramName, paramValue := range toModify {
384+
// default to this if something goes wrong looking up parameter
385+
// defaults
386+
applyMethod := svcsdk.ApplyMethodImmediate
387+
pMeta, err = cachedParamMeta.get(
388+
ctx, *family, paramName, rm.getFamilyParameters,
389+
)
390+
if err != nil {
391+
return err
392+
}
393+
if !pMeta.isModifiable {
394+
return newErrUnmodifiableParameter(paramName)
395+
}
396+
if !pMeta.isDynamic {
397+
applyMethod = svcsdk.ApplyMethodPendingReboot
398+
}
335399
p := &svcsdk.Parameter{
336400
ParameterName: aws.String(paramName),
337401
ParameterValue: paramValue,
338402
// TODO(jaypipes): Look up appropriate apply method for this
339403
// parameter...
340-
ApplyMethod: aws.String(svcsdk.ApplyMethodImmediate),
404+
ApplyMethod: aws.String(applyMethod),
341405
}
342406
inputParams = append(inputParams, p)
343407
}
@@ -360,6 +424,42 @@ func (rm *resourceManager) modifyParameters(
360424
return nil
361425
}
362426

427+
// getFamilyParameters calls the RDS DescribeEngineDefaultParameters API to
428+
// retrieve the set of parameter information for a DB parameter group family.
429+
func (rm *resourceManager) getFamilyParameters(
430+
ctx context.Context,
431+
family string,
432+
) (map[string]paramMeta, error) {
433+
var marker *string
434+
familyMeta := map[string]paramMeta{}
435+
436+
for {
437+
resp, err := rm.sdkapi.DescribeEngineDefaultParametersWithContext(
438+
ctx,
439+
&svcsdk.DescribeEngineDefaultParametersInput{
440+
DBParameterGroupFamily: aws.String(family),
441+
Marker: marker,
442+
},
443+
)
444+
rm.metrics.RecordAPICall("GET", "DescribeEngineDefaultParameters", err)
445+
if err != nil {
446+
return nil, err
447+
}
448+
for _, param := range resp.EngineDefaults.Parameters {
449+
pName := *param.ParameterName
450+
familyMeta[pName] = paramMeta{
451+
isModifiable: *param.IsModifiable,
452+
isDynamic: *param.ApplyType != applyTypeStatic,
453+
}
454+
}
455+
marker = resp.EngineDefaults.Marker
456+
if marker == nil {
457+
break
458+
}
459+
}
460+
return familyMeta, nil
461+
}
462+
363463
// computeParametersDelta compares two Parameter arrays and returns the new
364464
// parameters to add, to update and the parameter identifiers to delete
365465
func computeParametersDelta(
@@ -431,3 +531,79 @@ func mapStringChunks(
431531

432532
return chunks
433533
}
534+
535+
type paramMeta struct {
536+
isModifiable bool
537+
isDynamic bool
538+
}
539+
540+
// metaFetcher is the functor we pass to the paramMetaCache that allows it to
541+
// fetch engine default parameter information
542+
type metaFetcher func(ctx context.Context, family string) (map[string]paramMeta, error)
543+
544+
// paramMetaCache stores information about a parameter for a DB parameter group
545+
// family. We use this cached information to determine whether a parameter is
546+
// statically or dynamically defined (whether changes can be applied
547+
// immediately or pending a reboot) and whether a parameter is modifiable.
548+
//
549+
// Keeping things super simple for now and not adding any TTL or expiration
550+
// behaviour to the cache. Engine defaults are pretty static information...
551+
type paramMetaCache struct {
552+
sync.RWMutex
553+
hits uint64
554+
misses uint64
555+
cache map[string]map[string]paramMeta
556+
}
557+
558+
// get retrieves the metadata for a named parameter group family and parameter
559+
// name.
560+
func (c *paramMetaCache) get(
561+
ctx context.Context,
562+
family string,
563+
name string,
564+
fetcher metaFetcher,
565+
) (*paramMeta, error) {
566+
c.RLock()
567+
defer c.RUnlock()
568+
569+
var err error
570+
var found bool
571+
var metas map[string]paramMeta
572+
var meta paramMeta
573+
574+
metas, found = c.cache[family]
575+
if !found {
576+
c.misses++
577+
metas, err = c.loadFamily(ctx, family, fetcher)
578+
if err != nil {
579+
return nil, err
580+
}
581+
}
582+
meta, found = metas[name]
583+
if !found {
584+
return nil, newErrUnknownParameter(name)
585+
}
586+
c.hits++
587+
return &meta, nil
588+
}
589+
590+
// loadFamily fetches parameter information from the AWS RDS
591+
// DescribeEngineDefaultParameters API and caches that information.
592+
func (c *paramMetaCache) loadFamily(
593+
ctx context.Context,
594+
family string,
595+
fetcher metaFetcher,
596+
) (map[string]paramMeta, error) {
597+
familyMeta, err := fetcher(ctx, family)
598+
if err != nil {
599+
return nil, err
600+
}
601+
c.Lock()
602+
defer c.Unlock()
603+
c.cache[family] = familyMeta
604+
return familyMeta, nil
605+
}
606+
607+
var cachedParamMeta = paramMetaCache{
608+
cache: map[string]map[string]paramMeta{},
609+
}

pkg/resource/db_parameter_group/sdk.go

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)