package runner
import (
"fmt"
"lunar/engine/actions"
"lunar/engine/config"
lunarMessages "lunar/engine/messages"
"lunar/engine/services"
"lunar/engine/services/diagnoses"
sharedActions "lunar/shared-model/actions"
sharedConfig "lunar/shared-model/config"
"github.com/rs/zerolog/log"
)
const (
unknownRemedyError = "error running OnResponse for remedy [%+v]. " +
"Unknown or undefined remedy type: %v"
)
type runResult[A any, R any] struct {
action A
activeRemedies map[sharedConfig.RemedyType][]R
}
type (
requestRunResult runResult[actions.ReqLunarAction, sharedActions.RemedyReqRunResult]
responseRunResult runResult[actions.RespLunarAction, sharedActions.RemedyRespRunResult]
)
func runOnRequest(
args lunarMessages.OnRequest,
remedies []config.ScopedRemedy,
services *services.RemedyPlugins,
accounts map[sharedConfig.AccountID]sharedConfig.Account,
) (requestRunResult, error) {
var prioritizedAction actions.ReqLunarAction = &actions.NoOpAction{}
activeRemedies := map[sharedConfig.RemedyType][]sharedActions.RemedyReqRunResult{}
for _, remedy := range remedies {
action, err := remedyOnRequest(args, remedy, accounts, services)
if err != nil {
return requestRunResult{
action: nil,
activeRemedies: map[sharedConfig.RemedyType][]sharedActions.RemedyReqRunResult{},
}, err
}
if action.ReqRunResult() != sharedActions.ReqNoOp {
activeRemedies[remedy.Remedy.Type()] = append(
activeRemedies[remedy.Remedy.Type()],
action.ReqRunResult(),
)
}
action.EnsureRequestIsUpdated(&args)
prioritizedAction = prioritizedAction.ReqPrioritize(action)
}
return requestRunResult{
action: prioritizedAction,
activeRemedies: activeRemedies,
}, nil
}
func runOnResponse(
args lunarMessages.OnResponse,
remedies []config.ScopedRemedy,
services *services.RemedyPlugins,
) (responseRunResult, error) {
var prioritizedAction actions.RespLunarAction = &actions.NoOpAction{}
activeRemedies := map[sharedConfig.RemedyType][]sharedActions.RemedyRespRunResult{}
for _, remedy := range remedies {
action, err := remedyOnResponse(args, remedy, services)
if err != nil {
return responseRunResult{
action: nil,
activeRemedies: map[sharedConfig.RemedyType][]sharedActions.RemedyRespRunResult{},
}, err
}
if action.RespRunResult() != sharedActions.RespNoOp {
activeRemedies[remedy.Remedy.Type()] = append(
activeRemedies[remedy.Remedy.Type()],
action.RespRunResult(),
)
}
action.EnsureResponseIsUpdated(&args)
prioritizedAction = prioritizedAction.RespPrioritize(action)
}
return responseRunResult{
action: prioritizedAction,
activeRemedies: activeRemedies,
}, nil
}
func runOnTransaction(
onRequest lunarMessages.OnRequest,
onResponse lunarMessages.OnResponse,
diagnoses []*config.ScopedDiagnosis,
services *services.DiagnosisPlugins,
exporters *services.Exporters,
policyTree *config.EndpointPolicyTree,
) {
for _, diagnosis := range diagnoses {
output := diagnosisOnTransaction(
onRequest,
onResponse,
diagnosis,
services,
policyTree,
)
if output == nil {
log.Debug().
Msg("could not obtain diagnosis output, will not export anything")
return
}
exportDiagnosisOutput(output, diagnosis, exporters)
}
}
func remedyOnRequest(
args lunarMessages.OnRequest,
scopedRemedy config.ScopedRemedy,
accounts map[sharedConfig.AccountID]sharedConfig.Account,
services *services.RemedyPlugins,
) (actions.ReqLunarAction, error) {
remedy := scopedRemedy.Remedy
switch remedyType := remedy.Type(); remedyType {
case sharedConfig.RemedyCaching:
return services.CachingPlugin.OnRequest(
args, remedy.Config.Caching, scopedRemedy.PathParams)
case sharedConfig.RemedyResponseBasedThrottling:
return services.ResponseBasedThrottlingPlugin.OnRequest(
args,
remedy.Config.ResponseBasedThrottling,
)
case sharedConfig.RemedyStrategyBasedThrottling:
return services.StrategyBasedThrottlingPlugin.OnRequest(
args,
scopedRemedy,
)
case sharedConfig.RemedyConcurrencyBasedThrottling:
return services.ConcurrencyBasedThrottlingPlugin.OnRequest(
args,
scopedRemedy,
)
case sharedConfig.RemedyStrategyBasedQueue:
return services.StrategyBasedQueuePlugin.OnRequest(args, scopedRemedy)
case sharedConfig.RemedyAccountOrchestration:
return services.AccountOrchestrationPlugin.OnRequest(
args,
remedy.Config.AccountOrchestration,
accounts,
)
case sharedConfig.RemedyFixedResponse:
return services.FixedResponsePlugin.OnRequest(
args,
remedy.Config.FixedResponse,
)
case sharedConfig.RemedyRetry:
return services.RetryPlugin.OnRequest(args, remedy.Config.Retry)
case sharedConfig.RemedyAuth:
return services.AuthPlugin.OnRequest(
args,
scopedRemedy,
accounts,
)
case sharedConfig.RemedyUndefined:
return nil,
fmt.Errorf(unknownRemedyError, remedy, remedyType)
default:
return nil,
fmt.Errorf(unknownRemedyError, remedy, remedyType)
}
}
func remedyOnResponse(
args lunarMessages.OnResponse,
scopedRemedy config.ScopedRemedy,
services *services.RemedyPlugins,
) (actions.RespLunarAction, error) {
remedy := scopedRemedy.Remedy
switch remedyType := remedy.Type(); remedyType {
case sharedConfig.RemedyCaching:
return services.CachingPlugin.OnResponse(args, remedy.Config.Caching,
scopedRemedy.PathParams)
case sharedConfig.RemedyResponseBasedThrottling:
return services.ResponseBasedThrottlingPlugin.OnResponse(
args,
remedy.Config.ResponseBasedThrottling,
)
case sharedConfig.RemedyStrategyBasedThrottling:
return services.StrategyBasedThrottlingPlugin.OnResponse(
args,
scopedRemedy,
)
case sharedConfig.RemedyConcurrencyBasedThrottling:
return services.ConcurrencyBasedThrottlingPlugin.OnResponse(
args,
scopedRemedy,
)
case sharedConfig.RemedyStrategyBasedQueue:
return services.StrategyBasedQueuePlugin.OnResponse(args, scopedRemedy)
case sharedConfig.RemedyAccountOrchestration:
return services.AccountOrchestrationPlugin.OnResponse(
args,
remedy.Config.AccountOrchestration,
)
case sharedConfig.RemedyFixedResponse:
return services.FixedResponsePlugin.OnResponse(
args,
remedy.Config.FixedResponse,
)
case sharedConfig.RemedyRetry:
return services.RetryPlugin.OnResponse(args, remedy.Config.Retry)
case sharedConfig.RemedyAuth:
return services.AuthPlugin.OnResponse()
case sharedConfig.RemedyUndefined:
return nil, fmt.Errorf(unknownRemedyError, remedy, remedyType)
default:
return nil, fmt.Errorf(unknownRemedyError, remedy, remedyType)
}
}
func diagnosisOnTransaction(
onRequest lunarMessages.OnRequest,
onResponse lunarMessages.OnResponse,
scopedDiagnosis *config.ScopedDiagnosis,
diagnosisPlugins *services.DiagnosisPlugins,
policyTree *config.EndpointPolicyTree,
) *diagnoses.DiagnosisOutput {
var err error
var diagnosisOutput *diagnoses.DiagnosisOutput
switch diagnosisType := scopedDiagnosis.Diagnosis.Type(); diagnosisType {
case sharedConfig.DiagnosisHARExporter:
diagnosisOutput, err = diagnosisPlugins.HARGeneratorPlugin.OnTransaction(
onRequest,
onResponse,
policyTree,
scopedDiagnosis,
)
if err != nil {
log.Error().
Err(err).
Msg("Failed to run HARGenerator diagnosis plugin")
}
case sharedConfig.DiagnosisMetricsCollector:
diagnosisOutput, err = diagnosisPlugins.MetricsCollector.OnTransaction(
onRequest,
onResponse,
policyTree,
scopedDiagnosis,
)
if err != nil {
log.Error().
Err(err).
Msg("Failed to run MetricsCollector diagnosis plugin")
}
case sharedConfig.DiagnosisVoid:
diagnosisOutput, err = diagnosisPlugins.Void.OnTransaction(
onRequest,
onResponse,
policyTree,
scopedDiagnosis,
)
if err != nil {
log.Error().
Err(err).
Msg("Failed to run Void diagnosis plugin")
}
case sharedConfig.DiagnosisUndefined:
log.Warn().Msgf("Error running OnTransaction for diagnosis [%+v]. "+
"Unknown or undefined type: %v", scopedDiagnosis, diagnosisType)
}
return diagnosisOutput
}
func exportDiagnosisOutput(
diagnosisOutput *diagnoses.DiagnosisOutput,
diagnosis *config.ScopedDiagnosis,
exporter *services.Exporters,
) {
var err error
exporterType := diagnosis.Diagnosis.ExporterType()
switch diagnosis.Diagnosis.ExporterKind() {
case sharedConfig.ExporterKindRawData:
err = exporter.Content.Export(*diagnosisOutput, exporterType)
case sharedConfig.ExporterKindMetrics:
err = exporter.Prometheus.Export(*diagnosisOutput)
case sharedConfig.ExporterKindUndefined:
log.Error().
Msg("Diagnosis has no exporter defined, will not export output")
}
if err != nil {
log.Error().Err(err).
Msgf("Failed to export output from diagnosis %v", diagnosis.Diagnosis.Name)
}
}