pkg/deploy/lattice/rule_synthesizer.go (146 lines of code) (raw):
package lattice
import (
"context"
"errors"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-application-networking-k8s/pkg/model/core"
model "github.com/aws/aws-application-networking-k8s/pkg/model/lattice"
"github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog"
)
type ruleSynthesizer struct {
log gwlog.Logger
ruleManager RuleManager
tgManager TargetGroupManager
stack core.Stack
}
func NewRuleSynthesizer(
log gwlog.Logger,
ruleManager RuleManager,
tgManager TargetGroupManager,
stack core.Stack,
) *ruleSynthesizer {
return &ruleSynthesizer{
log: log,
ruleManager: ruleManager,
tgManager: tgManager,
stack: stack,
}
}
// helper types for checking which leftover rules are no longer referenced
// and need to be deleted
type ruleIdMap map[string]*model.Rule
type snlKey struct {
SvcId string
ListenerId string
}
func (r *ruleSynthesizer) Synthesize(ctx context.Context) error {
var resRule []*model.Rule
err := r.stack.ListResources(&resRule)
if err != nil {
return err
}
// svc id -> listener id -> rule id
snlStackRules := make(map[snlKey]ruleIdMap)
for _, rule := range resRule {
// this will also populate our map with rules for each service+listener
err = r.createOrUpdateRules(ctx, rule, snlStackRules)
if err != nil {
return err
}
}
// for each service/listener, remove any lingering lattice rules
err = r.deleteStaleLatticeRules(ctx, snlStackRules)
if err != nil {
return err
}
// now we have a clean set of rules, update priorities accordingly
err = r.adjustPriorities(ctx, snlStackRules, resRule)
if err != nil {
return err
}
return nil
}
func (r *ruleSynthesizer) createOrUpdateRules(ctx context.Context, rule *model.Rule, snlRules map[snlKey]ruleIdMap) error {
stackListener, stackSvc, err := r.getStackObjects(rule)
if err != nil {
return err
}
err = r.tgManager.ResolveRuleTgIds(ctx, &rule.Spec.Action, r.stack)
if err != nil {
return err
}
status, err := r.ruleManager.Upsert(ctx, rule, stackListener, stackSvc)
if err != nil {
return fmt.Errorf("failed RuleManager.Upsert due to %s", err)
}
rule.Status = &status
// build a map svc + listener -> all current rules
key := snlKey{
SvcId: stackSvc.Status.Id,
ListenerId: stackListener.Status.Id,
}
var ok bool
var ruleMap ruleIdMap
if ruleMap, ok = snlRules[key]; !ok {
// create and add a map if there isn't one already
ruleMap = make(ruleIdMap)
snlRules[key] = ruleMap
}
ruleMap[rule.Status.Id] = rule
return nil
}
func (r *ruleSynthesizer) deleteStaleLatticeRules(ctx context.Context, snlRules map[snlKey]ruleIdMap) error {
var delErr error
for snl := range snlRules {
allLatticeRules, err := r.ruleManager.List(ctx, snl.SvcId, snl.ListenerId)
if err != nil {
return fmt.Errorf("failed RuleManager.List %s/%s, due to %s", snl.SvcId, snl.ListenerId, err)
}
activeRules := snlRules[snl]
for _, lr := range allLatticeRules {
if aws.BoolValue(lr.IsDefault) {
continue
}
// if the rule is not in our list of ids, we need to remove it
// make sure to skip the default
ruleId := aws.StringValue(lr.Id)
if _, ok := activeRules[ruleId]; !ok {
err := r.ruleManager.Delete(ctx, ruleId, snl.SvcId, snl.ListenerId)
if err != nil {
delErr = errors.Join(delErr,
fmt.Errorf("failed RuleManager.Delete %s/%s/%s, due to %s", snl.SvcId, snl.ListenerId, ruleId, err))
}
}
}
}
return delErr
}
func (r *ruleSynthesizer) adjustPriorities(ctx context.Context, snlStackRules map[snlKey]ruleIdMap, resRule []*model.Rule) error {
var updateErr error
for snl := range snlStackRules {
activeRules := snlStackRules[snl]
for _, rule := range activeRules {
if rule.Spec.Priority != rule.Status.Priority {
// *any* mismatch in priority prompts a batch update of ALL priorities
r.log.Debugf(ctx, "Found rule priority mismatch, update required")
var rulesToUpdate []*model.Rule
for _, snlRule := range activeRules {
rulesToUpdate = append(rulesToUpdate, snlRule)
}
err := r.ruleManager.UpdatePriorities(ctx, snl.SvcId, snl.ListenerId, rulesToUpdate)
if err != nil {
updateErr = errors.Join(updateErr,
fmt.Errorf("failed RuleManager.UpdatePriorities for rules %+v due to %s", resRule, err))
}
break
}
}
}
return updateErr
}
func (r *ruleSynthesizer) getStackObjects(rule *model.Rule) (*model.Listener, *model.Service, error) {
listener := &model.Listener{}
err := r.stack.GetResource(rule.Spec.StackListenerId, listener)
if err != nil {
return nil, nil, err
}
svc := &model.Service{}
err = r.stack.GetResource(listener.Spec.StackServiceId, svc)
if err != nil {
return nil, nil, err
}
return listener, svc, nil
}
func (r *ruleSynthesizer) PostSynthesize(ctx context.Context) error {
return nil
}