mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
app/vmauth: allow using regexps in url_map
paths
See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1112
This commit is contained in:
parent
5807ff57f3
commit
26cb6f8861
7 changed files with 112 additions and 26 deletions
|
@ -65,13 +65,13 @@ users:
|
||||||
|
|
||||||
|
|
||||||
# A single user for querying and inserting data:
|
# A single user for querying and inserting data:
|
||||||
# - Requests to http://vmauth:8427/api/v1/query or http://vmauth:8427/api/v1/query_range
|
# - Requests to http://vmauth:8427/api/v1/query, http://vmauth:8427/api/v1/query_range
|
||||||
# are routed to http://vmselect:8481/select/42/prometheus.
|
# and http://vmauth:8427/api/v1/label/<label_name>/values are routed to http://vmselect:8481/select/42/prometheus.
|
||||||
# For example, http://vmauth:8427/api/v1/query is routed to http://vmselect:8480/select/42/prometheus/api/v1/query
|
# For example, http://vmauth:8427/api/v1/query is routed to http://vmselect:8480/select/42/prometheus/api/v1/query
|
||||||
# - Requests to http://vmauth:8427/api/v1/write are routed to http://vminsert:8480/insert/42/prometheus/api/v1/write
|
# - Requests to http://vmauth:8427/api/v1/write are routed to http://vminsert:8480/insert/42/prometheus/api/v1/write
|
||||||
- username: "foobar"
|
- username: "foobar"
|
||||||
url_map:
|
url_map:
|
||||||
- src_paths: ["/api/v1/query", "/api/v1/query_range"]
|
- src_paths: ["/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^/]+/values"]
|
||||||
url_prefix: "http://vmselect:8481/select/42/prometheus"
|
url_prefix: "http://vmselect:8481/select/42/prometheus"
|
||||||
- src_paths: ["/api/v1/write"]
|
- src_paths: ["/api/v1/write"]
|
||||||
url_prefix: "http://vminsert:8480/insert/42/prometheus"
|
url_prefix: "http://vminsert:8480/insert/42/prometheus"
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
@ -38,10 +39,49 @@ type UserInfo struct {
|
||||||
|
|
||||||
// URLMap is a mapping from source paths to target urls.
|
// URLMap is a mapping from source paths to target urls.
|
||||||
type URLMap struct {
|
type URLMap struct {
|
||||||
SrcPaths []string `yaml:"src_paths"`
|
SrcPaths []*SrcPath `yaml:"src_paths"`
|
||||||
URLPrefix string `yaml:"url_prefix"`
|
URLPrefix string `yaml:"url_prefix"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SrcPath represents an src path
|
||||||
|
type SrcPath struct {
|
||||||
|
sOriginal string
|
||||||
|
re *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SrcPath) match(s string) bool {
|
||||||
|
prefix, ok := sp.re.LiteralPrefix()
|
||||||
|
if ok {
|
||||||
|
// Fast path - literal match
|
||||||
|
return s == prefix
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(s, prefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return sp.re.MatchString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements yaml.Unmarshaler
|
||||||
|
func (sp *SrcPath) UnmarshalYAML(f func(interface{}) error) error {
|
||||||
|
var s string
|
||||||
|
if err := f(&s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sAnchored := "^(?:" + s + ")$"
|
||||||
|
re, err := regexp.Compile(sAnchored)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot build regexp from %q: %w", s, err)
|
||||||
|
}
|
||||||
|
sp.sOriginal = s
|
||||||
|
sp.re = re
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalYAML implements yaml.Marshaler.
|
||||||
|
func (sp *SrcPath) MarshalYAML() (interface{}, error) {
|
||||||
|
return sp.sOriginal, nil
|
||||||
|
}
|
||||||
|
|
||||||
func initAuthConfig() {
|
func initAuthConfig() {
|
||||||
if len(*authConfigPath) == 0 {
|
if len(*authConfigPath) == 0 {
|
||||||
logger.Fatalf("missing required `-auth.config` command-line flag")
|
logger.Fatalf("missing required `-auth.config` command-line flag")
|
||||||
|
@ -127,11 +167,6 @@ func parseAuthConfig(data []byte) (map[string]*UserInfo, error) {
|
||||||
if len(e.SrcPaths) == 0 {
|
if len(e.SrcPaths) == 0 {
|
||||||
return nil, fmt.Errorf("missing `src_paths`")
|
return nil, fmt.Errorf("missing `src_paths`")
|
||||||
}
|
}
|
||||||
for _, path := range e.SrcPaths {
|
|
||||||
if !strings.HasPrefix(path, "/") {
|
|
||||||
return nil, fmt.Errorf("`src_path`=%q must start with `/`", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
urlPrefix, err := sanitizeURLPrefix(e.URLPrefix)
|
urlPrefix, err := sanitizeURLPrefix(e.URLPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseAuthConfigFailure(t *testing.T) {
|
func TestParseAuthConfigFailure(t *testing.T) {
|
||||||
|
@ -79,12 +83,12 @@ users:
|
||||||
- url_prefix: http://foobar
|
- url_prefix: http://foobar
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// src_path not starting with `/`
|
// Invalid regexp in src_path.
|
||||||
f(`
|
f(`
|
||||||
users:
|
users:
|
||||||
- username: a
|
- username: a
|
||||||
url_map:
|
url_map:
|
||||||
- src_paths: [foobar]
|
- src_paths: ['fo[obar']
|
||||||
url_prefix: http://foobar
|
url_prefix: http://foobar
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
@ -97,8 +101,8 @@ func TestParseAuthConfigSuccess(t *testing.T) {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
removeMetrics(m)
|
removeMetrics(m)
|
||||||
if !reflect.DeepEqual(m, expectedAuthConfig) {
|
if err := areEqualConfigs(m, expectedAuthConfig); err != nil {
|
||||||
t.Fatalf("unexpected auth config\ngot\n%v\nwant\n%v", m, expectedAuthConfig)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +143,7 @@ users:
|
||||||
users:
|
users:
|
||||||
- username: foo
|
- username: foo
|
||||||
url_map:
|
url_map:
|
||||||
- src_paths: ["/api/v1/query","/api/v1/query_range"]
|
- src_paths: ["/api/v1/query","/api/v1/query_range","/api/v1/label/[^./]+/.+"]
|
||||||
url_prefix: http://vmselect/select/0/prometheus
|
url_prefix: http://vmselect/select/0/prometheus
|
||||||
- src_paths: ["/api/v1/write"]
|
- src_paths: ["/api/v1/write"]
|
||||||
url_prefix: http://vminsert/insert/0/prometheus
|
url_prefix: http://vminsert/insert/0/prometheus
|
||||||
|
@ -148,11 +152,11 @@ users:
|
||||||
Username: "foo",
|
Username: "foo",
|
||||||
URLMap: []URLMap{
|
URLMap: []URLMap{
|
||||||
{
|
{
|
||||||
SrcPaths: []string{"/api/v1/query", "/api/v1/query_range"},
|
SrcPaths: getSrcPaths([]string{"/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^./]+/.+"}),
|
||||||
URLPrefix: "http://vmselect/select/0/prometheus",
|
URLPrefix: "http://vmselect/select/0/prometheus",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SrcPaths: []string{"/api/v1/write"},
|
SrcPaths: getSrcPaths([]string{"/api/v1/write"}),
|
||||||
URLPrefix: "http://vminsert/insert/0/prometheus",
|
URLPrefix: "http://vminsert/insert/0/prometheus",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -160,8 +164,34 @@ users:
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSrcPaths(paths []string) []*SrcPath {
|
||||||
|
var sps []*SrcPath
|
||||||
|
for _, path := range paths {
|
||||||
|
sps = append(sps, &SrcPath{
|
||||||
|
sOriginal: path,
|
||||||
|
re: regexp.MustCompile("^(?:" + path + ")$"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return sps
|
||||||
|
}
|
||||||
|
|
||||||
func removeMetrics(m map[string]*UserInfo) {
|
func removeMetrics(m map[string]*UserInfo) {
|
||||||
for _, info := range m {
|
for _, info := range m {
|
||||||
info.requests = nil
|
info.requests = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func areEqualConfigs(a, b map[string]*UserInfo) error {
|
||||||
|
aData, err := yaml.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot marshal a: %w", err)
|
||||||
|
}
|
||||||
|
bData, err := yaml.Marshal(b)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot marshal b: %w", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(aData, bData) {
|
||||||
|
return fmt.Errorf("unexpected configs;\ngot\n%s\nwant\n%s", aData, bData)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ func createTargetURL(ui *UserInfo, uOrig *url.URL) (string, error) {
|
||||||
u.Path = "/" + u.Path
|
u.Path = "/" + u.Path
|
||||||
}
|
}
|
||||||
for _, e := range ui.URLMap {
|
for _, e := range ui.URLMap {
|
||||||
for _, path := range e.SrcPaths {
|
for _, sp := range e.SrcPaths {
|
||||||
if u.Path == path {
|
if sp.match(u.Path) {
|
||||||
return e.URLPrefix + u.RequestURI(), nil
|
return e.URLPrefix + u.RequestURI(), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,11 +44,11 @@ func TestCreateTargetURLSuccess(t *testing.T) {
|
||||||
ui := &UserInfo{
|
ui := &UserInfo{
|
||||||
URLMap: []URLMap{
|
URLMap: []URLMap{
|
||||||
{
|
{
|
||||||
SrcPaths: []string{"/api/v1/query"},
|
SrcPaths: getSrcPaths([]string{"/api/v1/query"}),
|
||||||
URLPrefix: "http://vmselect/0/prometheus",
|
URLPrefix: "http://vmselect/0/prometheus",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SrcPaths: []string{"/api/v1/write"},
|
SrcPaths: getSrcPaths([]string{"/api/v1/write"}),
|
||||||
URLPrefix: "http://vminsert/0/prometheus",
|
URLPrefix: "http://vminsert/0/prometheus",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -57,6 +57,26 @@ func TestCreateTargetURLSuccess(t *testing.T) {
|
||||||
f(ui, "/api/v1/query?query=up", "http://vmselect/0/prometheus/api/v1/query?query=up")
|
f(ui, "/api/v1/query?query=up", "http://vmselect/0/prometheus/api/v1/query?query=up")
|
||||||
f(ui, "/api/v1/write", "http://vminsert/0/prometheus/api/v1/write")
|
f(ui, "/api/v1/write", "http://vminsert/0/prometheus/api/v1/write")
|
||||||
f(ui, "/api/v1/query_range", "http://default-server/api/v1/query_range")
|
f(ui, "/api/v1/query_range", "http://default-server/api/v1/query_range")
|
||||||
|
|
||||||
|
// Complex routing regexp paths in `url_map`
|
||||||
|
ui = &UserInfo{
|
||||||
|
URLMap: []URLMap{
|
||||||
|
{
|
||||||
|
SrcPaths: getSrcPaths([]string{"/api/v1/query(_range)?", "/api/v1/label/[^/]+/values"}),
|
||||||
|
URLPrefix: "http://vmselect/0/prometheus",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SrcPaths: getSrcPaths([]string{"/api/v1/write"}),
|
||||||
|
URLPrefix: "http://vminsert/0/prometheus",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
URLPrefix: "http://default-server",
|
||||||
|
}
|
||||||
|
f(ui, "/api/v1/query?query=up", "http://vmselect/0/prometheus/api/v1/query?query=up")
|
||||||
|
f(ui, "/api/v1/query_range?query=up", "http://vmselect/0/prometheus/api/v1/query_range?query=up")
|
||||||
|
f(ui, "/api/v1/label/foo/values", "http://vmselect/0/prometheus/api/v1/label/foo/values")
|
||||||
|
f(ui, "/api/v1/write", "http://vminsert/0/prometheus/api/v1/write")
|
||||||
|
f(ui, "/api/v1/foo/bar", "http://default-server/api/v1/foo/bar")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateTargetURLFailure(t *testing.T) {
|
func TestCreateTargetURLFailure(t *testing.T) {
|
||||||
|
@ -78,7 +98,7 @@ func TestCreateTargetURLFailure(t *testing.T) {
|
||||||
f(&UserInfo{
|
f(&UserInfo{
|
||||||
URLMap: []URLMap{
|
URLMap: []URLMap{
|
||||||
{
|
{
|
||||||
SrcPaths: []string{"/api/v1/query"},
|
SrcPaths: getSrcPaths([]string{"/api/v1/query"}),
|
||||||
URLPrefix: "http://foobar/baz",
|
URLPrefix: "http://foobar/baz",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
- `histogram_stdvar(buckets)` - returns standard variance for the given buckets.
|
- `histogram_stdvar(buckets)` - returns standard variance for the given buckets.
|
||||||
- `histogram_stddev(buckets)` - returns standard deviation for the given buckets.
|
- `histogram_stddev(buckets)` - returns standard deviation for the given buckets.
|
||||||
* FEATURE: vmagent: add ability to replicate scrape targets among `vmagent` instances in the cluster with `-promscrape.cluster.replicationFactor` command-line flag. See [these docs](https://victoriametrics.github.io/vmagent.html#scraping-big-number-of-targets).
|
* FEATURE: vmagent: add ability to replicate scrape targets among `vmagent` instances in the cluster with `-promscrape.cluster.replicationFactor` command-line flag. See [these docs](https://victoriametrics.github.io/vmagent.html#scraping-big-number-of-targets).
|
||||||
|
* FEATURE: vmauth: allow using regexp paths in `url_map`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1112) for details.
|
||||||
|
|
||||||
* BUGFIX: vmagent: reduce memory usage when Kubernetes service discovery is used in big number of distinct jobs by sharing the cache. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1113
|
* BUGFIX: vmagent: reduce memory usage when Kubernetes service discovery is used in big number of distinct jobs by sharing the cache. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1113
|
||||||
|
|
||||||
|
|
|
@ -65,13 +65,13 @@ users:
|
||||||
|
|
||||||
|
|
||||||
# A single user for querying and inserting data:
|
# A single user for querying and inserting data:
|
||||||
# - Requests to http://vmauth:8427/api/v1/query or http://vmauth:8427/api/v1/query_range
|
# - Requests to http://vmauth:8427/api/v1/query, http://vmauth:8427/api/v1/query_range
|
||||||
# are routed to http://vmselect:8481/select/42/prometheus.
|
# and http://vmauth:8427/api/v1/label/<label_name>/values are routed to http://vmselect:8481/select/42/prometheus.
|
||||||
# For example, http://vmauth:8427/api/v1/query is routed to http://vmselect:8480/select/42/prometheus/api/v1/query
|
# For example, http://vmauth:8427/api/v1/query is routed to http://vmselect:8480/select/42/prometheus/api/v1/query
|
||||||
# - Requests to http://vmauth:8427/api/v1/write are routed to http://vminsert:8480/insert/42/prometheus/api/v1/write
|
# - Requests to http://vmauth:8427/api/v1/write are routed to http://vminsert:8480/insert/42/prometheus/api/v1/write
|
||||||
- username: "foobar"
|
- username: "foobar"
|
||||||
url_map:
|
url_map:
|
||||||
- src_paths: ["/api/v1/query", "/api/v1/query_range"]
|
- src_paths: ["/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^/]+/values"]
|
||||||
url_prefix: "http://vmselect:8481/select/42/prometheus"
|
url_prefix: "http://vmselect:8481/select/42/prometheus"
|
||||||
- src_paths: ["/api/v1/write"]
|
- src_paths: ["/api/v1/write"]
|
||||||
url_prefix: "http://vminsert:8480/insert/42/prometheus"
|
url_prefix: "http://vminsert:8480/insert/42/prometheus"
|
||||||
|
|
Loading…
Reference in a new issue