2020-05-07 09:36:32 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2021-10-22 16:08:06 +00:00
|
|
|
"fmt"
|
2020-05-07 09:36:32 +00:00
|
|
|
"net/url"
|
2023-09-08 20:39:17 +00:00
|
|
|
"reflect"
|
2024-03-06 23:02:13 +00:00
|
|
|
"strings"
|
2020-05-07 09:36:32 +00:00
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
2023-11-13 21:30:39 +00:00
|
|
|
func TestDropPrefixParts(t *testing.T) {
|
|
|
|
f := func(path string, parts int, expectedResult string) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
result := dropPrefixParts(path, parts)
|
|
|
|
if result != expectedResult {
|
|
|
|
t.Fatalf("unexpected result; got %q; want %q", result, expectedResult)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f("", 0, "")
|
|
|
|
f("", 1, "")
|
|
|
|
f("", 10, "")
|
|
|
|
f("foo", 0, "foo")
|
|
|
|
f("foo", -1, "foo")
|
|
|
|
f("foo", 1, "")
|
|
|
|
|
|
|
|
f("/foo", 0, "/foo")
|
|
|
|
f("/foo/bar", 0, "/foo/bar")
|
|
|
|
f("/foo/bar/baz", 0, "/foo/bar/baz")
|
|
|
|
|
|
|
|
f("foo", 0, "foo")
|
|
|
|
f("foo/bar", 0, "foo/bar")
|
|
|
|
f("foo/bar/baz", 0, "foo/bar/baz")
|
|
|
|
|
|
|
|
f("/foo/", 0, "/foo/")
|
|
|
|
f("/foo/bar/", 0, "/foo/bar/")
|
|
|
|
f("/foo/bar/baz/", 0, "/foo/bar/baz/")
|
|
|
|
|
|
|
|
f("/foo", 1, "")
|
|
|
|
f("/foo/bar", 1, "/bar")
|
|
|
|
f("/foo/bar/baz", 1, "/bar/baz")
|
|
|
|
|
|
|
|
f("foo", 1, "")
|
|
|
|
f("foo/bar", 1, "/bar")
|
|
|
|
f("foo/bar/baz", 1, "/bar/baz")
|
|
|
|
|
|
|
|
f("/foo/", 1, "/")
|
|
|
|
f("/foo/bar/", 1, "/bar/")
|
|
|
|
f("/foo/bar/baz/", 1, "/bar/baz/")
|
|
|
|
|
|
|
|
f("/foo", 2, "")
|
|
|
|
f("/foo/bar", 2, "")
|
|
|
|
f("/foo/bar/baz", 2, "/baz")
|
|
|
|
|
|
|
|
f("foo", 2, "")
|
|
|
|
f("foo/bar", 2, "")
|
|
|
|
f("foo/bar/baz", 2, "/baz")
|
|
|
|
|
|
|
|
f("/foo/", 2, "")
|
|
|
|
f("/foo/bar/", 2, "/")
|
|
|
|
f("/foo/bar/baz/", 2, "/baz/")
|
|
|
|
|
|
|
|
f("/foo", 3, "")
|
|
|
|
f("/foo/bar", 3, "")
|
|
|
|
f("/foo/bar/baz", 3, "")
|
|
|
|
|
|
|
|
f("foo", 3, "")
|
|
|
|
f("foo/bar", 3, "")
|
|
|
|
f("foo/bar/baz", 3, "")
|
|
|
|
|
|
|
|
f("/foo/", 3, "")
|
|
|
|
f("/foo/bar/", 3, "")
|
|
|
|
f("/foo/bar/baz/", 3, "/")
|
|
|
|
|
|
|
|
f("/foo/", 4, "")
|
|
|
|
f("/foo/bar/", 4, "")
|
|
|
|
f("/foo/bar/baz/", 4, "")
|
|
|
|
}
|
|
|
|
|
2021-02-11 10:40:59 +00:00
|
|
|
func TestCreateTargetURLSuccess(t *testing.T) {
|
2023-11-13 21:30:39 +00:00
|
|
|
f := func(ui *UserInfo, requestURI, expectedTarget, expectedRequestHeaders, expectedResponseHeaders string,
|
2023-12-08 21:27:53 +00:00
|
|
|
expectedRetryStatusCodes []int, expectedLoadBalancingPolicy string, expectedDropSrcPathPrefixParts int) {
|
2020-05-07 09:36:32 +00:00
|
|
|
t.Helper()
|
2023-12-08 21:27:53 +00:00
|
|
|
if err := ui.initURLs(); err != nil {
|
|
|
|
t.Fatalf("cannot initialize urls inside UserInfo: %s", err)
|
|
|
|
}
|
2020-05-07 09:36:32 +00:00
|
|
|
u, err := url.Parse(requestURI)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("cannot parse %q: %s", requestURI, err)
|
|
|
|
}
|
2023-02-10 05:05:13 +00:00
|
|
|
u = normalizeURL(u)
|
2024-03-06 19:56:32 +00:00
|
|
|
up, hc := ui.getURLPrefixAndHeaders(u, nil)
|
2023-04-26 09:04:35 +00:00
|
|
|
if up == nil {
|
|
|
|
t.Fatalf("cannot determie backend: %s", err)
|
2021-02-11 10:40:59 +00:00
|
|
|
}
|
2024-03-06 23:02:13 +00:00
|
|
|
bu := up.getBackendURL()
|
2023-12-13 23:04:46 +00:00
|
|
|
target := mergeURLs(bu.url, u, up.dropSrcPathPrefixParts)
|
2023-02-11 08:27:40 +00:00
|
|
|
bu.put()
|
2024-04-17 07:54:43 +00:00
|
|
|
|
2024-04-17 12:03:15 +00:00
|
|
|
gotTarget := target.String()
|
2024-04-17 07:54:43 +00:00
|
|
|
if gotTarget != expectedTarget {
|
|
|
|
t.Fatalf("unexpected target; \ngot:\n%q;\nwant:\n%q", gotTarget, expectedTarget)
|
2020-05-07 09:36:32 +00:00
|
|
|
}
|
2024-03-06 23:02:13 +00:00
|
|
|
if s := headersToString(hc.RequestHeaders); s != expectedRequestHeaders {
|
|
|
|
t.Fatalf("unexpected request headers; got %q; want %q", s, expectedRequestHeaders)
|
|
|
|
}
|
|
|
|
if s := headersToString(hc.ResponseHeaders); s != expectedResponseHeaders {
|
|
|
|
t.Fatalf("unexpected response headers; got %q; want %q", s, expectedResponseHeaders)
|
2023-09-08 20:39:17 +00:00
|
|
|
}
|
2023-12-08 21:27:53 +00:00
|
|
|
if !reflect.DeepEqual(up.retryStatusCodes, expectedRetryStatusCodes) {
|
|
|
|
t.Fatalf("unexpected retryStatusCodes; got %d; want %d", up.retryStatusCodes, expectedRetryStatusCodes)
|
|
|
|
}
|
|
|
|
if up.loadBalancingPolicy != expectedLoadBalancingPolicy {
|
|
|
|
t.Fatalf("unexpected loadBalancingPolicy; got %q; want %q", up.loadBalancingPolicy, expectedLoadBalancingPolicy)
|
2021-10-22 16:08:06 +00:00
|
|
|
}
|
2023-12-13 23:04:46 +00:00
|
|
|
if up.dropSrcPathPrefixParts != expectedDropSrcPathPrefixParts {
|
|
|
|
t.Fatalf("unexpected dropSrcPathPrefixParts; got %d; want %d", up.dropSrcPathPrefixParts, expectedDropSrcPathPrefixParts)
|
2023-11-13 21:30:39 +00:00
|
|
|
}
|
2020-05-07 09:36:32 +00:00
|
|
|
}
|
2021-02-11 10:40:59 +00:00
|
|
|
// Simple routing with `url_prefix`
|
|
|
|
f(&UserInfo{
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("http://foo.bar"),
|
2024-03-06 23:02:13 +00:00
|
|
|
}, "", "http://foo.bar/.", "", "", nil, "least_loaded", 0)
|
2021-02-11 10:40:59 +00:00
|
|
|
f(&UserInfo{
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("http://foo.bar"),
|
2023-09-01 07:21:10 +00:00
|
|
|
HeadersConf: HeadersConf{
|
2024-04-17 12:03:15 +00:00
|
|
|
RequestHeaders: []*Header{
|
|
|
|
mustNewHeader("'bb: aaa'"),
|
2024-03-06 23:02:13 +00:00
|
|
|
},
|
2024-04-17 12:03:15 +00:00
|
|
|
ResponseHeaders: []*Header{
|
|
|
|
mustNewHeader("'x: y'"),
|
2024-03-06 23:02:13 +00:00
|
|
|
},
|
2023-09-01 07:21:10 +00:00
|
|
|
},
|
2023-11-13 21:30:39 +00:00
|
|
|
RetryStatusCodes: []int{503, 501},
|
2023-12-08 21:27:53 +00:00
|
|
|
LoadBalancingPolicy: "first_available",
|
2023-12-13 23:04:46 +00:00
|
|
|
DropSrcPathPrefixParts: intp(2),
|
2024-03-06 23:02:13 +00:00
|
|
|
}, "/a/b/c", "http://foo.bar/c", `bb: aaa`, `x: y`, []int{503, 501}, "first_available", 2)
|
2021-08-25 10:28:50 +00:00
|
|
|
f(&UserInfo{
|
|
|
|
URLPrefix: mustParseURL("http://foo.bar/federate"),
|
2024-03-06 23:02:13 +00:00
|
|
|
}, "/", "http://foo.bar/federate", "", "", nil, "least_loaded", 0)
|
2021-02-11 10:40:59 +00:00
|
|
|
f(&UserInfo{
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("http://foo.bar"),
|
2024-03-06 23:02:13 +00:00
|
|
|
}, "a/b?c=d", "http://foo.bar/a/b?c=d", "", "", nil, "least_loaded", 0)
|
2021-02-11 10:40:59 +00:00
|
|
|
f(&UserInfo{
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("https://sss:3894/x/y"),
|
2024-03-06 23:02:13 +00:00
|
|
|
}, "/z", "https://sss:3894/x/y/z", "", "", nil, "least_loaded", 0)
|
2021-02-11 10:40:59 +00:00
|
|
|
f(&UserInfo{
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("https://sss:3894/x/y"),
|
2024-03-06 23:02:13 +00:00
|
|
|
}, "/../../aaa", "https://sss:3894/x/y/aaa", "", "", nil, "least_loaded", 0)
|
2021-02-11 10:40:59 +00:00
|
|
|
f(&UserInfo{
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("https://sss:3894/x/y"),
|
2024-04-17 12:03:15 +00:00
|
|
|
}, "/./asd/../../aaa?a=d&s=s/../d", "https://sss:3894/x/y/aaa?a=d&s=s%2F..%2Fd", "", "", nil, "least_loaded", 0)
|
2021-02-11 10:40:59 +00:00
|
|
|
|
|
|
|
// Complex routing with `url_map`
|
|
|
|
ui := &UserInfo{
|
2023-01-27 08:18:58 +00:00
|
|
|
URLMaps: []URLMap{
|
2021-02-11 10:40:59 +00:00
|
|
|
{
|
2024-03-06 18:52:23 +00:00
|
|
|
SrcHosts: getRegexs([]string{"host42"}),
|
|
|
|
SrcPaths: getRegexs([]string{"/vmsingle/api/v1/query"}),
|
2024-04-17 12:03:15 +00:00
|
|
|
SrcQueryArgs: []*QueryArg{
|
|
|
|
mustNewQueryArg("db=foo"),
|
2024-03-06 18:52:23 +00:00
|
|
|
},
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("http://vmselect/0/prometheus"),
|
2023-09-01 07:21:10 +00:00
|
|
|
HeadersConf: HeadersConf{
|
2024-04-17 12:03:15 +00:00
|
|
|
RequestHeaders: []*Header{
|
|
|
|
mustNewHeader("'xx: aa'"),
|
|
|
|
mustNewHeader("'yy: asdf'"),
|
2021-10-22 16:08:06 +00:00
|
|
|
},
|
2024-04-17 12:03:15 +00:00
|
|
|
ResponseHeaders: []*Header{
|
|
|
|
mustNewHeader("'qwe: rty'"),
|
2021-10-22 16:08:06 +00:00
|
|
|
},
|
2023-09-01 07:21:10 +00:00
|
|
|
},
|
2023-11-13 21:30:39 +00:00
|
|
|
RetryStatusCodes: []int{503, 500, 501},
|
2023-12-08 21:27:53 +00:00
|
|
|
LoadBalancingPolicy: "first_available",
|
2023-12-13 23:04:46 +00:00
|
|
|
DropSrcPathPrefixParts: intp(1),
|
2021-02-11 10:40:59 +00:00
|
|
|
},
|
|
|
|
{
|
2023-12-13 23:04:46 +00:00
|
|
|
SrcPaths: getRegexs([]string{"/api/v1/write"}),
|
|
|
|
URLPrefix: mustParseURL("http://vminsert/0/prometheus"),
|
|
|
|
RetryStatusCodes: []int{},
|
|
|
|
DropSrcPathPrefixParts: intp(0),
|
2021-02-11 10:40:59 +00:00
|
|
|
},
|
|
|
|
},
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("http://default-server"),
|
2023-09-01 07:21:10 +00:00
|
|
|
HeadersConf: HeadersConf{
|
2024-04-17 12:03:15 +00:00
|
|
|
RequestHeaders: []*Header{
|
|
|
|
mustNewHeader("'bb: aaa'"),
|
|
|
|
},
|
|
|
|
ResponseHeaders: []*Header{
|
|
|
|
mustNewHeader("'x: y'"),
|
|
|
|
},
|
2023-09-01 07:21:10 +00:00
|
|
|
},
|
2023-11-13 21:30:39 +00:00
|
|
|
RetryStatusCodes: []int{502},
|
2023-12-13 23:04:46 +00:00
|
|
|
DropSrcPathPrefixParts: intp(2),
|
2021-02-11 10:40:59 +00:00
|
|
|
}
|
2024-03-06 18:52:23 +00:00
|
|
|
f(ui, "http://host42/vmsingle/api/v1/query?query=up&db=foo", "http://vmselect/0/prometheus/api/v1/query?db=foo&query=up",
|
2024-03-06 23:02:13 +00:00
|
|
|
"xx: aa\nyy: asdf", "qwe: rty", []int{503, 500, 501}, "first_available", 1)
|
2023-12-13 22:46:36 +00:00
|
|
|
f(ui, "http://host123/vmsingle/api/v1/query?query=up", "http://default-server/v1/query?query=up",
|
2024-03-06 23:02:13 +00:00
|
|
|
"bb: aaa", "x: y", []int{502}, "least_loaded", 2)
|
|
|
|
f(ui, "https://foo-host/api/v1/write", "http://vminsert/0/prometheus/api/v1/write", "", "", []int{}, "least_loaded", 0)
|
|
|
|
f(ui, "https://foo-host/foo/bar/api/v1/query_range", "http://default-server/api/v1/query_range", "bb: aaa", "x: y", []int{502}, "least_loaded", 2)
|
2021-03-05 16:21:11 +00:00
|
|
|
|
|
|
|
// Complex routing regexp paths in `url_map`
|
|
|
|
ui = &UserInfo{
|
2023-01-27 08:18:58 +00:00
|
|
|
URLMaps: []URLMap{
|
2021-03-05 16:21:11 +00:00
|
|
|
{
|
2023-12-13 22:46:36 +00:00
|
|
|
SrcPaths: getRegexs([]string{"/api/v1/query(_range)?", "/api/v1/label/[^/]+/values"}),
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("http://vmselect/0/prometheus"),
|
2021-03-05 16:21:11 +00:00
|
|
|
},
|
|
|
|
{
|
2023-12-13 22:46:36 +00:00
|
|
|
SrcPaths: getRegexs([]string{"/api/v1/write"}),
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("http://vminsert/0/prometheus"),
|
2021-03-05 16:21:11 +00:00
|
|
|
},
|
2023-12-13 22:46:36 +00:00
|
|
|
{
|
|
|
|
SrcHosts: getRegexs([]string{"vmui\\..+"}),
|
|
|
|
URLPrefix: mustParseURL("http://vmui.host:1234/vmui/"),
|
|
|
|
},
|
2021-03-05 16:21:11 +00:00
|
|
|
},
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("http://default-server"),
|
2021-03-05 16:21:11 +00:00
|
|
|
}
|
2024-03-06 23:02:13 +00:00
|
|
|
f(ui, "/api/v1/query?query=up", "http://vmselect/0/prometheus/api/v1/query?query=up", "", "", nil, "least_loaded", 0)
|
|
|
|
f(ui, "/api/v1/query_range?query=up", "http://vmselect/0/prometheus/api/v1/query_range?query=up", "", "", nil, "least_loaded", 0)
|
|
|
|
f(ui, "/api/v1/label/foo/values", "http://vmselect/0/prometheus/api/v1/label/foo/values", "", "", nil, "least_loaded", 0)
|
|
|
|
f(ui, "/api/v1/write", "http://vminsert/0/prometheus/api/v1/write", "", "", nil, "least_loaded", 0)
|
|
|
|
f(ui, "/api/v1/foo/bar", "http://default-server/api/v1/foo/bar", "", "", nil, "least_loaded", 0)
|
|
|
|
f(ui, "https://vmui.foobar.com/a/b?c=d", "http://vmui.host:1234/vmui/a/b?c=d", "", "", nil, "least_loaded", 0)
|
2023-12-13 22:46:36 +00:00
|
|
|
|
2021-04-20 07:51:03 +00:00
|
|
|
f(&UserInfo{
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("http://foo.bar?extra_label=team=dev"),
|
2024-03-06 23:02:13 +00:00
|
|
|
}, "/api/v1/query", "http://foo.bar/api/v1/query?extra_label=team=dev", "", "", nil, "least_loaded", 0)
|
2021-04-20 07:51:03 +00:00
|
|
|
f(&UserInfo{
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("http://foo.bar?extra_label=team=mobile"),
|
2024-04-17 12:03:15 +00:00
|
|
|
}, "/api/v1/query?extra_label=team=dev", "http://foo.bar/api/v1/query?extra_label=team%3Dmobile", "", "", nil, "least_loaded", 0)
|
2024-04-17 07:54:43 +00:00
|
|
|
|
|
|
|
// Complex routing regexp query args in `url_map`
|
|
|
|
ui = &UserInfo{
|
|
|
|
URLMaps: []URLMap{
|
|
|
|
{
|
|
|
|
SrcPaths: getRegexs([]string{"/api/v1/query"}),
|
2024-04-17 12:03:15 +00:00
|
|
|
SrcQueryArgs: []*QueryArg{
|
|
|
|
mustNewQueryArg(`query=~.*{.*env="dev".*}*.`),
|
2024-04-17 07:54:43 +00:00
|
|
|
},
|
|
|
|
URLPrefix: mustParseURL("http://vmselect/0/prometheus"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
SrcPaths: getRegexs([]string{"/api/v1/query"}),
|
2024-04-17 12:03:15 +00:00
|
|
|
SrcQueryArgs: []*QueryArg{
|
|
|
|
mustNewQueryArg(`query=~.*{.*env="prod".*}.*`),
|
2024-04-17 07:54:43 +00:00
|
|
|
},
|
|
|
|
URLPrefix: mustParseURL("http://vmselect/1/prometheus"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
URLPrefix: mustParseURL("http://default-server"),
|
|
|
|
}
|
2024-04-17 12:03:15 +00:00
|
|
|
f(ui, `/api/v1/query?query=up{env="prod"}`, `http://vmselect/1/prometheus/api/v1/query?query=up%7Benv%3D%22prod%22%7D`, "", "", nil, "least_loaded", 0)
|
|
|
|
f(ui, `/api/v1/query?query=up{foo="bar",env="dev",pod!=""}`, `http://vmselect/0/prometheus/api/v1/query?query=up%7Bfoo%3D%22bar%22%2Cenv%3D%22dev%22%2Cpod%21%3D%22%22%7D`, "", "", nil, "least_loaded", 0)
|
|
|
|
f(ui, `/api/v1/query?query=up{foo="bar"}`, `http://default-server/api/v1/query?query=up%7Bfoo%3D%22bar%22%7D`, "", "", nil, "least_loaded", 0)
|
2021-02-11 10:40:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateTargetURLFailure(t *testing.T) {
|
|
|
|
f := func(ui *UserInfo, requestURI string) {
|
|
|
|
t.Helper()
|
|
|
|
u, err := url.Parse(requestURI)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("cannot parse %q: %s", requestURI, err)
|
|
|
|
}
|
2023-02-10 05:05:13 +00:00
|
|
|
u = normalizeURL(u)
|
2024-03-06 19:56:32 +00:00
|
|
|
up, hc := ui.getURLPrefixAndHeaders(u, nil)
|
2023-02-10 05:05:13 +00:00
|
|
|
if up != nil {
|
2023-02-11 08:27:40 +00:00
|
|
|
t.Fatalf("unexpected non-empty up=%#v", up)
|
2021-02-11 10:40:59 +00:00
|
|
|
}
|
2023-09-08 20:39:17 +00:00
|
|
|
if hc.RequestHeaders != nil {
|
2024-04-17 12:03:15 +00:00
|
|
|
t.Fatalf("unexpected non-empty request headers: %s", headersToString(hc.RequestHeaders))
|
2023-09-08 20:39:17 +00:00
|
|
|
}
|
|
|
|
if hc.ResponseHeaders != nil {
|
2024-04-17 12:03:15 +00:00
|
|
|
t.Fatalf("unexpected non-empty response headers: %s", headersToString(hc.ResponseHeaders))
|
2023-08-31 12:26:51 +00:00
|
|
|
}
|
2021-02-11 10:40:59 +00:00
|
|
|
}
|
|
|
|
f(&UserInfo{}, "/foo/bar")
|
|
|
|
f(&UserInfo{
|
2023-01-27 08:18:58 +00:00
|
|
|
URLMaps: []URLMap{
|
2021-02-11 10:40:59 +00:00
|
|
|
{
|
2023-12-13 22:46:36 +00:00
|
|
|
SrcPaths: getRegexs([]string{"/api/v1/query"}),
|
2021-04-21 07:55:29 +00:00
|
|
|
URLPrefix: mustParseURL("http://foobar/baz"),
|
2021-02-11 10:40:59 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}, "/api/v1/write")
|
2020-05-07 09:36:32 +00:00
|
|
|
}
|
2024-03-06 23:02:13 +00:00
|
|
|
|
2024-04-17 12:03:15 +00:00
|
|
|
func headersToString(hs []*Header) string {
|
2024-03-06 23:02:13 +00:00
|
|
|
a := make([]string, len(hs))
|
|
|
|
for i, h := range hs {
|
|
|
|
a[i] = fmt.Sprintf("%s: %s", h.Name, h.Value)
|
|
|
|
}
|
|
|
|
return strings.Join(a, "\n")
|
|
|
|
}
|