feature: [vmagent] Add service discovery support for OVH Cloud VPS and dedicated server (#6160)

### Describe Your Changes
related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6071

#### Added
- Added service discovery support for OVH Cloud:
    - VPS.
    - Dedicated server.

#### Docs
- `CHANGELOG.md`, `sd_configs.md`, `vmagent.md` are updated.

#### Note
- Useful links: 
    - OVH Cloud VPS API: https://eu.api.ovh.com/console/#/vps~GET
- OVH Cloud Dedicated server API:
https://eu.api.ovh.com/console/#/dedicated/server~GET
    - OVH Cloud SDK: https://github.com/ovh/go-ovh
- Prometheus SD:
https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ovhcloud_sd_config

Tested on OVH Cloud VPS and dedicated server.
<img width="1722" alt="image"
src="https://github.com/VictoriaMetrics/VictoriaMetrics/assets/30280396/d3f0adc8-b0ef-423e-9379-8a9b9b0792ee">

<img width="1724" alt="image"
src="https://github.com/VictoriaMetrics/VictoriaMetrics/assets/30280396/18b5b730-3512-4fc0-8b2c-f2450ac550fd">

---
Signed-off-by: Jiekun <jiekun@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
This commit is contained in:
Zhu Jiekun 2024-09-30 20:42:46 +08:00 committed by GitHub
parent aafa9262c5
commit 7bb8853a5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 968 additions and 2 deletions

View file

@ -3061,6 +3061,8 @@ Pass `-help` to VictoriaMetrics in order to see the list of supported command-li
Interval for checking for changes in Nomad. This works only if nomad_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#nomad_sd_configs for details (default 30s)
-promscrape.openstackSDCheckInterval duration
Interval for checking for changes in openstack API server. This works only if openstack_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#openstack_sd_configs for details (default 30s)
-promscrape.ovhcloudSDCheckInterval duration
Interval for checking for changes in OVH Cloud VPS and dedicated server. This works only if ovhcloud_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#ovhcloud_sd_configs for details (default 30s)
-promscrape.seriesLimitPerTarget int
Optional limit on the number of unique time series a single scrape target can expose. See https://docs.victoriametrics.com/vmagent/#cardinality-limiter for more info
-promscrape.streamParse

View file

@ -33,6 +33,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
* FEATURE: [dashboards](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/dashboards) for VM single-node, cluster, vmalert, vmagent, VictoriaLogs: add `Go scheduling latency` panel to show the 99th quantile of Go goroutines scheduling. This panel should help identifying insufficient CPU resources for the service. It is especially useful if CPU gets throttled, which now should be visible on this panel.
* FEATURE: [alerts](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts-health.yml): add alerting rule to track the Go scheduling latency for goroutines. It should notify users if VM component doesn't have enough CPU to run or gets throttled.
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/) and [Single-node VictoriaMetrics](https://docs.victoriametrics.com/): hide jobs that contain only healthy targets when `show_only_unhealthy` filter is enabled. Before, jobs without unhealthy targets were still displayed on the page. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3536).
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add service discovery support for [OVH Cloud VPS](https://www.ovhcloud.com/en/vps/) and [OVH Cloud dedicated server](https://ovhcloud.com/en/bare-metal/). See [these docs](https://docs.victoriametrics.com/sd_configs/#ovhcloud_sd_configs) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6071).
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert): bump default values for sending data to `remoteWrite.url`: `remoteWrite.maxQueueSize` from `100_000` to `1_000_000`, `remoteWrite.maxBatchSize` from `1_000` to `10_000`, `remoteWrite.concurrency` from `1` to `4`. The new settings should improve remote write performance of vmalert with default settings.
* FEATURE: [vmbackupmanager](https://docs.victoriametrics.com/vmbackupmanager/): add API for creating/scheduling backups. See [documentation](https://docs.victoriametrics.com/vmbackupmanager/#api-methods)

View file

@ -30,6 +30,7 @@ supports the following Prometheus-compatible service discovery options for Prome
* `kuma_sd_configs` is for discovering and scraping [Kuma](https://kuma.io) targets. See [these docs](#kuma_sd_configs).
* `nomad_sd_configs` is for discovering and scraping targets registered in [HashiCorp Nomad](https://www.nomadproject.io/). See [these docs](#nomad_sd_configs).
* `openstack_sd_configs` is for discovering and scraping OpenStack targets. See [these docs](#openstack_sd_configs).
* `ovhcloud_sd_configs` is for discovering and scraping OVH Cloud VPS and dedicated server targets. See [these docs](#ovhcloud_sd_configs).
* `static_configs` is for scraping statically defined targets. See [these docs](#static_configs).
* `vultr_sd_configs` is for discovering and scraping [Vultr](https://www.vultr.com/) targets. See [these docs](#vultr_sd_configs).
* `yandexcloud_sd_configs` is for discovering and scraping [Yandex Cloud](https://cloud.yandex.com/en/) targets. See [these docs](#yandexcloud_sd_configs).
@ -1459,6 +1460,92 @@ One of the following `role` types can be configured to discover targets:
The list of discovered OpenStack targets is refreshed at the interval, which can be configured via `-promscrape.openstackSDCheckInterval` command-line flag.
## ovhcloud_sd_configs
_Available from [v1.104](https://docs.victoriametrics.com/changelog/#v11040) version._
OVH Cloud SD configuration allows retrieving scrape targets from [OVH Cloud VPS](https://www.ovhcloud.com/en/vps/)
and [OVH Cloud dedicated server](https://ovhcloud.com/en/bare-metal/).
Configuration example:
```yaml
scrape_configs:
- job_name: ovh_job
ovhcloud_sd_configs:
# (optional) depending on the API you want to use, you may set the endpoint to:
# `ovh-eu` for OVH Europe API (default).
# `ovh-us` for OVH US API.
# `ovh-ca` for OVH North-America API.
# `soyoustart-eu` for "So you Start Europe API".
# `soyoustart-ca` for "So you Start North America API".
# `kimsufi-eu` for Kimsufi Europe API.
# `kimsufi-ca` for Kimsufi North America API.
# See: https://github.com/ovh/go-ovh?tab=readme-ov-file#supported-apis
- endpoint: "..."
# (mandatory) application_key is a self generated tokens.
# create one by visiting: https://eu.api.ovh.com/createApp/
application_key: "..."
# (mandatory) application_secret holds the application secret key.
application_secret: "..."
# (mandatory) consumer_key holds the user/app specific token. It must have been validated before use.
consumer_key: "..."
# (mandatory) service could be either `vps` or `dedicated_server`.
service: "..."
# Additional HTTP API client options can be specified here.
# See https://docs.victoriametrics.com/sd_configs.html#http-api-client-options
```
Each discovered target has an [`__address__`](https://docs.victoriametrics.com/relabeling/#how-to-modify-scrape-urls-in-targets) label set to either `<ipv4>` address or `<ipv6>` address.
In addition, the `instance` label for the VPS/dedicated server will be set to the VPS/dedicated server name as retrieved from OVH Cloud API.
The following meta labels are available on discovered targets during [relabeling](https://docs.victoriametrics.com/vmagent.html#relabeling).
VPS:
* `__meta_ovhcloud_vps_cluster`: the cluster of the server.
* `__meta_ovhcloud_vps_datacenter`: the datacenter of the server.
* `__meta_ovhcloud_vps_disk`: the disk of the server.
* `__meta_ovhcloud_vps_display_name`: the display name of the server.
* `__meta_ovhcloud_vps_ipv4`: the IPv4 of the server.
* `__meta_ovhcloud_vps_ipv6`: the IPv6 of the server.
* `__meta_ovhcloud_vps_keymap`: the KVM keyboard layout of the server.
* `__meta_ovhcloud_vps_maximum_additional_ip`: the maximum additional IPs of the server.
* `__meta_ovhcloud_vps_memory_limit`: the memory limit of the server.
* `__meta_ovhcloud_vps_memory`: the memory of the server.
* `__meta_ovhcloud_vps_monitoring_ip_blocks`: the monitoring IP blocks of the server.
* `__meta_ovhcloud_vps_name`: the name of the server.
* `__meta_ovhcloud_vps_netboot_mode`: the netboot mode of the server.
* `__meta_ovhcloud_vps_offer_type`: the offer type of the server.
* `__meta_ovhcloud_vps_offer`: the offer of the server.
* `__meta_ovhcloud_vps_state`: the state of the server.
* `__meta_ovhcloud_vps_vcore`: the number of virtual cores of the server.
* `__meta_ovhcloud_vps_version`: the version of the server.
* `__meta_ovhcloud_vps_zone`: the zone of the server.
Dedicated servers:
* `__meta_ovhcloud_dedicated_server_commercial_range`: the commercial range of the server.
* `__meta_ovhcloud_dedicated_server_datacenter`: the datacenter of the server.
* `__meta_ovhcloud_dedicated_server_ipv4`: the IPv4 of the server.
* `__meta_ovhcloud_dedicated_server_ipv6`: the IPv6 of the server.
* `__meta_ovhcloud_dedicated_server_link_speed`: the link speed of the server.
* `__meta_ovhcloud_dedicated_server_name`: the name of the server.
* `__meta_ovhcloud_dedicated_server_no_intervention`: the [intervention](https://support.us.ovhcloud.com/hc/en-us/articles/27991435200147-FAQ-Interventions-and-Hardware-Replacement) of the server.
* `__meta_ovhcloud_dedicated_server_os`: the operating system of the server.
* `__meta_ovhcloud_dedicated_server_rack`: the rack of the server.
* `__meta_ovhcloud_dedicated_server_reverse`: the reverse DNS name of the server.
* `__meta_ovhcloud_dedicated_server_server_id`: the ID of the server.
* `__meta_ovhcloud_dedicated_server_state`: the state of the server.
* `__meta_ovhcloud_dedicated_server_support_level`: the support level of the server.
The list of discovered OVH Cloud targets is refreshed at the interval, which can be configured via `-promscrape.ovhcloudSDCheckInterval` command-line flag.
## static_configs
A static config allows specifying a list of targets and a common label set for them.

View file

@ -2006,8 +2006,8 @@ See the docs at https://docs.victoriametrics.com/vmagent/ .
Interval for checking for changes in Nomad. This works only if nomad_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#nomad_sd_configs for details (default 30s)
-promscrape.openstackSDCheckInterval duration
Interval for checking for changes in openstack API server. This works only if openstack_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#openstack_sd_configs for details (default 30s)
-promscrape.scrapeExemplars
Whether to enable scraping of exemplars from scrape targets.
-promscrape.ovhcloudSDCheckInterval duration
Interval for checking for changes in OVH Cloud VPS and dedicated server. This works only if ovhcloud_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#ovhcloud_sd_configs for details (default 30s)
-promscrape.seriesLimitPerTarget int
Optional limit on the number of unique time series a single scrape target can expose. See https://docs.victoriametrics.com/vmagent/#cardinality-limiter for more info
-promscrape.streamParse

View file

@ -37,6 +37,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/kuma"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/nomad"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/openstack"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/ovhcloud"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/vultr"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/yandexcloud"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
@ -312,6 +313,7 @@ type ScrapeConfig struct {
KumaSDConfigs []kuma.SDConfig `yaml:"kuma_sd_configs,omitempty"`
NomadSDConfigs []nomad.SDConfig `yaml:"nomad_sd_configs,omitempty"`
OpenStackSDConfigs []openstack.SDConfig `yaml:"openstack_sd_configs,omitempty"`
OVHCloudSDConfigs []ovhcloud.SDConfig `yaml:"ovhcloud_sd_configs,omitempty"`
StaticConfigs []StaticConfig `yaml:"static_configs,omitempty"`
VultrSDConfigs []vultr.SDConfig `yaml:"vultr_configs,omitempty"`
YandexCloudSDConfigs []yandexcloud.SDConfig `yaml:"yandexcloud_sd_configs,omitempty"`
@ -394,6 +396,9 @@ func (sc *ScrapeConfig) mustStop() {
for i := range sc.OpenStackSDConfigs {
sc.OpenStackSDConfigs[i].MustStop()
}
for i := range sc.OVHCloudSDConfigs {
sc.OVHCloudSDConfigs[i].MustStop()
}
for i := range sc.VultrSDConfigs {
sc.VultrSDConfigs[i].MustStop()
}
@ -757,6 +762,16 @@ func (cfg *Config) getOpenStackSDScrapeWork(prev []*ScrapeWork) []*ScrapeWork {
return cfg.getScrapeWorkGeneric(visitConfigs, "openstack_sd_config", prev)
}
// getOVHCloudSDScrapeWork returns `ovhcloud_sd_configs` ScrapeWork from cfg.
func (cfg *Config) getOVHCloudSDScrapeWork(prev []*ScrapeWork) []*ScrapeWork {
visitConfigs := func(sc *ScrapeConfig, visitor func(sdc targetLabelsGetter)) {
for i := range sc.OVHCloudSDConfigs {
visitor(&sc.OVHCloudSDConfigs[i])
}
}
return cfg.getScrapeWorkGeneric(visitConfigs, "ovhcloud_sd_config", prev)
}
// getVultrSDScrapeWork returns `vultr_sd_configs` ScrapeWork from cfg.
func (cfg *Config) getVultrSDScrapeWork(prev []*ScrapeWork) []*ScrapeWork {
visitConfigs := func(sc *ScrapeConfig, visitor func(sdc targetLabelsGetter)) {

View file

@ -0,0 +1,76 @@
package ovhcloud
import (
"fmt"
"sync/atomic"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
)
// mapping for endpoint names to their URI for external configuration
var availableEndpoints = map[string]string{
"ovh-eu": "https://eu.api.ovh.com/1.0",
"ovh-ca": "https://ca.api.ovh.com/1.0",
"ovh-us": "https://api.us.ovhcloud.com/1.0",
"kimsufi-eu": "https://eu.api.kimsufi.com/1.0",
"kimsufi-ca": "https://ca.api.kimsufi.com/1.0",
"soyoustart-eu": "https://eu.api.soyoustart.com/1.0",
"soyoustart-ca": "https://ca.api.soyoustart.com/1.0",
}
var configMap = discoveryutils.NewConfigMap()
type apiConfig struct {
client *discoveryutils.Client
applicationKey string `yaml:"application_key"`
applicationSecret string `yaml:"application_secret"`
consumerKey string `yaml:"consumer_key"`
// internal fields, for ovh auth
timeDelta atomic.Value
}
func getAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
v, err := configMap.Get(sdc, func() (interface{}, error) { return newAPIConfig(sdc, baseDir) })
if err != nil {
return nil, err
}
return v.(*apiConfig), nil
}
func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
if sdc.Endpoint == "" {
sdc.Endpoint = "ovh-eu"
}
apiServer, ok := availableEndpoints[sdc.Endpoint]
if !ok {
return nil, fmt.Errorf(
"unsupported `endpoint` for ovhcloud sd: %s, see: https://docs.victoriametrics.com/sd_configs/#ovhcloud_sd_configs",
sdc.Endpoint,
)
}
ac, err := sdc.HTTPClientConfig.NewConfig(baseDir)
if err != nil {
return nil, fmt.Errorf("cannot parse auth config: %w", err)
}
proxyAC, err := sdc.ProxyClientConfig.NewConfig(baseDir)
if err != nil {
return nil, fmt.Errorf("cannot parse proxy auth config: %w", err)
}
client, err := discoveryutils.NewClient(apiServer, ac, sdc.ProxyURL, proxyAC, &sdc.HTTPClientConfig)
if err != nil {
return nil, fmt.Errorf("cannot create HTTP client for %q: %w", apiServer, err)
}
return &apiConfig{
client: client,
applicationKey: sdc.ApplicationKey,
applicationSecret: sdc.ApplicationSecret.String(),
consumerKey: sdc.ConsumerKey.String(),
}, nil
}

View file

@ -0,0 +1,35 @@
package ovhcloud
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
)
func Test_newAPIConfig(t *testing.T) {
t.Run("normal case", func(t *testing.T) {
sdc := &SDConfig{
Endpoint: "ovh-ca",
ApplicationKey: "no-op",
ApplicationSecret: &promauth.Secret{S: "no-op"},
ConsumerKey: &promauth.Secret{S: "no-op"},
Service: "vps",
}
if _, err := newAPIConfig(sdc, ""); err != nil {
t.Fatalf("newAPIConfig got error: %v", err)
}
})
t.Run("incorrect endpoint", func(t *testing.T) {
sdc := &SDConfig{
Endpoint: "in-correct-endpoint",
ApplicationKey: "no-op",
ApplicationSecret: &promauth.Secret{S: "no-op"},
ConsumerKey: &promauth.Secret{S: "no-op"},
Service: "vps",
}
if _, err := newAPIConfig(sdc, ""); err == nil {
t.Fatalf("newAPIConfig want error, but error = %v", err)
}
})
}

View file

@ -0,0 +1,103 @@
package ovhcloud
import (
"crypto/sha1"
"fmt"
"net/http"
"net/netip"
"strconv"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
)
func getAuthHeaders(cfg *apiConfig, headers http.Header, endpoint, path string) (http.Header, error) {
timestamp := getOVHTimestamp(cfg)
headers = setGeneralHeaders(cfg, headers)
headers.Set("X-Ovh-Timestamp", strconv.FormatInt(timestamp, 10))
headers.Add("X-Ovh-Consumer", cfg.consumerKey)
h := sha1.New()
h.Write([]byte(fmt.Sprintf("%s+%s+%s+%s+%s+%d",
cfg.applicationSecret,
cfg.consumerKey,
"GET",
endpoint+path,
"", // no body contained in any service discovery request, so it's set to empty by default
timestamp,
)))
headers.Set("X-Ovh-Signature", fmt.Sprintf("$1$%x", h.Sum(nil)))
return headers, nil
}
func setGeneralHeaders(cfg *apiConfig, headers http.Header) http.Header {
headers.Set("X-Ovh-Application", cfg.applicationKey)
headers.Set("Accept", "application/json")
headers.Set("User-Agent", "github.com/VictoriaMetrics/VictoriaMetrics")
return headers
}
func getServerTime(cfg *apiConfig) (*time.Time, error) {
resp, err := cfg.client.GetAPIResponseWithReqParams("/auth/time", func(req *http.Request) {
req.Header = setGeneralHeaders(cfg, req.Header)
})
if err != nil {
return nil, fmt.Errorf("failed to get server time from /auth/time: %w", err)
}
ts, err := strconv.ParseInt(string(resp), 10, 0)
if err != nil {
return nil, fmt.Errorf("parse ovh response to timestamp failed: %w", err)
}
serverTime := time.Unix(ts, 0)
return &serverTime, nil
}
// getOVHTimestamp return the server timestamp which is required by X-Ovh-Timestamp header.
// The timestamp is calculated by now() - timeDelta, where timeDelta is retrieved from /auth/time API and stored in config.
// It returns now() when server timestamp is unknown.
func getOVHTimestamp(cfg *apiConfig) int64 {
d, ok := cfg.timeDelta.Load().(time.Duration)
if ok {
return time.Now().Add(-d).Unix()
}
ovhTime, err := getServerTime(cfg)
if err != nil {
logger.Warnf("cannot get OVH server time, err: %v. using current timestamp.", err)
return time.Now().Unix()
}
d = time.Since(*ovhTime)
cfg.timeDelta.Store(d)
return time.Now().Add(-d).Unix()
}
func parseIPList(ipList []string) ([]netip.Addr, error) {
var ipAddresses []netip.Addr
for _, ip := range ipList {
ipAddr, err := netip.ParseAddr(ip)
if err != nil {
ipPrefix, err := netip.ParsePrefix(ip)
if err != nil {
return nil, fmt.Errorf("could not parse IP addresses: %s", ip)
}
if ipPrefix.IsValid() {
netmask := ipPrefix.Bits()
if netmask != 32 {
continue
}
ipAddr = ipPrefix.Addr()
}
}
if ipAddr.IsValid() && !ipAddr.IsUnspecified() {
ipAddresses = append(ipAddresses, ipAddr)
}
}
if len(ipAddresses) == 0 {
return nil, fmt.Errorf("could not parse IP addresses from ip List: %v", ipList)
}
return ipAddresses, nil
}

View file

@ -0,0 +1,152 @@
package ovhcloud
import (
"encoding/json"
"fmt"
"net/http"
"net/netip"
"net/url"
"path"
"strconv"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
)
// dedicatedServer struct from API.
// IP addresses are fetched independently.
// See: https://eu.api.ovh.com/console/#/dedicated/server/%7BserviceName%7D~GET and getDedicatedServerDetails
type dedicatedServer struct {
State string `json:"state"`
IPs []netip.Addr
CommercialRange string `json:"commercialRange"`
LinkSpeed int `json:"linkSpeed"`
Rack string `json:"rack"`
NoIntervention bool `json:"noIntervention"`
Os string `json:"os"`
SupportLevel string `json:"supportLevel"`
ServerID int64 `json:"serverId"`
Reverse string `json:"reverse"`
Datacenter string `json:"datacenter"`
Name string `json:"name"`
}
// getDedicatedServerLabels get labels for dedicated servers.
func getDedicatedServerLabels(cfg *apiConfig) ([]*promutils.Labels, error) {
dedicatedServerList, err := getDedicatedServerList(cfg)
if err != nil {
return nil, err
}
// Attach properties to each VPS and compose vpsDetailedList
dedicatedServerDetailList := make([]dedicatedServer, 0, len(dedicatedServerList))
for _, dedicatedServerName := range dedicatedServerList {
dedicatedServer, err := getDedicatedServerDetails(cfg, dedicatedServerName)
if err != nil {
logger.Errorf("getDedicatedServerDetails for %s failed, err: %v", dedicatedServerName, err)
continue
}
dedicatedServerDetailList = append(dedicatedServerDetailList, *dedicatedServer)
}
ms := make([]*promutils.Labels, 0, len(dedicatedServerDetailList))
for _, server := range dedicatedServerDetailList {
// convert IPs into string and select default IP.
var ipv4, ipv6 string
for _, ip := range server.IPs {
if ip.Is4() {
ipv4 = ip.String()
}
if ip.Is6() {
ipv6 = ip.String()
}
}
defaultIP := ipv4
if defaultIP == "" {
defaultIP = ipv6
}
m := promutils.NewLabels(15)
m.Add("__address__", defaultIP)
m.Add("instance", server.Name)
m.Add("__meta_ovhcloud_dedicated_server_state", server.State)
m.Add("__meta_ovhcloud_dedicated_server_commercial_range", server.CommercialRange)
m.Add("__meta_ovhcloud_dedicated_server_link_speed", fmt.Sprintf("%d", server.LinkSpeed))
m.Add("__meta_ovhcloud_dedicated_server_rack", server.Rack)
m.Add("__meta_ovhcloud_dedicated_server_no_intervention", strconv.FormatBool(server.NoIntervention))
m.Add("__meta_ovhcloud_dedicated_server_os", server.Os)
m.Add("__meta_ovhcloud_dedicated_server_support_level", server.SupportLevel)
m.Add("__meta_ovhcloud_dedicated_server_server_id", fmt.Sprintf("%d", server.ServerID))
m.Add("__meta_ovhcloud_dedicated_server_reverse", server.Reverse)
m.Add("__meta_ovhcloud_dedicated_server_datacenter", server.Datacenter)
m.Add("__meta_ovhcloud_dedicated_server_name", server.Name)
m.Add("__meta_ovhcloud_dedicated_server_ipv4", ipv4)
m.Add("__meta_ovhcloud_dedicated_server_ipv6", ipv6)
ms = append(ms, m)
}
return ms, nil
}
// getVPSDetails get properties of a dedicated server.
// Also see: https://eu.api.ovh.com/console/#/dedicated/server/%7BserviceName%7D~GET
func getDedicatedServerDetails(cfg *apiConfig, dedicatedServerName string) (*dedicatedServer, error) {
// get properties.
reqPath := path.Join("/dedicated/server", url.QueryEscape(dedicatedServerName))
resp, err := cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
})
if err != nil {
return nil, fmt.Errorf("request %s error: %v", reqPath, err)
}
var dedicatedServerDetails dedicatedServer
if err = json.Unmarshal(resp, &dedicatedServerDetails); err != nil {
return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
}
// get IPs for this dedicated server.
// e.g. ["139.99.154.111","2402:1f00:8100:401::bb6"]
// Also see: https://eu.api.ovh.com/console/#/dedicated/server/%7BserviceName%7D/ips~GET
reqPath = path.Join(reqPath, "ips")
resp, err = cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
})
if err != nil {
return nil, fmt.Errorf("request %s error: %v", reqPath, err)
}
var ips []string
if err = json.Unmarshal(resp, &ips); err != nil {
return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
}
// handle different IP formats
parsedIPs, err := parseIPList(ips)
if err != nil {
return nil, err
}
dedicatedServerDetails.IPs = parsedIPs
return &dedicatedServerDetails, nil
}
// getDedicatedServerList list available services.
// example: ["ns0000000.ip-00-00-000.eu"]
// Also see: https://eu.api.ovh.com/console/#/dedicated/server~GET
func getDedicatedServerList(cfg *apiConfig) ([]string, error) {
var dedicatedServerList []string
reqPath := "/dedicated/server"
resp, err := cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
})
if err != nil {
return nil, fmt.Errorf("request %s error: %v", reqPath, err)
}
if err = json.Unmarshal(resp, &dedicatedServerList); err != nil {
return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
}
return dedicatedServerList, nil
}

View file

@ -0,0 +1,99 @@
package ovhcloud
import (
"errors"
"reflect"
"sync/atomic"
"testing"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
)
func Test_getDedicatedServerLabels(t *testing.T) {
mockSvr := newMockOVHCloudServer(func(path string) ([]byte, error) {
switch path {
case "/dedicated/server":
return []byte(`["ns0000000.ip-00-00-000.eu"]`), nil
case "/dedicated/server/ns0000000.ip-00-00-000.eu":
return mockDedicatedServerDetail, nil
case "/dedicated/server/ns0000000.ip-00-00-000.eu/ips":
return []byte(`["2001:40d0:302:8874::/64","50.75.126.113/32"]`), nil
default:
return []byte{}, errors.New("invalid request")
}
})
c, _ := discoveryutils.NewClient(mockSvr.URL, nil, nil, nil, &promauth.HTTPClientConfig{})
td := atomic.Value{}
td.Store(time.Duration(1))
cfg := &apiConfig{
client: c,
applicationKey: "",
applicationSecret: "",
consumerKey: "",
timeDelta: td,
}
expectLabels := &promutils.Labels{}
expectLabels.Add("__address__", "50.75.126.113")
expectLabels.Add("instance", "ns0000000.ip-00-00-000.eu")
expectLabels.Add("__meta_ovhcloud_dedicated_server_state", "ok")
expectLabels.Add("__meta_ovhcloud_dedicated_server_commercial_range", "RISE-3")
expectLabels.Add("__meta_ovhcloud_dedicated_server_link_speed", "1000")
expectLabels.Add("__meta_ovhcloud_dedicated_server_rack", "G000A00")
expectLabels.Add("__meta_ovhcloud_dedicated_server_no_intervention", "false")
expectLabels.Add("__meta_ovhcloud_dedicated_server_os", "centos7_64")
expectLabels.Add("__meta_ovhcloud_dedicated_server_support_level", "pro")
expectLabels.Add("__meta_ovhcloud_dedicated_server_server_id", "1000000")
expectLabels.Add("__meta_ovhcloud_dedicated_server_reverse", "ns0000000.ip-00-00-000.eu")
expectLabels.Add("__meta_ovhcloud_dedicated_server_datacenter", "gra2")
expectLabels.Add("__meta_ovhcloud_dedicated_server_name", "ns0000000.ip-00-00-000.eu")
expectLabels.Add("__meta_ovhcloud_dedicated_server_ipv4", "50.75.126.113")
expectLabels.Add("__meta_ovhcloud_dedicated_server_ipv6", "")
expect := []*promutils.Labels{
expectLabels,
}
result, err := getDedicatedServerLabels(cfg)
if err != nil {
t.Fatalf("getDedicatedServerLabels unexpected error: %v", err)
}
if !reflect.DeepEqual(expect, result) {
t.Fatalf("getDedicatedServerLabels incorrect, want: %v, got: %v", expect, result)
}
}
var mockDedicatedServerDetail = []byte(
`{
"name": "ns0000000.ip-00-00-000.eu",
"availabilityZone": "eu-west-gra-a",
"datacenter": "gra2",
"bootScript": null,
"linkSpeed": 1000,
"reverse": "ns0000000.ip-00-00-000.eu",
"serverId": 1000000,
"monitoring": false,
"rootDevice": null,
"noIntervention": false,
"newUpgradeSystem": true,
"rack": "G000A00",
"rescueSshKey": null,
"supportLevel": "pro",
"powerState": "poweron",
"commercialRange": "RISE-3",
"professionalUse": false,
"rescueMail": null,
"region": "eu-west-gra",
"bootId": 1,
"state": "ok",
"os": "centos7_64",
"ip": "50.75.126.113",
"iam": {
"displayName": "ns0000000.ip-00-00-000.eu",
"id": "000da00d-00d0-0b00-0000-00000a0000bd",
"urn": "urn:v1:eu:resource:dedicatedServer:ns0000000.ip-00-00-000.eu"
}
}`)

View file

@ -0,0 +1,40 @@
package ovhcloud
import (
"fmt"
"net/http"
"net/http/httptest"
)
func newMockOVHCloudServer(jsonResponse func(path string) ([]byte, error)) *ovhcloudServer {
rw := &ovhcloudServer{}
rw.Server = httptest.NewServer(http.HandlerFunc(rw.handler))
rw.jsonResponse = jsonResponse
return rw
}
type ovhcloudServer struct {
*httptest.Server
jsonResponse func(path string) ([]byte, error)
}
func (rw *ovhcloudServer) err(w http.ResponseWriter, err error) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
}
func (rw *ovhcloudServer) handler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
rw.err(w, fmt.Errorf("bad method %q", r.Method))
return
}
resp, err := rw.jsonResponse(r.RequestURI)
if err != nil {
rw.err(w, err)
return
}
w.Write(resp)
w.WriteHeader(http.StatusOK)
}

View file

@ -0,0 +1,54 @@
package ovhcloud
import (
"flag"
"fmt"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/proxy"
)
// SDCheckInterval defines interval for targets refresh.
var SDCheckInterval = flag.Duration("promscrape.ovhcloudSDCheckInterval", 30*time.Second, "Interval for checking for changes in OVH Cloud API. "+
"This works only if ovhcloud_sd_configs is configured in '-promscrape.config' file. "+
"See https://docs.victoriametrics.com/sd_configs/#ovhcloud_sd_configs for details")
// SDConfig is the configuration for OVH Cloud service discovery.
type SDConfig struct {
Endpoint string `yaml:"endpoint"`
ApplicationKey string `yaml:"application_key"`
ApplicationSecret *promauth.Secret `yaml:"application_secret"`
ConsumerKey *promauth.Secret `yaml:"consumer_key"`
Service string `yaml:"service"`
HTTPClientConfig promauth.HTTPClientConfig `yaml:",inline"`
ProxyURL *proxy.URL `yaml:"proxy_url,omitempty"`
ProxyClientConfig promauth.ProxyClientConfig `yaml:",inline"`
}
// GetLabels returns labels for OVH Cloud according to service discover config.
func (sdc *SDConfig) GetLabels(baseDir string) ([]*promutils.Labels, error) {
cfg, err := getAPIConfig(sdc, baseDir)
if err != nil {
return nil, fmt.Errorf("cannot get API config: %w", err)
}
switch sdc.Service {
case "dedicated_server":
return getDedicatedServerLabels(cfg)
case "vps":
return getVPSLabels(cfg)
default:
return nil, fmt.Errorf("skipping unexpected service=%q; only `dedicated_server` and `vps` are supported for now", sdc.Service)
}
}
// MustStop stops further usage for sdc.
func (sdc *SDConfig) MustStop() {
v := configMap.Delete(sdc)
if v != nil {
cfg := v.(*apiConfig)
cfg.client.Stop()
}
}

View file

@ -0,0 +1,22 @@
package ovhcloud
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
)
func Test_GetLabels(t *testing.T) {
t.Run("incorrect service", func(t *testing.T) {
sdc := &SDConfig{
Endpoint: "ovh-ca",
ApplicationKey: "no-op",
ApplicationSecret: &promauth.Secret{S: "no-op"},
ConsumerKey: &promauth.Secret{S: "no-op"},
Service: "incorrect service",
}
if _, err := sdc.GetLabels(""); err == nil {
t.Fatalf("newAPIConfig want err, got: %v", err)
}
})
}

View file

@ -0,0 +1,174 @@
package ovhcloud
import (
"encoding/json"
"fmt"
"net/http"
"net/netip"
"net/url"
"path"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
)
// vpsModel struct from API.
// See: https://eu.api.ovh.com/console/#/vps/%7BserviceName%7D~GET and getVPSDetails
type vpsModel struct {
MaximumAdditionalIP int `json:"maximumAdditionnalIp"`
Offer string `json:"offer"`
Datacenter []string `json:"datacenter"`
Vcore int `json:"vcore"`
Version string `json:"version"`
Name string `json:"name"`
Disk int `json:"disk"`
Memory int `json:"memory"`
}
// virtualPrivateServer struct from API.
// IP addresses are fetched independently.
type virtualPrivateServer struct {
IPs []netip.Addr
Zone string `json:"zone"`
Model vpsModel `json:"model"`
DisplayName string `json:"displayName"`
Cluster string `json:"cluster"`
State string `json:"state"`
Name string `json:"name"`
NetbootMode string `json:"netbootMode"`
MemoryLimit int `json:"memoryLimit"`
OfferType string `json:"offerType"`
Vcore int `json:"vcore"`
// The following fields are defined in the response but are not used during service discovery.
//Keymap []string `json:"keymap"`
//MonitoringIPBlocks []string `json:"monitoringIpBlocks"`
}
// getVPSLabels get labels for VPS.
func getVPSLabels(cfg *apiConfig) ([]*promutils.Labels, error) {
vpsList, err := getVPSList(cfg)
if err != nil {
return nil, err
}
// Attach properties to each VPS and compose vpsDetailedList
vpsDetailedList := make([]virtualPrivateServer, 0, len(vpsList))
for _, vpsName := range vpsList {
vpsDetailed, err := getVPSDetails(cfg, vpsName)
if err != nil {
logger.Errorf("getVPSDetails for %s failed, err: %v", vpsName, err)
continue
}
vpsDetailedList = append(vpsDetailedList, *vpsDetailed)
}
ms := make([]*promutils.Labels, 0, len(vpsDetailedList))
for _, server := range vpsDetailedList {
// convert IPs into string and select default IP.
var ipv4, ipv6 string
for _, ip := range server.IPs {
if ip.Is4() {
ipv4 = ip.String()
}
if ip.Is6() {
ipv6 = ip.String()
}
}
defaultIP := ipv4
if defaultIP == "" {
defaultIP = ipv6
}
m := promutils.NewLabels(21)
m.Add("__address__", defaultIP)
m.Add("instance", server.Name)
m.Add("__meta_ovhcloud_vps_offer", server.Model.Offer)
m.Add("__meta_ovhcloud_vps_datacenter", fmt.Sprintf("%+v", server.Model.Datacenter))
m.Add("__meta_ovhcloud_vps_model_vcore", fmt.Sprintf("%d", server.Model.Vcore))
m.Add("__meta_ovhcloud_vps_maximum_additional_ip", fmt.Sprintf("%d", server.Model.MaximumAdditionalIP))
m.Add("__meta_ovhcloud_vps_version", server.Model.Version)
m.Add("__meta_ovhcloud_vps_model_name", server.Model.Name)
m.Add("__meta_ovhcloud_vps_disk", fmt.Sprintf("%d", server.Model.Disk))
m.Add("__meta_ovhcloud_vps_memory", fmt.Sprintf("%d", server.Model.Memory))
m.Add("__meta_ovhcloud_vps_zone", server.Zone)
m.Add("__meta_ovhcloud_vps_display_name", server.DisplayName)
m.Add("__meta_ovhcloud_vps_cluster", server.Cluster)
m.Add("__meta_ovhcloud_vps_state", server.State)
m.Add("__meta_ovhcloud_vps_name", server.Name)
m.Add("__meta_ovhcloud_vps_netboot_mode", server.NetbootMode)
m.Add("__meta_ovhcloud_vps_memory_limit", fmt.Sprintf("%d", server.MemoryLimit))
m.Add("__meta_ovhcloud_vps_offer_type", server.OfferType)
m.Add("__meta_ovhcloud_vps_vcore", fmt.Sprintf("%d", server.Vcore))
m.Add("__meta_ovhcloud_vps_ipv4", ipv4)
m.Add("__meta_ovhcloud_vps_ipv6", ipv6)
ms = append(ms, m)
}
return ms, nil
}
// getVPSDetails get properties of a VPS.
// Also see: https://eu.api.ovh.com/console/#/vps/%7BserviceName%7D~GET
func getVPSDetails(cfg *apiConfig, vpsName string) (*virtualPrivateServer, error) {
// get properties.
reqPath := path.Join("/vps", url.QueryEscape(vpsName))
resp, err := cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
})
if err != nil {
return nil, fmt.Errorf("request %s error: %v", reqPath, err)
}
var vpsDetails virtualPrivateServer
if err = json.Unmarshal(resp, &vpsDetails); err != nil {
return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
}
// get IPs for this vps.
// e.g. ["139.99.154.111","2402:1f00:8100:401::bb6"]
// Also see: https://eu.api.ovh.com/console/#/vps/%7BserviceName%7D/ips~GET
reqPath = path.Join(reqPath, "ips")
resp, err = cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
})
if err != nil {
return nil, fmt.Errorf("request %s error: %v", reqPath, err)
}
var ips []string
if err = json.Unmarshal(resp, &ips); err != nil {
return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
}
// handle different IP formats
parsedIPs, err := parseIPList(ips)
if err != nil {
return nil, err
}
// attach to details
vpsDetails.IPs = parsedIPs
return &vpsDetails, nil
}
// getVPSList list available services.
// example: ["vps-000e0e00.vps.ovh.ca", "vps-000e0e01.vps.ovh.ca"]
// Also see: https://eu.api.ovh.com/console/#/vps~GET
func getVPSList(cfg *apiConfig) ([]string, error) {
reqPath := "/vps"
resp, err := cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
})
if err != nil {
return nil, fmt.Errorf("request %s error: %v", reqPath, err)
}
var vpsList []string
if err = json.Unmarshal(resp, &vpsList); err != nil {
return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
}
return vpsList, nil
}

View file

@ -0,0 +1,104 @@
package ovhcloud
import (
"errors"
"reflect"
"sync/atomic"
"testing"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
)
func Test_getVpsLabels(t *testing.T) {
mockSvr := newMockOVHCloudServer(func(path string) ([]byte, error) {
switch path {
case "/vps":
return []byte(`["vps-000e0e00.vps.ovh.ca"]`), nil
case "/vps/vps-000e0e00.vps.ovh.ca":
return mockVpsDetail, nil
case "/vps/vps-000e0e00.vps.ovh.ca/ips":
return []byte(`["139.99.154.158","2402:1f00:8100:401::bb6"]`), nil
default:
return []byte{}, errors.New("invalid request")
}
})
c, _ := discoveryutils.NewClient(mockSvr.URL, nil, nil, nil, &promauth.HTTPClientConfig{})
td := atomic.Value{}
td.Store(time.Duration(1))
cfg := &apiConfig{
client: c,
applicationKey: "",
applicationSecret: "",
consumerKey: "",
timeDelta: td,
}
expectLabels := &promutils.Labels{}
expectLabels.Add("__address__", "139.99.154.158")
expectLabels.Add("instance", "vps-000e0e00.vps.ovh.ca")
expectLabels.Add("__meta_ovhcloud_vps_offer", "VPS vps2020-starter-1-2-20")
expectLabels.Add("__meta_ovhcloud_vps_datacenter", "[]")
expectLabels.Add("__meta_ovhcloud_vps_model_vcore", "1")
expectLabels.Add("__meta_ovhcloud_vps_maximum_additional_ip", "16")
expectLabels.Add("__meta_ovhcloud_vps_version", "2019v1")
expectLabels.Add("__meta_ovhcloud_vps_model_name", "vps-starter-1-2-20")
expectLabels.Add("__meta_ovhcloud_vps_disk", "20")
expectLabels.Add("__meta_ovhcloud_vps_memory", "2048")
expectLabels.Add("__meta_ovhcloud_vps_zone", "Region OpenStack: os-syd2")
expectLabels.Add("__meta_ovhcloud_vps_display_name", "vps-000e0e00.vps.ovh.ca")
expectLabels.Add("__meta_ovhcloud_vps_cluster", "")
expectLabels.Add("__meta_ovhcloud_vps_state", "running")
expectLabels.Add("__meta_ovhcloud_vps_name", "vps-000e0e00.vps.ovh.ca")
expectLabels.Add("__meta_ovhcloud_vps_netboot_mode", "local")
expectLabels.Add("__meta_ovhcloud_vps_memory_limit", "2048")
expectLabels.Add("__meta_ovhcloud_vps_offer_type", "ssd")
expectLabels.Add("__meta_ovhcloud_vps_vcore", "1")
expectLabels.Add("__meta_ovhcloud_vps_ipv4", "139.99.154.158")
expectLabels.Add("__meta_ovhcloud_vps_ipv6", "2402:1f00:8100:401::bb6")
expect := []*promutils.Labels{
expectLabels,
}
result, err := getVPSLabels(cfg)
if err != nil {
t.Fatalf("getDedicatedServerLabels unexpected error: %v", err)
}
if !reflect.DeepEqual(expect, result) {
t.Fatalf("getDedicatedServerLabels incorrect, want: %v, got: %v", expect, result)
}
}
var mockVpsDetail = []byte(
`{
"model": {
"name": "vps-starter-1-2-20",
"offer": "VPS vps2020-starter-1-2-20",
"availableOptions": [],
"maximumAdditionnalIp": 16,
"version": "2019v1",
"datacenter": [],
"vcore": 1,
"memory": 2048,
"disk": 20
},
"netbootMode": "local",
"cluster": "",
"name": "vps-000e0e00.vps.ovh.ca",
"displayName": "vps-000e0e00.vps.ovh.ca",
"vcore": 1,
"monitoringIpBlocks": [],
"zone": "Region OpenStack: os-syd2",
"memoryLimit": 2048,
"offerType": "ssd",
"state": "running",
"keymap": null,
"slaMonitoring": false,
"iam": {
"id": "00ea0000-0b0f-0000-0000-e000000a0000",
"urn": "urn:v1:eu:resource:vps:vps-000e0e00.vps.ovh.ca"
}
}`)

View file

@ -30,6 +30,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/kuma"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/nomad"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/openstack"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/ovhcloud"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/vultr"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/yandexcloud"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
@ -141,6 +142,7 @@ func runScraper(configFile string, pushData func(at *auth.Token, wr *prompbmarsh
scs.add("kuma_sd_configs", *kuma.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getKumaSDScrapeWork(swsPrev) })
scs.add("nomad_sd_configs", *nomad.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getNomadSDScrapeWork(swsPrev) })
scs.add("openstack_sd_configs", *openstack.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getOpenStackSDScrapeWork(swsPrev) })
scs.add("ovhcloud_sd_configs", *ovhcloud.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getOVHCloudSDScrapeWork(swsPrev) })
scs.add("vultr_sd_configs", *vultr.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getVultrSDScrapeWork(swsPrev) })
scs.add("yandexcloud_sd_configs", *yandexcloud.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getYandexCloudSDScrapeWork(swsPrev) })
scs.add("static_configs", 0, func(cfg *Config, _ []*ScrapeWork) []*ScrapeWork { return cfg.getStaticScrapeWork() })