app/vmagent: fixes azure service discovery pagination

Azure API response with link to the next page was incorrectly validate. Validation used url.Host header to match configure API URL.


https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6784
This commit is contained in:
Zhu Jiekun 2024-08-09 21:22:47 +08:00 committed by f41gh7
parent 229f8217a0
commit 49f63b2b9a
No known key found for this signature in database
GPG key ID: 4558311CF775EC72
4 changed files with 40 additions and 9 deletions

View file

@ -35,6 +35,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/): reduce memory usage when scraping targets with big response body. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6759). * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/): reduce memory usage when scraping targets with big response body. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6759).
* FEATURE: [vmbackup](https://docs.victoriametrics.com/vmbackup/), [vmrestore](https://docs.victoriametrics.com/vmrestore/), [vmbackupmanager](https://docs.victoriametrics.com/vmbackupmanager/): use exponential backoff for retries when uploading or downloading data from S3. This should reduce the number of failed uploads and downloads when S3 is temporarily unavailable. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6732). * FEATURE: [vmbackup](https://docs.victoriametrics.com/vmbackup/), [vmrestore](https://docs.victoriametrics.com/vmrestore/), [vmbackupmanager](https://docs.victoriametrics.com/vmbackupmanager/): use exponential backoff for retries when uploading or downloading data from S3. This should reduce the number of failed uploads and downloads when S3 is temporarily unavailable. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6732).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent/) fix service discovery of Azure Virtual Machines for response contains `nextLink`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6784).
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert): respect HTTP headers defined in [notifier configuration file](https://docs.victoriametrics.com/vmalert/#notifier-configuration-file) for each request to notifiers. Previously, this param was ignored by mistake. * BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert): respect HTTP headers defined in [notifier configuration file](https://docs.victoriametrics.com/vmalert/#notifier-configuration-file) for each request to notifiers. Previously, this param was ignored by mistake.
* BUGFIX: [stream aggregation](https://docs.victoriametrics.com/stream-aggregation/): correctly apply `-streamAggr.dropInputLabels` when global stream deduplication is enabled without `-streamAggr.config`. Previously, `-remoteWrite.streamAggr.dropInputLabels` was used instead. * BUGFIX: [stream aggregation](https://docs.victoriametrics.com/stream-aggregation/): correctly apply `-streamAggr.dropInputLabels` when global stream deduplication is enabled without `-streamAggr.config`. Previously, `-remoteWrite.streamAggr.dropInputLabels` was used instead.
* BUGFIX: [stream aggregation](https://docs.victoriametrics.com/stream-aggregation/): fix command-line flag `-remoteWrite.streamAggr.ignoreFirstIntervals` to accept multiple values and be applied per each corresponding `-remoteWrite.url`. Previously, this flag only could have been used globally for all URLs. * BUGFIX: [stream aggregation](https://docs.victoriametrics.com/stream-aggregation/): fix command-line flag `-remoteWrite.streamAggr.ignoreFirstIntervals` to accept multiple values and be applied per each corresponding `-remoteWrite.url`. Previously, this flag only could have been used globally for all URLs.

View file

@ -67,6 +67,9 @@ type apiConfig struct {
tokenLock sync.Mutex tokenLock sync.Mutex
token string token string
tokenExpireDeadline time.Time tokenExpireDeadline time.Time
// apiServerHost is only used for verifying the `nextLink` in response of the list API.
apiServerHost string
} }
type refreshTokenFunc func() (string, time.Duration, error) type refreshTokenFunc func() (string, time.Duration, error)
@ -114,8 +117,12 @@ func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot create client for %q: %w", env.ResourceManagerEndpoint, err) return nil, fmt.Errorf("cannot create client for %q: %w", env.ResourceManagerEndpoint, err)
} }
// It's already verified in discoveryutils.NewClient so no need to check err.
u, _ := url.Parse(c.APIServer())
cfg := &apiConfig{ cfg := &apiConfig{
c: c, c: c,
apiServerHost: u.Host,
port: port, port: port,
resourceGroup: sdc.ResourceGroup, resourceGroup: sdc.ResourceGroup,
subscriptionID: sdc.SubscriptionID, subscriptionID: sdc.SubscriptionID,

View file

@ -96,8 +96,8 @@ func visitAllAPIObjects(ac *apiConfig, apiURL string, cb func(data json.RawMessa
return fmt.Errorf("cannot parse nextLink from response %q: %w", lar.NextLink, err) return fmt.Errorf("cannot parse nextLink from response %q: %w", lar.NextLink, err)
} }
if nextURL.Host != "" && nextURL.Host != ac.c.APIServer() { if nextURL.Host != "" && nextURL.Host != ac.apiServerHost {
return fmt.Errorf("unexpected nextLink host %q, expecting %q", nextURL.Host, ac.c.APIServer()) return fmt.Errorf("unexpected nextLink host %q, expecting %q", nextURL.Host, ac.apiServerHost)
} }
nextLinkURI = nextURL.RequestURI() nextLinkURI = nextURL.RequestURI()

View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -13,6 +14,11 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils" "github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
) )
var (
testServerURL string
listAPICall int
)
func TestGetVirtualMachinesSuccess(t *testing.T) { func TestGetVirtualMachinesSuccess(t *testing.T) {
prettifyVMs := func(src []virtualMachine) string { prettifyVMs := func(src []virtualMachine) string {
var sb strings.Builder var sb strings.Builder
@ -41,39 +47,51 @@ func TestGetVirtualMachinesSuccess(t *testing.T) {
} }
return sb.String() return sb.String()
} }
f := func(name string, expectedVMs []virtualMachine, apiResponses [4]string) { f := func(name string, expectedVMs []virtualMachine, apiResponses [5]string) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch { switch {
// list vms response // list vms response
case strings.Contains(r.URL.Path, "/providers/Microsoft.Compute/virtualMachines"): case strings.Contains(r.URL.Path, "/providers/Microsoft.Compute/virtualMachines"):
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, apiResponses[0]) if listAPICall == 0 {
// with nextLink
apiResponse := strings.Replace(apiResponses[0], "{nextLinkPlaceHolder}", testServerURL+"/providers/Microsoft.Compute/virtualMachines", 1)
fmt.Fprint(w, apiResponse)
listAPICall++
} else {
// without nextLink
fmt.Fprint(w, apiResponses[1])
}
// list scaleSets response // list scaleSets response
case strings.Contains(r.URL.RequestURI(), "/providers/Microsoft.Compute/virtualMachineScaleSets?api-version=2022-03-01"): case strings.Contains(r.URL.RequestURI(), "/providers/Microsoft.Compute/virtualMachineScaleSets?api-version=2022-03-01"):
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, apiResponses[1]) fmt.Fprint(w, apiResponses[2])
// list scalesets vms response // list scalesets vms response
case strings.Contains(r.URL.Path, "/providers/Microsoft.Compute/virtualMachineScaleSets/{virtualMachineScaleSetName}/virtualMach"): case strings.Contains(r.URL.Path, "/providers/Microsoft.Compute/virtualMachineScaleSets/{virtualMachineScaleSetName}/virtualMach"):
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, apiResponses[2]) fmt.Fprint(w, apiResponses[3])
// nic response // nic response
case strings.Contains(r.URL.Path, "/networkInterfaces/"): case strings.Contains(r.URL.Path, "/networkInterfaces/"):
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, apiResponses[3]) fmt.Fprint(w, apiResponses[4])
default: default:
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "API path not found: %s", r.URL.Path) fmt.Fprintf(w, "API path not found: %s", r.URL.Path)
} }
})) }))
defer testServer.Close() defer testServer.Close()
testServerURL = testServer.URL
c, err := discoveryutils.NewClient(testServer.URL, nil, nil, nil, &promauth.HTTPClientConfig{}) c, err := discoveryutils.NewClient(testServer.URL, nil, nil, nil, &promauth.HTTPClientConfig{})
if err != nil { if err != nil {
t.Fatalf("unexpected error at client create: %s", err) t.Fatalf("unexpected error at client create: %s", err)
} }
u, _ := url.Parse(c.APIServer())
defer c.Stop() defer c.Stop()
ac := &apiConfig{ ac := &apiConfig{
c: c, c: c,
apiServerHost: u.Host,
subscriptionID: "some-id", subscriptionID: "some-id",
refreshToken: func() (string, time.Duration, error) { refreshToken: func() (string, time.Duration, error) {
return "auth-token", 0, nil return "auth-token", 0, nil
@ -102,7 +120,7 @@ func TestGetVirtualMachinesSuccess(t *testing.T) {
}, },
Tags: map[string]string{}, Tags: map[string]string{},
}, },
}, [4]string{ }, [5]string{
` `
{ {
"value": [ "value": [
@ -193,6 +211,10 @@ func TestGetVirtualMachinesSuccess(t *testing.T) {
"name": "{virtualMachineName}" "name": "{virtualMachineName}"
} }
], ],
"nextLink": "{nextLinkPlaceHolder}"
}`,
`{
"value": [],
"nextLink": "" "nextLink": ""
}`, }`,
`{}`, `{}`,
@ -255,7 +277,8 @@ func TestGetVirtualMachinesSuccess(t *testing.T) {
}, },
Tags: map[string]string{}, Tags: map[string]string{},
}, },
}, [4]string{ }, [5]string{
`{}`,
`{}`, `{}`,
`{ `{
"value": [ "value": [