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

feat: enable multiple label values #115

Merged

Conversation

Kirchen99
Copy link
Contributor

Signed-off-by: Kirchen99 latias_latios@126.com

Solves the issue: #27

Example:

Run prom-label-proxy with following args:

                "--insecure-listen-address=127.0.0.1:8080",
                "--upstream=http://demo.do.prometheus.io:9090",
                "--label=job"

Try to get something with curl:

curl http://127.0.0.1:8080/api/v1/query\?query\="up"\&job\="grafana"\&job\="random"\&job\="caddy"

See the result:

{
   "status":"success",
   "data":{
      "resultType":"vector",
      "result":[
         {
            "metric":{
               "__name__":"up",
               "instance":"demo.do.prometheus.io:3000",
               "job":"grafana"
            },
            "value":[
               1655460956.224,
               "1"
            ]
         },
         {
            "metric":{
               "__name__":"up",
               "instance":"demo.do.prometheus.io:8996",
               "job":"random"
            },
            "value":[
               1655460956.224,
               "1"
            ]
         },
         {
            "metric":{
               "__name__":"up",
               "instance":"demo.do.prometheus.io:8997",
               "job":"random"
            },
            "value":[
               1655460956.224,
               "1"
            ]
         },
         {
            "metric":{
               "__name__":"up",
               "instance":"demo.do.prometheus.io:8998",
               "job":"random"
            },
            "value":[
               1655460956.224,
               "1"
            ]
         },
         {
            "metric":{
               "__name__":"up",
               "instance":"demo.do.prometheus.io:8999",
               "job":"random"
            },
            "value":[
               1655460956.224,
               "1"
            ]
         },
         {
            "metric":{
               "__name__":"up",
               "instance":"localhost:2019",
               "job":"caddy"
            },
            "value":[
               1655460956.224,
               "1"
            ]
         }
      ]
   }
}

injectproxy/routes.go Outdated Show resolved Hide resolved
Copy link
Contributor

@simonpasquier simonpasquier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you'd need a rebase :)

.gitignore Outdated Show resolved Hide resolved
injectproxy/routes.go Outdated Show resolved Hide resolved
injectproxy/routes.go Show resolved Hide resolved
injectproxy/routes.go Outdated Show resolved Hide resolved
injectproxy/routes.go Outdated Show resolved Hide resolved
if r.labelValue != "" && formValue != "" {
err := req.ParseForm()
if err != nil {
return "", fmt.Errorf("the query parameter can not be parsed. %s", err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return "", fmt.Errorf("the query parameter can not be parsed. %s", err.Error())
return "", fmt.Errorf("the query parameter can not be parsed: %w", err)

injectproxy/routes.go Outdated Show resolved Hide resolved
injectproxy/routes.go Outdated Show resolved Hide resolved
main.go Outdated Show resolved Hide resolved
injectproxy/routes_test.go Outdated Show resolved Hide resolved
Copy link
Contributor

@simonpasquier simonpasquier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize that there's more work needed: while the PR works the /api/v1/query and /api/v1/query_range endpoints, it would break other endpoints such as /federate, /api/v1/rules, /api/v2/silences, ...
I think that enforceLabel() needs to store the slice of label values in the context. Then each downstream handler does whatever it needs with it.

injectproxy/routes.go Outdated Show resolved Hide resolved
injectproxy/routes.go Outdated Show resolved Hide resolved
injectproxy/routes.go Outdated Show resolved Hide resolved
injectproxy/routes.go Outdated Show resolved Hide resolved
Copy link
Contributor

@simonpasquier simonpasquier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the effort! It's definitely more involved than initially envisioned 😅
I'm still not decided on the best way to deal with the silences endpoint, i'll need to think about it 🤔

injectproxy/silences.go Outdated Show resolved Hide resolved
injectproxy/silences.go Outdated Show resolved Hide resolved
injectproxy/silences.go Outdated Show resolved Hide resolved
injectproxy/routes.go Outdated Show resolved Hide resolved
injectproxy/silences.go Outdated Show resolved Hide resolved
injectproxy/silences.go Outdated Show resolved Hide resolved
modified := models.Matchers{
&models.Matcher{Name: &(r.label), Value: &lvalue, IsRegex: &falsy},
&models.Matcher{Name: &(r.label), Value: &matcherValue, IsRegex: &truthy},
}
for _, m := range sil.Matchers {
if m.Name != nil && *m.Name == r.label {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the enforced label is namespace=~"ns1|ns2" and someone creates a silence with namespace="ns1" then namespace="ns1" will be dropped which is not the desired outcome because you may want to narrow down the silence.

I think that we need to keep all input matchers and append the enforced matcher unconditionally? It would also simplify the delete endpoint since the only check would be to verify that the deleted silence contains a namespace=~"ns1|ns2" matcher (given example above).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the code.

If the enforced label is namespace=~"ns1", and someone creates a silence with namespace="ns1", then both matchers will be added:

[
    "namespace=~\"ns1\"",
    "namespace=\"ns1\""
]

For List, Update and Delete, only silences exactly contain a namespace=~"ns1" will be processed.

Did I understand you correctly?

injectproxy/routes.go Outdated Show resolved Hide resolved
injectproxy/routes.go Outdated Show resolved Hide resolved
injectproxy/rules.go Outdated Show resolved Hide resolved
@Kirchen99
Copy link
Contributor Author

I resolved some conversations to make this thread(pull request) easier to read and follow. Please tell me if I'd better not to resolve them. @simonpasquier

@gasmick
Copy link

gasmick commented Sep 29, 2022

Wow, it would be great to have this merged soon!

@bt909
Copy link

bt909 commented Oct 4, 2022

Nice useful feature, is there any plan to merge this soon?

@kekscode
Copy link

kekscode commented Oct 6, 2022

@simonpasquier can we expect that this gets merged anytime soon or are there any open issues you see?

@lud97x
Copy link

lud97x commented Oct 17, 2022

+1

@dbluxo
Copy link

dbluxo commented Nov 9, 2022

@simonpasquier doesn't seem to have had time to review this PR further. Would maybe someone else from the maintainers have time to take a look here? @squat @brancz

@simonpasquier
Copy link
Contributor

hey! Sorry for the lag on this PR. I'm putting it at the top of my review list as I'm aware that there's high demand.

@simonpasquier
Copy link
Contributor

@Kirchen99 now that #118 is merged, you'd have to rebase and resolve conflicts. Sorry about that but once it's done, I'll review the PR :)

Kirchen99 and others added 13 commits June 12, 2023 15:10
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <latias_latios@126.com>
Signed-off-by: Kirchen99 <9981745+Kirchen99@users.noreply.github.com>
@simonpasquier
Copy link
Contributor

👋 I'm taking a look at it right now. Since the silences API is problematic with multiple label values, I'd be in favor of declaring that the proxy doesn't support silences in this mode of operations. The main use case of multi-labels is for the Prometheus API anyway so not supporting the Alertmanager API isn't a blocker IMHO.

I'm going to push some commits on top of your PR to implement this.

Signed-off-by: Simon Pasquier <spasquie@redhat.com>
Signed-off-by: Simon Pasquier <spasquie@redhat.com>
When the original filter query parameter already includes the enforced
label, the proxy should preserve it as it indicates that the user wants
to filter down the list of silences/alerts.

Signed-off-by: Simon Pasquier <spasquie@redhat.com>
When prom-label-proxy is configured with multiple label values, it
should preserve the existing matchers because users actually want to
return results for a subset of the metrics. In practice when configured
with `namespace=~"bar|foo"`, the query `up{namespace="foo"}` is
translated into `up{namespace="foo",namespace=~"bar|foo"}` instead of
`{namespace=~"bar|foo"}`.

This change is similar to 26b0a61 but for the Prometheus API endpoints.

Signed-off-by: Simon Pasquier <spasquie@redhat.com>
Signed-off-by: Simon Pasquier <spasquie@redhat.com>
@simonpasquier
Copy link
Contributor

@Kirchen99 I've added a couple of commits on top of your work. The main changes are

  • Any call to the Alertmanager Silences endpoints with multi label values configured returns 422 Unprocessable Entity (https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422).
  • When multi label values are configured, the proxy won't replace the existing matchers. E.g. when you query up{namespace="bar"}, the proxy should translate to up{namespace="bar",namespace=~"bar|foo"} and not up{namespace=~"bar|foo"}.
  • The same applies to the Alertmanager's Alerts and Alert groups API.

Regarding the last 2 points, I don't remember if we already discussed it in this PR but IMHO this is what makes most sense compared to dropping the existing matcher unilaterally.

My changes are https://github.com/prometheus-community/prom-label-proxy/pull/115/files/8472eba307611d1f2cf9181e70e3319ecbb90976..4882a18523e612324514b1f5ff8c7f8c465e71cc. Happy to hear your feedback.

@Kirchen99
Copy link
Contributor Author

@simonpasquier Thank you for your commits and the detailed explanation of the changes. I like your Idea on the last 2 points and really appreciate your work and the improvements you've made. The changes look good to me, and I'm excited to see them being merged.

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
Copy link
Contributor

@simonpasquier simonpasquier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll give a few days for @squat and @SuperQ to review the change (it's quite large). If no reply, I'll merge it by the end of the week probably and cut a new release.

@simonpasquier simonpasquier merged commit e4829ba into prometheus-community:main Jun 15, 2023
@simonpasquier
Copy link
Contributor

Thanks again @Kirchen99 for your tenacity 🎉 and sorry it took so long to review...
I'll cut a new release either today or tomorrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.