Compare commits

...

21 Commits

Author SHA1 Message Date
Aliaksandr Valialkin
fc0edfab11 docs/CHANGELOG.md: cut v1.79.2 2022-08-08 16:46:36 +03:00
Aliaksandr Valialkin
d3b38ddb2e app/vmselect/promql/transform.go: reuse evalNumber() function for constructing timezone_offset() results 2022-08-08 16:39:27 +03:00
Roman Khavronenko
056960102a lib/promrelabel: fix expected test result (#2957)
follow-up after 68c4ec9472

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2022-08-08 16:29:39 +03:00
Aliaksandr Valialkin
aef7b33867 docs/CHANGELOG.md: document bugfix for https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2874
This is a follow-up for edecd2493c
2022-08-08 15:53:07 +03:00
Yury Molodov
095933eef8 fix: change the z-index of the datepicker (#2891) 2022-08-08 15:52:17 +03:00
Aliaksandr Valialkin
d335436b9a lib/promscrape/discovery/kubernetes: add missing __meta_kubernetes_ingress_class_name label for role: ingress
See 7e65ad3e43
and 7e1111ff14
2022-08-08 15:51:24 +03:00
Aliaksandr Valialkin
d77455a485 docs/CHANGELOG.md: link to the issue regarding the increased load on Consul
This is a follow-up for 68de1f4e4a

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2940
2022-08-08 15:49:35 +03:00
Aliaksandr Valialkin
e3f8796e90 lib/promscrape/discovery/consul: allow stale responses from Consul service discovery by default
This aligns with Prometheus behaviour.

See `allow_stale` option description at https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config
2022-08-08 15:48:34 +03:00
Aliaksandr Valialkin
33268b261e lib/promscrape/discovery/dockerswarm: properly set __meta_dockerswarm_container_label_* labels instead of __meta_dockerswarm_task_label_* labels
See https://github.com/prometheus/prometheus/issues/9187
2022-08-08 15:46:51 +03:00
Aliaksandr Valialkin
2426695571 app/vmselect/promql: properly return q1 series from q1 ifnot q2 when q2 returns nothing 2022-08-08 15:44:42 +03:00
Aliaksandr Valialkin
7e9794cf9f app/{vmselect,vmalert}: properly generate http redirects if -http.pathPrefix command-line flag is set
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2918
2022-08-08 15:41:14 +03:00
Aliaksandr Valialkin
05356d2a49 lib/promrelabel: do not split regex into multiple lines if it contains groups
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2928
2022-08-08 15:39:17 +03:00
Aliaksandr Valialkin
b161fc46dc docs/CHANGELOG.md: document 2f9668eba5 2022-08-08 15:35:47 +03:00
Boris Petersen
8f74f1bc91 fix assume role when running in ECS. (#2876)
This fixes #2875

Signed-off-by: Boris Petersen <boris.petersen@idealo.de>
2022-08-08 15:35:03 +03:00
Aliaksandr Valialkin
0bbe842c31 lib/promscrape: reload all the scrape configs when the global section is changed inside -promscrape.config
See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2884
2022-08-08 15:33:36 +03:00
Aliaksandr Valialkin
0d5a025934 lib/promscrape: set up=0 for partially failed scrape in stream parsing mode
This behaviour aligns with Prometheus behavior
2022-08-08 15:30:28 +03:00
Aliaksandr Valialkin
5b0b4e1078 lib/promscrape/discovery/ec2: properly handle custom endpoint option in ec2_sd_configs
This option was ignored since d289ecded1

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1287
2022-08-08 15:26:42 +03:00
Aliaksandr Valialkin
c28aba604d app/vmselect/netstorage: prevent from calling processBlocks callback after the exit from ProcessBlocks function
This should prevent from panic at multi-level vmselect
when the top-level vmselect is configured with -replicationFactor > 1
2022-08-08 15:22:47 +03:00
Aliaksandr Valialkin
8afcc01582 deployment/docker: update Go builder from v1.18.4 to v1.18.5
See https://github.com/golang/go/issues?q=milestone%3AGo1.18.5+label%3ACherryPickApproved
2022-08-03 10:53:21 +03:00
Aliaksandr Valialkin
208a63e045 docs/CHANGELOG.md: document v1.79.1 security fix 2022-08-02 13:37:10 +03:00
Aliaksandr Valialkin
5df6790daf deployment/docker: update alpine base image from 3.16.0 to 3.16.1
See https://alpinelinux.org/posts/Alpine-3.16.1-released.html
2022-08-01 14:44:36 +08:00
24 changed files with 346 additions and 156 deletions

View File

@@ -56,10 +56,12 @@ var (
awsUseSigv4 = flagutil.NewArrayBool("remoteWrite.aws.useSigv4", "Enables SigV4 request signing for the corresponding -remoteWrite.url. "+
"It is expected that other -remoteWrite.aws.* command-line flags are set if sigv4 request signing is enabled")
awsRegion = flagutil.NewArray("remoteWrite.aws.region", "Optional AWS region to use for the corresponding -remoteWrite.url if -remoteWrite.aws.useSigv4 is set")
awsRoleARN = flagutil.NewArray("remoteWrite.aws.roleARN", "Optional AWS roleARN to use for the corresponding -remoteWrite.url if -remoteWrite.aws.useSigv4 is set")
awsAccessKey = flagutil.NewArray("remoteWrite.aws.accessKey", "Optional AWS AccessKey to use for the corresponding -remoteWrite.url if -remoteWrite.aws.useSigv4 is set")
awsService = flagutil.NewArray("remoteWrite.aws.service", "Optional AWS Service to use for the corresponding -remoteWrite.url if -remoteWrite.aws.useSigv4 is set. "+
awsEC2Endpoint = flagutil.NewArray("remoteWrite.aws.ec2Endpoint", "Optional AWS EC2 API endpoint to use for the corresponding -remoteWrite.url if -remoteWrite.aws.useSigv4 is set")
awsSTSEndpoint = flagutil.NewArray("remoteWrite.aws.stsEndpoint", "Optional AWS STS API endpoint to use for the corresponding -remoteWrite.url if -remoteWrite.aws.useSigv4 is set")
awsRegion = flagutil.NewArray("remoteWrite.aws.region", "Optional AWS region to use for the corresponding -remoteWrite.url if -remoteWrite.aws.useSigv4 is set")
awsRoleARN = flagutil.NewArray("remoteWrite.aws.roleARN", "Optional AWS roleARN to use for the corresponding -remoteWrite.url if -remoteWrite.aws.useSigv4 is set")
awsAccessKey = flagutil.NewArray("remoteWrite.aws.accessKey", "Optional AWS AccessKey to use for the corresponding -remoteWrite.url if -remoteWrite.aws.useSigv4 is set")
awsService = flagutil.NewArray("remoteWrite.aws.service", "Optional AWS Service to use for the corresponding -remoteWrite.url if -remoteWrite.aws.useSigv4 is set. "+
"Defaults to \"aps\"")
awsSecretKey = flagutil.NewArray("remoteWrite.aws.secretKey", "Optional AWS SecretKey to use for the corresponding -remoteWrite.url if -remoteWrite.aws.useSigv4 is set")
)
@@ -231,12 +233,14 @@ func getAWSAPIConfig(argIdx int) (*awsapi.Config, error) {
if !awsUseSigv4.GetOptionalArg(argIdx) {
return nil, nil
}
ec2Endpoint := awsEC2Endpoint.GetOptionalArg(argIdx)
stsEndpoint := awsSTSEndpoint.GetOptionalArg(argIdx)
region := awsRegion.GetOptionalArg(argIdx)
roleARN := awsRoleARN.GetOptionalArg(argIdx)
accessKey := awsAccessKey.GetOptionalArg(argIdx)
secretKey := awsSecretKey.GetOptionalArg(argIdx)
service := awsService.GetOptionalArg(argIdx)
cfg, err := awsapi.NewConfig(region, roleARN, accessKey, secretKey, service)
cfg, err := awsapi.NewConfig(ec2Endpoint, stsEndpoint, region, roleARN, accessKey, secretKey, service)
if err != nil {
return nil, err
}

View File

@@ -160,7 +160,7 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
if strings.HasPrefix(r.URL.Path, "/api/v1/") {
redirectURL = alert.APILink()
}
http.Redirect(w, r, "/"+redirectURL, http.StatusPermanentRedirect)
httpserver.RedirectPermanent(w, "/"+redirectURL)
return true
}
}

View File

@@ -168,7 +168,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
_ = r.ParseForm()
path = strings.TrimPrefix(path, "/")
newURL := path + "/?" + r.Form.Encode()
http.Redirect(w, r, newURL, http.StatusMovedPermanently)
httpserver.RedirectPermanent(w, newURL)
return true
}
if strings.HasPrefix(path, "/vmui/") {
@@ -217,7 +217,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
// vmalert access via incomplete url without `/` in the end. Redirecto to complete url.
// Use relative redirect, since, since the hostname and path prefix may be incorrect if VictoriaMetrics
// is hidden behind vmauth or similar proxy.
http.Redirect(w, r, "vmalert/", http.StatusMovedPermanently)
httpserver.RedirectPermanent(w, "vmalert/")
return true
}
if strings.HasPrefix(path, "/vmalert/") {

View File

@@ -36,9 +36,9 @@ var binaryOpFuncs = map[string]binaryOpFunc{
"unless": binaryOpUnless,
// New ops
"if": newBinaryOpArithFunc(binaryop.If),
"ifnot": newBinaryOpArithFunc(binaryop.Ifnot),
"default": newBinaryOpArithFunc(binaryop.Default),
"if": binaryOpIf,
"ifnot": binaryOpIfnot,
"default": binaryOpDefault,
}
func getBinaryOpFunc(op string) binaryOpFunc {
@@ -86,17 +86,6 @@ func newBinaryOpFunc(bf func(left, right float64, isBool bool) float64) binaryOp
right := bfa.right
op := bfa.be.Op
switch true {
case op == "ifnot":
left = removeEmptySeries(left)
// Do not remove empty series on the right side,
// so the left-side series could be matched against them.
case op == "default":
// Do not remove empty series on the left and the right side,
// since this may lead to missing result:
// - if empty time series are removed on the left side,
// then they won't be substituted by time series from the right side.
// - if empty time series are removed on the right side,
// then this may result in missing time series from the left side.
case metricsql.IsBinaryOpCmp(op):
// Do not remove empty series for comparison operations,
// since this may lead to missing result.
@@ -136,7 +125,7 @@ func newBinaryOpFunc(bf func(left, right float64, isBool bool) float64) binaryOp
func adjustBinaryOpTags(be *metricsql.BinaryOpExpr, left, right []*timeseries) ([]*timeseries, []*timeseries, []*timeseries, error) {
if len(be.GroupModifier.Op) == 0 && len(be.JoinModifier.Op) == 0 {
if isScalar(left) && be.Op != "default" && be.Op != "if" && be.Op != "ifnot" {
if isScalar(left) {
// Fast path: `scalar op vector`
rvsLeft := make([]*timeseries, len(right))
tsLeft := left[0]
@@ -324,14 +313,23 @@ func resetMetricGroupIfRequired(be *metricsql.BinaryOpExpr, ts *timeseries) {
// Do not reset MetricGroup for non-boolean `compare` binary ops like Prometheus does.
return
}
switch be.Op {
case "default", "if", "ifnot":
// Do not reset MetricGroup for these ops.
return
}
ts.MetricName.ResetMetricGroup()
}
func binaryOpIf(bfa *binaryOpFuncArg) ([]*timeseries, error) {
mLeft, mRight := createTimeseriesMapByTagSet(bfa.be, bfa.left, bfa.right)
var rvs []*timeseries
for k, tssLeft := range mLeft {
tssRight := seriesByKey(mRight, k)
if tssRight == nil {
continue
}
tssLeft = addRightNaNsToLeft(tssLeft, tssRight)
rvs = append(rvs, tssLeft...)
}
return rvs, nil
}
func binaryOpAnd(bfa *binaryOpFuncArg) ([]*timeseries, error) {
mLeft, mRight := createTimeseriesMapByTagSet(bfa.be, bfa.left, bfa.right)
var rvs []*timeseries
@@ -340,24 +338,47 @@ func binaryOpAnd(bfa *binaryOpFuncArg) ([]*timeseries, error) {
if tssLeft == nil {
continue
}
// Add gaps to tssLeft if there are gaps at tssRight.
for _, tsLeft := range tssLeft {
valuesLeft := tsLeft.Values
for i := range valuesLeft {
hasValue := false
for _, tsRight := range tssRight {
if !math.IsNaN(tsRight.Values[i]) {
hasValue = true
break
}
}
if !hasValue {
valuesLeft[i] = nan
tssLeft = addRightNaNsToLeft(tssLeft, tssRight)
rvs = append(rvs, tssLeft...)
}
return rvs, nil
}
func addRightNaNsToLeft(tssLeft, tssRight []*timeseries) []*timeseries {
for _, tsLeft := range tssLeft {
valuesLeft := tsLeft.Values
for i := range valuesLeft {
hasValue := false
for _, tsRight := range tssRight {
if !math.IsNaN(tsRight.Values[i]) {
hasValue = true
break
}
}
if !hasValue {
valuesLeft[i] = nan
}
}
tssLeft = removeEmptySeries(tssLeft)
}
return removeEmptySeries(tssLeft)
}
func binaryOpDefault(bfa *binaryOpFuncArg) ([]*timeseries, error) {
mLeft, mRight := createTimeseriesMapByTagSet(bfa.be, bfa.left, bfa.right)
var rvs []*timeseries
if len(mLeft) == 0 {
for _, tss := range mRight {
rvs = append(rvs, tss...)
}
return rvs, nil
}
for k, tssLeft := range mLeft {
rvs = append(rvs, tssLeft...)
tssRight := seriesByKey(mRight, k)
if tssRight == nil {
continue
}
fillLeftNaNsWithRightValues(tssLeft, tssRight)
}
return rvs, nil
}
@@ -374,24 +395,43 @@ func binaryOpOr(bfa *binaryOpFuncArg) ([]*timeseries, error) {
rvs = append(rvs, tssRight...)
continue
}
// Fill gaps in tssLeft with values from tssRight as Prometheus does.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/552
for _, tsLeft := range tssLeft {
valuesLeft := tsLeft.Values
for i, v := range valuesLeft {
if !math.IsNaN(v) {
continue
}
for _, tsRight := range tssRight {
vRight := tsRight.Values[i]
if !math.IsNaN(vRight) {
valuesLeft[i] = vRight
break
}
fillLeftNaNsWithRightValues(tssLeft, tssRight)
}
return rvs, nil
}
func fillLeftNaNsWithRightValues(tssLeft, tssRight []*timeseries) {
// Fill gaps in tssLeft with values from tssRight as Prometheus does.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/552
for _, tsLeft := range tssLeft {
valuesLeft := tsLeft.Values
for i, v := range valuesLeft {
if !math.IsNaN(v) {
continue
}
for _, tsRight := range tssRight {
vRight := tsRight.Values[i]
if !math.IsNaN(vRight) {
valuesLeft[i] = vRight
break
}
}
}
}
}
func binaryOpIfnot(bfa *binaryOpFuncArg) ([]*timeseries, error) {
mLeft, mRight := createTimeseriesMapByTagSet(bfa.be, bfa.left, bfa.right)
var rvs []*timeseries
for k, tssLeft := range mLeft {
tssRight := seriesByKey(mRight, k)
if tssRight == nil {
rvs = append(rvs, tssLeft...)
continue
}
tssLeft = addLeftNaNsIfNoRightNaNs(tssLeft, tssRight)
rvs = append(rvs, tssLeft...)
}
return rvs, nil
}
@@ -404,24 +444,44 @@ func binaryOpUnless(bfa *binaryOpFuncArg) ([]*timeseries, error) {
rvs = append(rvs, tssLeft...)
continue
}
// Add gaps to tssLeft if the are no gaps at tssRight.
for _, tsLeft := range tssLeft {
valuesLeft := tsLeft.Values
for i := range valuesLeft {
for _, tsRight := range tssRight {
if !math.IsNaN(tsRight.Values[i]) {
valuesLeft[i] = nan
break
}
}
}
}
tssLeft = removeEmptySeries(tssLeft)
tssLeft = addLeftNaNsIfNoRightNaNs(tssLeft, tssRight)
rvs = append(rvs, tssLeft...)
}
return rvs, nil
}
func addLeftNaNsIfNoRightNaNs(tssLeft, tssRight []*timeseries) []*timeseries {
for _, tsLeft := range tssLeft {
valuesLeft := tsLeft.Values
for i := range valuesLeft {
for _, tsRight := range tssRight {
if !math.IsNaN(tsRight.Values[i]) {
valuesLeft[i] = nan
break
}
}
}
}
return removeEmptySeries(tssLeft)
}
func seriesByKey(m map[string][]*timeseries, key string) []*timeseries {
tss := m[key]
if tss != nil {
return tss
}
if len(m) != 1 {
return nil
}
for _, tss := range m {
if isScalar(tss) {
return tss
}
return nil
}
return nil
}
func createTimeseriesMapByTagSet(be *metricsql.BinaryOpExpr, left, right []*timeseries) (map[string][]*timeseries, map[string][]*timeseries) {
groupTags := be.GroupModifier.Args
groupOp := strings.ToLower(be.GroupModifier.Op)

View File

@@ -2803,7 +2803,12 @@ func TestExecSuccess(t *testing.T) {
t.Run(`scalar default vector1`, func(t *testing.T) {
t.Parallel()
q := `time() > 1400 default label_set(123, "foo", "bar")`
resultExpected := []netstorage.Result{}
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, nan, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`scalar default vector2`, func(t *testing.T) {
@@ -6092,7 +6097,18 @@ func TestExecSuccess(t *testing.T) {
t.Run(`ifnot-no-matching-timeseries`, func(t *testing.T) {
t.Parallel()
q := `label_set(time(), "foo", "bar") ifnot label_set(time() > 1400, "x", "y")`
resultExpected := []netstorage.Result{}
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r.MetricName.Tags = []storage.Tag{
{
Key: []byte("foo"),
Value: []byte("bar"),
},
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`quantile(-2)`, func(t *testing.T) {

View File

@@ -2183,17 +2183,13 @@ func transformTimezoneOffset(tfa *transformFuncArg) ([]*timeseries, error) {
return nil, fmt.Errorf("cannot load timezone %q: %w", tzString, err)
}
var ts timeseries
ts.denyReuse = true
timestamps := tfa.ec.getSharedTimestamps()
values := make([]float64, len(timestamps))
for i, v := range timestamps {
_, offset := time.Unix(v/1000, 0).In(loc).Zone()
values[i] = float64(offset)
tss := evalNumber(tfa.ec, nan)
ts := tss[0]
for i, timestamp := range ts.Timestamps {
_, offset := time.Unix(timestamp/1000, 0).In(loc).Zone()
ts.Values[i] = float64(offset)
}
ts.Values = values
ts.Timestamps = timestamps
return []*timeseries{&ts}, nil
return tss, nil
}
func transformTime(tfa *transformFuncArg) ([]*timeseries, error) {

View File

@@ -6,7 +6,7 @@ COPY web/ /build/
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o web-amd64 github.com/VictoriMetrics/vmui/ && \
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o web-windows github.com/VictoriMetrics/vmui/
FROM alpine:3.16.0
FROM alpine:3.16.1
USER root
COPY --from=build-web-stage /build/web-amd64 /app/web

View File

@@ -110,7 +110,9 @@ export const TimeSelector: FC = () => {
open={open}
anchorEl={anchorEl}
placement="bottom-end"
modifiers={[{name: "offset", options: {offset: [0, 6]}}]}>
modifiers={[{name: "offset", options: {offset: [0, 6]}}]}
sx={{zIndex: 3, position: "relative"}}
>
<ClickAwayListener onClickAway={() => setAnchorEl(null)}>
<Paper elevation={3}>
<Box sx={classes.container}>

View File

@@ -2,9 +2,9 @@
DOCKER_NAMESPACE := victoriametrics
ROOT_IMAGE ?= alpine:3.16.0
CERTS_IMAGE := alpine:3.16.0
GO_BUILDER_IMAGE := golang:1.18.4-alpine
ROOT_IMAGE ?= alpine:3.16.1
CERTS_IMAGE := alpine:3.16.1
GO_BUILDER_IMAGE := golang:1.18.5-alpine
BUILDER_IMAGE := local/builder:2.0.0-$(shell echo $(GO_BUILDER_IMAGE) | tr :/ __)-1
BASE_IMAGE := local/base:1.1.3-$(shell echo $(ROOT_IMAGE) | tr :/ __)-$(shell echo $(CERTS_IMAGE) | tr :/ __)

View File

@@ -13,7 +13,32 @@ The following tip changes can be tested by building VictoriaMetrics components f
* [How to build vmauth](https://docs.victoriametrics.com/vmauth.html#how-to-build-from-sources)
* [How to build vmctl](https://docs.victoriametrics.com/vmctl.html#how-to-build)
## tip
## v1.79.x long-time support release (LTS)
## [v1.79.2](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.79.2)
Released at 08-08-2022
* BUGFIX: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): fix potential panic in [multi-level cluster setup](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#multi-level-cluster-setup) when top-level `vmselect` is configured with `-replicationFactor` bigger than 1. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2961).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly handle custom `endpoint` value in [ec2_sd_configs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ec2_sd_config). It was ignored since [v1.77.0](https://docs.victoriametrics.com/CHANGELOG.html#v1770) because of a bug in the implementation of [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1287).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): add missing `__meta_kubernetes_ingress_class_name` meta-label for `role: ingress` service discovery in Kubernetes. See [this commit from Prometheus](https://github.com/prometheus/prometheus/commit/7e65ad3e432bd2837c17e3e63e85dcbcc30f4a8a).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): allow stale responses from Consul service discovery (aka [consul_sd_configs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#consul_sd_config)) by default in the same way as Prometheus does. This should reduce load on Consul when discovering big number of targets. Stale responses can be disabled by specifying `allow_stale: false` option in `consul_sd_config`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2940).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): [dockerswarm_sd_configs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dockerswarm_sd_config): properly set `__meta_dockerswarm_container_label_*` labels instead of `__meta_dockerswarm_task_label_*` labels as Prometheus does. See [this issue](https://github.com/prometheus/prometheus/issues/9187).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): set `up` metric to `0` for partial scrapes in [stream parsing mode](https://docs.victoriametrics.com/vmagent.html#stream-parsing-mode). Previously the `up` metric was set to `1` when at least a single metric has been scraped before the error. This aligns the behaviour of `vmselect` with Prometheus.
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): restart all the scrape jobs during [config reload](https://docs.victoriametrics.com/vmagent.html#configuration-update) after `global` section is changed inside `-promscrape.config`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2884).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly assume role with AWS ECS credentials. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2875). Thanks to @transacid for [the fix](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2876).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): do not split regex in [relabeling rules](https://docs.victoriametrics.com/vmagent.html#relabeling) into multiple lines if it contains groups. This fixes [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2928).
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): return series from `q1` if `q2` doesn't return matching time series in the query `q1 ifnot q2`. Previously series from `q1` weren't returned in this case.
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): properly show date picker at `Table` tab. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2874).
* BUGFIX: properly generate http redirects if `-http.pathPrefix` command-line flag is set. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2918).
## [v1.79.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.79.1)
Released at 02-08-2022
* SECURITY FIX: upgrade base docker image (alpine) from 3.16.0 to 3.16.1 . See [alpine 3.16.1 release notes](https://alpinelinux.org/posts/Alpine-3.16.1-released.html).
## [v1.79.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.79.0)

View File

@@ -43,8 +43,8 @@ type credentials struct {
Expiration time.Time
}
// NewConfig returns new AWS Config.
func NewConfig(region, roleARN, accessKey, secretKey, service string) (*Config, error) {
// NewConfig returns new AWS Config from the given args.
func NewConfig(ec2Endpoint, stsEndpoint, region, roleARN, accessKey, secretKey, service string) (*Config, error) {
cfg := &Config{
client: http.DefaultClient,
region: region,
@@ -65,8 +65,8 @@ func NewConfig(region, roleARN, accessKey, secretKey, service string) (*Config,
}
cfg.region = r
}
cfg.ec2Endpoint = buildAPIEndpoint(cfg.ec2Endpoint, cfg.region, "ec2")
cfg.stsEndpoint = buildAPIEndpoint(cfg.stsEndpoint, cfg.region, "sts")
cfg.ec2Endpoint = buildAPIEndpoint(ec2Endpoint, cfg.region, "ec2")
cfg.stsEndpoint = buildAPIEndpoint(stsEndpoint, cfg.region, "sts")
if cfg.roleARN == "" {
cfg.roleARN = os.Getenv("AWS_ROLE_ARN")
}
@@ -204,7 +204,11 @@ func (cfg *Config) getAPICredentials() (*credentials, error) {
}
if ecsMetaURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); len(ecsMetaURI) > 0 {
path := "http://169.254.170.2" + ecsMetaURI
return getECSRoleCredentialsByPath(cfg.client, path)
ac, err := getECSRoleCredentialsByPath(cfg.client, path)
if err != nil {
return nil, fmt.Errorf("cannot obtain ECS role credentials: %w", err)
}
acNew = ac
}
// we need instance credentials if dont have access keys

View File

@@ -235,13 +235,27 @@ func handlerWrapper(s *server, w http.ResponseWriter, r *http.Request, rh Reques
connTimeoutClosedConns.Inc()
w.Header().Set("Connection", "close")
}
path, err := getCanonicalPath(r.URL.Path)
if err != nil {
Errorf(w, r, "cannot get canonical path: %s", err)
unsupportedRequestErrors.Inc()
return
path := r.URL.Path
prefix := GetPathPrefix()
if prefix != "" {
// Trim -http.pathPrefix from path
prefixNoTrailingSlash := strings.TrimSuffix(prefix, "/")
if path == prefixNoTrailingSlash {
// Redirect to url with / at the end.
// This is needed for proper handling of relative urls in web browsers.
// Intentionally ignore query args, since it is expected that the requested url
// is composed by a human, so it doesn't contain query args.
RedirectPermanent(w, prefix)
return
}
if !strings.HasPrefix(path, prefix) {
Errorf(w, r, "missing -http.pathPrefix=%q in the requested path %q", *pathPrefix, path)
unsupportedRequestErrors.Inc()
return
}
path = path[len(prefix)-1:]
r.URL.Path = path
}
r.URL.Path = path
switch r.URL.Path {
case "/health":
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
@@ -327,24 +341,6 @@ func handlerWrapper(s *server, w http.ResponseWriter, r *http.Request, rh Reques
}
}
func getCanonicalPath(path string) (string, error) {
if len(*pathPrefix) == 0 || path == "/" {
return path, nil
}
if *pathPrefix == path {
return "/", nil
}
prefix := *pathPrefix
if !strings.HasSuffix(prefix, "/") {
prefix = prefix + "/"
}
if !strings.HasPrefix(path, prefix) {
return "", fmt.Errorf("missing `-pathPrefix=%q` in the requested path: %q", *pathPrefix, path)
}
path = path[len(prefix)-1:]
return path, nil
}
func checkBasicAuth(w http.ResponseWriter, r *http.Request) bool {
if len(*httpAuthUsername) == 0 {
// HTTP Basic Auth is disabled.
@@ -643,7 +639,17 @@ func IsTLS() bool {
// GetPathPrefix - returns http server path prefix.
func GetPathPrefix() string {
return *pathPrefix
prefix := *pathPrefix
if prefix == "" {
return ""
}
if !strings.HasPrefix(prefix, "/") {
prefix = "/" + prefix
}
if !strings.HasSuffix(prefix, "/") {
prefix += "/"
}
return prefix
}
// WriteAPIHelp writes pathList to w in HTML format.
@@ -671,3 +677,12 @@ func GetRequestURI(r *http.Request) string {
}
return requestURI + delimiter + queryArgs
}
// RedirectPermanent redirects to the given url using 301 status code.
func RedirectPermanent(w http.ResponseWriter, url string) {
// Do not use http.Redirect, since it breaks relative redirects
// if the http.Request.URL contains unexpected url.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2918
w.Header().Set("Location", url)
w.WriteHeader(http.StatusMovedPermanently)
}

View File

@@ -104,6 +104,11 @@ func stringValue(v interface{}) (string, error) {
// MarshalYAML marshals mlr to YAML.
func (mlr *MultiLineRegex) MarshalYAML() (interface{}, error) {
if strings.ContainsAny(mlr.S, "([") {
// The mlr.S contains groups. Fall back to returning the regexp as is without splitting it into parts.
// This fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2928 .
return mlr.S, nil
}
a := strings.Split(mlr.S, "|")
if len(a) == 1 {
return a[0], nil

View File

@@ -7,6 +7,30 @@ import (
"gopkg.in/yaml.v2"
)
func TestMultiLineRegexUnmarshalMarshal(t *testing.T) {
f := func(data, resultExpected string) {
t.Helper()
var mlr MultiLineRegex
if err := yaml.UnmarshalStrict([]byte(data), &mlr); err != nil {
t.Fatalf("cannot unmarshal %q: %s", data, err)
}
result, err := yaml.Marshal(&mlr)
if err != nil {
t.Fatalf("cannot marshal %q: %s", data, err)
}
if string(result) != resultExpected {
t.Fatalf("unexpected marshaled data; got\n%q\nwant\n%q", result, resultExpected)
}
}
f(``, `""`+"\n")
f(`foo`, "foo\n")
f(`a|b||c`, "- a\n- b\n- \"\"\n- c\n")
f(`(a|b)`, "(a|b)\n")
f(`a|b[c|d]`, "a|b[c|d]\n")
f("- a\n- b", "- a\n- b\n")
f("- a\n- (b)", "a|(b)\n")
}
func TestRelabelConfigMarshalUnmarshal(t *testing.T) {
f := func(data, resultExpected string) {
t.Helper()
@@ -31,7 +55,7 @@ func TestRelabelConfigMarshalUnmarshal(t *testing.T) {
- regex:
- 'fo.+'
- '.*ba[r-z]a'
`, "- regex:\n - fo.+\n - .*ba[r-z]a\n")
`, "- regex: fo.+|.*ba[r-z]a\n")
f(`- regex: foo|bar`, "- regex:\n - foo\n - bar\n")
f(`- regex: True`, `- regex: "true"`+"\n")
f(`- regex: true`, `- regex: "true"`+"\n")

View File

@@ -130,6 +130,10 @@ func (cfg *Config) mustRestart(prevCfg *Config) {
prevScrapeCfgByName[scPrev.JobName] = scPrev
}
// Restart all the scrape jobs on Global config change.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2884
needGlobalRestart := !areEqualGlobalConfigs(&cfg.Global, &prevCfg.Global)
// Loop over the the new jobs, start new ones and restart updated ones.
var started, stopped, restarted int
currentJobNames := make(map[string]struct{}, len(cfg.ScrapeConfigs))
@@ -142,7 +146,7 @@ func (cfg *Config) mustRestart(prevCfg *Config) {
started++
continue
}
if areEqualScrapeConfigs(scPrev, sc) {
if !needGlobalRestart && areEqualScrapeConfigs(scPrev, sc) {
// The scrape config didn't change, so no need to restart it.
// Use the reference to the previous job, so it could be stopped properly later.
cfg.ScrapeConfigs[i] = scPrev
@@ -165,6 +169,12 @@ func (cfg *Config) mustRestart(prevCfg *Config) {
logger.Infof("restarted service discovery routines in %.3f seconds, stopped=%d, started=%d, restarted=%d", time.Since(startTime).Seconds(), stopped, started, restarted)
}
func areEqualGlobalConfigs(a, b *GlobalConfig) bool {
sa := a.marshalJSON()
sb := b.marshalJSON()
return string(sa) == string(sb)
}
func areEqualScrapeConfigs(a, b *ScrapeConfig) bool {
sa := a.marshalJSON()
sb := b.marshalJSON()
@@ -183,6 +193,14 @@ func (sc *ScrapeConfig) marshalJSON() []byte {
return data
}
func (gc *GlobalConfig) marshalJSON() []byte {
data, err := json.Marshal(gc)
if err != nil {
logger.Panicf("BUG: cannot marshal GlobalConfig: %s", err)
}
return data
}
func (cfg *Config) mustStop() {
startTime := time.Now()
logger.Infof("stopping service discovery routines...")

View File

@@ -27,7 +27,7 @@ type SDConfig struct {
Tags []string `yaml:"tags,omitempty"`
NodeMeta map[string]string `yaml:"node_meta,omitempty"`
TagSeparator *string `yaml:"tag_separator,omitempty"`
AllowStale bool `yaml:"allow_stale,omitempty"`
AllowStale *bool `yaml:"allow_stale,omitempty"`
// RefreshInterval time.Duration `yaml:"refresh_interval"`
// refresh_interval is obtained from `-promscrape.consulSDCheckInterval` command-line option.
}

View File

@@ -45,7 +45,7 @@ type serviceWatcher struct {
// newConsulWatcher creates new watcher and starts background service discovery for Consul.
func newConsulWatcher(client *discoveryutils.Client, sdc *SDConfig, datacenter, namespace string) *consulWatcher {
baseQueryArgs := "?dc=" + url.QueryEscape(datacenter)
if sdc.AllowStale {
if sdc.AllowStale == nil || *sdc.AllowStale {
baseQueryArgs += "&stale"
}
if namespace != "" {

View File

@@ -15,7 +15,6 @@ type task struct {
ID string
ServiceID string
NodeID string
Labels map[string]string
DesiredState string
NetworksAttachments []struct {
Addresses []string
@@ -32,6 +31,11 @@ type task struct {
Ports []portConfig
}
}
Spec struct {
ContainerSpec struct {
Labels map[string]string
}
}
Slot int
}
@@ -82,8 +86,8 @@ func addTasksLabels(tasks []task, nodesLabels, servicesLabels []map[string]strin
"__meta_dockerswarm_task_slot": strconv.Itoa(task.Slot),
"__meta_dockerswarm_task_state": task.Status.State,
}
for k, v := range task.Labels {
commonLabels["__meta_dockerswarm_task_label_"+discoveryutils.SanitizeLabelName(k)] = v
for k, v := range task.Spec.ContainerSpec.Labels {
commonLabels["__meta_dockerswarm_container_label_"+discoveryutils.SanitizeLabelName(k)] = v
}
var svcPorts []portConfig
for i, v := range services {

View File

@@ -27,13 +27,13 @@ func Test_parseTasks(t *testing.T) {
"Version": {
"Index": 23
},
"Labels": {
"label1": "value1"
},
"Spec": {
"ContainerSpec": {
"Image": "redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842",
"Init": false
"Init": false,
"Labels": {
"label1": "value1"
}
},
"Resources": {
"Limits": {},
@@ -70,8 +70,18 @@ func Test_parseTasks(t *testing.T) {
ID: "t4rdm7j2y9yctbrksiwvsgpu5",
ServiceID: "t91nf284wzle1ya09lqvyjgnq",
NodeID: "qauwmifceyvqs0sipvzu8oslu",
Labels: map[string]string{
"label1": "value1",
Spec: struct {
ContainerSpec struct {
Labels map[string]string
}
}{
ContainerSpec: struct {
Labels map[string]string
}{
Labels: map[string]string{
"label1": "value1",
},
},
},
DesiredState: "running",
Slot: 1,
@@ -97,7 +107,7 @@ func Test_parseTasks(t *testing.T) {
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseTasks() got = %v, want %v", got, tt.want)
t.Errorf("parseTasks() got\n%v\nwant\n%v", got, tt.want)
}
})
}
@@ -126,7 +136,6 @@ func Test_addTasksLabels(t *testing.T) {
ID: "t4rdm7j2y9yctbrksiwvsgpu5",
ServiceID: "t91nf284wzle1ya09lqvyjgnq",
NodeID: "qauwmifceyvqs0sipvzu8oslu",
Labels: map[string]string{},
DesiredState: "running",
Slot: 1,
Status: struct {
@@ -194,7 +203,6 @@ func Test_addTasksLabels(t *testing.T) {
ID: "t4rdm7j2y9yctbrksiwvsgpu5",
ServiceID: "tgsci5gd31aai3jyudv98pqxf",
NodeID: "qauwmifceyvqs0sipvzu8oslu",
Labels: map[string]string{},
DesiredState: "running",
Slot: 1,
NetworksAttachments: []struct {

View File

@@ -33,7 +33,11 @@ func newAPIConfig(sdc *SDConfig) (*apiConfig, error) {
if sdc.Port != nil {
port = *sdc.Port
}
awsCfg, err := awsapi.NewConfig(sdc.Region, sdc.RoleARN, sdc.AccessKey, sdc.SecretKey.String(), "ec2")
stsEndpoint := sdc.STSEndpoint
if stsEndpoint == "" {
stsEndpoint = sdc.Endpoint
}
awsCfg, err := awsapi.NewConfig(sdc.Endpoint, stsEndpoint, sdc.Region, sdc.RoleARN, sdc.AccessKey, sdc.SecretKey.String(), "ec2")
if err != nil {
return nil, err
}

View File

@@ -18,12 +18,13 @@ var SDCheckInterval = flag.Duration("promscrape.ec2SDCheckInterval", time.Minute
//
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ec2_sd_config
type SDConfig struct {
Region string `yaml:"region,omitempty"`
Endpoint string `yaml:"endpoint,omitempty"`
AccessKey string `yaml:"access_key,omitempty"`
SecretKey *promauth.Secret `yaml:"secret_key,omitempty"`
Region string `yaml:"region,omitempty"`
Endpoint string `yaml:"endpoint,omitempty"`
STSEndpoint string `yaml:"sts_endpoint,omitempty"`
AccessKey string `yaml:"access_key,omitempty"`
SecretKey *promauth.Secret `yaml:"secret_key,omitempty"`
// TODO add support for Profile, not working atm
Profile string `yaml:"profile,omitempty"`
// Profile string `yaml:"profile,omitempty"`
RoleARN string `yaml:"role_arn,omitempty"`
// RefreshInterval time.Duration `yaml:"refresh_interval"`
// refresh_interval is obtained from `-promscrape.ec2SDCheckInterval` command-line option.

View File

@@ -52,8 +52,9 @@ type Ingress struct {
//
// See https://v1-21.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#ingressspec-v1-networking-k8s-io
type IngressSpec struct {
TLS []IngressTLS `json:"tls"`
Rules []IngressRule
TLS []IngressTLS `json:"tls"`
Rules []IngressRule
IngressClassName string
}
// IngressTLS represents ingress TLS spec in k8s.
@@ -130,12 +131,13 @@ func matchesHostPattern(pattern, host string) bool {
func getLabelsForIngressPath(ig *Ingress, scheme, host, path string) map[string]string {
m := map[string]string{
"__address__": host,
"__meta_kubernetes_namespace": ig.Metadata.Namespace,
"__meta_kubernetes_ingress_name": ig.Metadata.Name,
"__meta_kubernetes_ingress_scheme": scheme,
"__meta_kubernetes_ingress_host": host,
"__meta_kubernetes_ingress_path": path,
"__address__": host,
"__meta_kubernetes_namespace": ig.Metadata.Namespace,
"__meta_kubernetes_ingress_name": ig.Metadata.Name,
"__meta_kubernetes_ingress_scheme": scheme,
"__meta_kubernetes_ingress_host": host,
"__meta_kubernetes_ingress_path": path,
"__meta_kubernetes_ingress_class_name": ig.Spec.IngressClassName,
}
ig.Metadata.registerLabelsAndAnnotations("__meta_kubernetes_ingress", m)
return m

View File

@@ -78,7 +78,8 @@ func TestParseIngressListSuccess(t *testing.T) {
{
"host": "foobar"
}
]
],
"ingressClassName": "foo-class"
},
"status": {
"loadBalancer": {
@@ -107,11 +108,12 @@ func TestParseIngressListSuccess(t *testing.T) {
"__address__": "foobar",
"__meta_kubernetes_ingress_annotation_kubectl_kubernetes_io_last_applied_configuration": `{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"annotations":{},"name":"test-ingress","namespace":"default"},"spec":{"backend":{"serviceName":"testsvc","servicePort":80}}}` + "\n",
"__meta_kubernetes_ingress_annotationpresent_kubectl_kubernetes_io_last_applied_configuration": "true",
"__meta_kubernetes_ingress_host": "foobar",
"__meta_kubernetes_ingress_name": "test-ingress",
"__meta_kubernetes_ingress_path": "/",
"__meta_kubernetes_ingress_scheme": "http",
"__meta_kubernetes_namespace": "default",
"__meta_kubernetes_ingress_host": "foobar",
"__meta_kubernetes_ingress_name": "test-ingress",
"__meta_kubernetes_ingress_path": "/",
"__meta_kubernetes_ingress_scheme": "http",
"__meta_kubernetes_ingress_class_name": "foo-class",
"__meta_kubernetes_namespace": "default",
}),
}
if !areEqualLabelss(sortedLabelss, expectedLabelss) {

View File

@@ -585,9 +585,9 @@ func (sw *scrapeWork) scrapeStream(scrapeTimestamp, realTimestamp int64) error {
scrapeResponseSize.Update(float64(sbr.bodyLen))
up := 1
if err != nil {
if samplesScraped == 0 {
up = 0
}
// Mark the scrape as failed even if it already read and pushed some samples
// to remote storage. This makes the logic compatible with Prometheus.
up = 0
scrapesFailed.Inc()
}
seriesAdded := 0