Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor scalers parsing to unify them #5037

Open
JorTurFer opened this issue Oct 1, 2023 · 12 comments
Open

Refactor scalers parsing to unify them #5037

JorTurFer opened this issue Oct 1, 2023 · 12 comments
Labels
feature-request All issues for new features that have not been committed to help wanted Looking for support from community needs-discussion stale-bot-ignore All issues that should not be automatically closed by our stale bot

Comments

@JorTurFer
Copy link
Member

Proposal

Currently, each scaler parses the configuration using its own way, some of them use helpers for reading envs, other don't, some use one helper, others use their own helpers...
There is a lot of fragmentation in scalers code and we should unify it

Use-Case

No response

Is this a feature you are interested in implementing yourself?

Maybe

Anything else?

No response

@JorTurFer JorTurFer added needs-discussion feature-request All issues for new features that have not been committed to help wanted Looking for support from community labels Oct 1, 2023
@JorTurFer JorTurFer moved this from To Triage to To Do in Roadmap - KEDA Core Oct 15, 2023
@dttung2905
Copy link
Contributor

I take a look at some use cases inside parse${SCALER_NAME}Metadata method and its true that the way we parse the config varies from scaler to scaler. I also noticed we have a helper method getParameterFromConfig in azure_log_analytics_scaler.go that does exactly this thing i.e. get the string value from either AuthParams or TriggerMetadata and throw out error if we can't find anything.

func getParameterFromConfig(config *ScalerConfig, parameter string, checkAuthParams bool) (string, error) {
if val, ok := config.AuthParams[parameter]; checkAuthParams && ok && val != "" {
return val, nil
} else if val, ok := config.TriggerMetadata[parameter]; ok && val != "" {
return val, nil
} else if val, ok := config.TriggerMetadata[fmt.Sprintf("%sFromEnv", parameter)]; ok && val != "" {
return config.ResolvedEnv[config.TriggerMetadata[fmt.Sprintf("%sFromEnv", parameter)]], nil
}
return "", fmt.Errorf("error parsing metadata. Details: %s was not found in metadata. Check your ScaledObject configuration", parameter)
}

My proposal is to move this helper function to something like pkg/scalers/helper.go ( not sure about the exact path though but it should be near to scalers. After we get the string value, whatever transformation can be done after that. What do you think about this ?

@zroubalik
Copy link
Member

There are also additional things that needs to be considered.

  • It should be able to distinguish between optional and mandatory fields
  • Some settings have dependencies - eg. if setting1 = foo then setting2 must be set etc. I don't have the specific example here, but I this is needed for some settings a setting around tls/auth

@dttung2905
Copy link
Contributor

Some settings have dependencies - eg. if setting1 = foo then setting2 must be set etc. I don't have the specific example here, but I this is needed for some settings a setting around tls/auth

So for this case we are just going to throw out an error right?
Assuming its throwing out an error, I'm thinking of modifying the function getParameterFromConfig to add in an extra arg isOptional of type bool. For case where dependencies are not met, this field should be set as isOptional=false

// getParameterFromConfig gets the parameter from the configs, if checkAuthParams is true
// then AuthParams is also check for the parameter
 func getParameterFromConfig(config *ScalerConfig, parameter string, checkAuthParams bool, isOptional bool) (string, error) {
	if val, ok := config.AuthParams[parameter]; checkAuthParams && ok && val != "" {
		return val, nil
	} else if val, ok := config.TriggerMetadata[parameter]; ok && val != "" {
		return val, nil
	} else if val, ok := config.TriggerMetadata[fmt.Sprintf("%sFromEnv", parameter)]; ok && val != "" {
		return config.ResolvedEnv[config.TriggerMetadata[fmt.Sprintf("%sFromEnv", parameter)]], nil
	}
        // if its an optional field, we can throw out something here, just put empty string as an arbitrary value for now
	if isOptional {
		return "", nil
	}
	return "", fmt.Errorf("error parsing metadata. Details: %s was not found in metadata. Check your ScaledObject configuration", parameter)

wdyt?

@JorTurFer
Copy link
Member Author

I think that this can be useful and I agree with the approach of having a single function like getParameterFromConfig with some config parameters, but I'd be more explicit, something like:

  • useMetadata
  • useAuthentication
  • useResolvedEnv
  • isOptional
  • default

There are some parameters that we must read only from secure sources 😄
If possible, I'd use generics here to return directly the expected value with the correct type, instead of having to parse outside, so maybe we have to add another parameter for the type

@zroubalik
Copy link
Member

zroubalik commented Jan 16, 2024

Our discussion on Slack:
@wozniakjan :
just fyi, I started looking into this PR (and also the PRs that merged earlier to get more familiar with the context),
I think it's generally on track with the previous development, so I guess the keda maintainers can approve and merge at their convenience.
But I have some additional ideas for potential improvements. I personally find the invocations a little hard to read

certGiven, err := getParameterFromConfigV2(config, "cert", false, true, false, true, "", reflect.TypeOf(""))

I will give it a try to see if I can make it a little more readable, I'm thinking something along the lines of

certGiven, err := getParameterFromConfigV2(config, "cert", USE_AUTHENTICATION | OPTIONAL, "")

I'm thinking we can leverage generics to sugar coat the reflections a bit, I guess we it will be hard to get rid of the reflections entirely but maybe we can at least hide them. And then the false, true, false, true - this might actually be a good candidate for masked binary flags, imho those would improve the code readability

@zroubalik:
Yeah, that’s true. Maybe we can try to investigate a way with options, where we have sane defaults and specific stuff (auth, …) is enabled by options:

func getParameterFromConfigV2(config *ScalerConfig, parameter string, options ...opt.ConfigOption) {}

...
param, err := getParameterFromConfigV2(config, "cert", opt.UseAuth(), opt.Optional(), opt.TypeString())

This is just a draft ^, there might be better names and usage, but I think it is also viable option to think about

@wozniakjan
Copy link
Member

wozniakjan commented Jan 16, 2024

I like the idea of ConfigOptions but I think it's not a blocker for #5319, we can merge that PR and then as a followup tackle just the options separately.

And I took a stab at replacing reflections from the current implementation of getParameterFromConfigV2 in favour of generics in #5388 (mainly just f03313c, other commits are from #5319 so I have an easier life when rebasing once #5319 merges). I kind of like the result, also enjoy the bonus side effect of adding a type constraint to catch bugs from calling getParameterFromConfigV2 with types not supported by convertToType during compile time.

@dttung2905
Copy link
Contributor

Thanks @wozniakjan for giving an attempt on this. Coincidently, I also drafted a go at this as well, using the functional option pattern. #5391 . Please help to take a look too 💪

Copy link

stale bot commented Mar 21, 2024

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale All issues that are marked as stale due to inactivity label Mar 21, 2024
@JorTurFer JorTurFer added the stale-bot-ignore All issues that should not be automatically closed by our stale bot label Mar 24, 2024
Copy link

stale bot commented Apr 1, 2024

This issue has been automatically closed due to inactivity.

@stale stale bot closed this as completed Apr 1, 2024
@github-project-automation github-project-automation bot moved this from In Progress to Ready To Ship in Roadmap - KEDA Core Apr 1, 2024
@JorTurFer JorTurFer reopened this Apr 1, 2024
@github-project-automation github-project-automation bot moved this from Ready To Ship to Proposed in Roadmap - KEDA Core Apr 1, 2024
@JorTurFer JorTurFer removed the stale All issues that are marked as stale due to inactivity label Apr 1, 2024
@wozniakjan
Copy link
Member

wozniakjan commented Apr 10, 2024

@JorTurFer, @dttung2905 I spent some time playing around with a slightly different approach to scalers config parsing and the desired refactor. My efforts are in #5676. I wanted to achieve a similar experience as json.Unmarshal() and I'd be curious to hear some early input from you. The #5676 contains only core logic and single scaler refactor, I chose prometheus scaler by a lucky draw, no higher reasoning.

In high-level terms, this is what I managed to get with an example of some fine-tunning, and I think it might be promising:

type MyConfigXYZ struct {
  Flag    bool           `keda:"name=flag,    parsingOrder=triggerMetadata"`
  Addr    string         `keda:"name=addr,    parsingOrder=triggerMetadata;resolvedEnv, optional"`
  Headers map[string]int `keda:"name=headers, parsingOrder=triggerMetadata,             optional, default=d"`
}

sc := &scalerconfig.ScalerConfig{ ... }
conf := MyConfigXYZ{}
if err := sc.TypedConfig(conf); err != nil {
   ...
}

@wozniakjan
Copy link
Member

given #5676 merged, I will continue with refactor of the individual scalers

$ for f in pkg/scalers/*; do basename "$f"; done | grep '_scaler.go' | sort | uniq | wc -l
59

It looks like there are 59 configs overall, prometheus already taken care of. @dttung2905 given you already worked on kafka, would you like to try that one?

I will try first five alphabetically this week

$ for f in pkg/scalers/*; do basename "$f"; done | grep '_scaler.go' | sort | uniq | head -n 5
activemq_scaler.go
apache_kafka_scaler.go
arangodb_scaler.go
artemis_scaler.go
aws_cloudwatch_scaler.go

@dttung2905
Copy link
Contributor

@wozniakjan I have created a separate issue to track this. I can start on this kafka later today as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request All issues for new features that have not been committed to help wanted Looking for support from community needs-discussion stale-bot-ignore All issues that should not be automatically closed by our stale bot
Projects
Status: Proposed
Development

No branches or pull requests

4 participants