From 7bb8853a5c3042ec982d188bad7f9889cfb98283 Mon Sep 17 00:00:00 2001 From: Zhu Jiekun Date: Mon, 30 Sep 2024 20:42:46 +0800 Subject: [PATCH] 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. image image --- Signed-off-by: Jiekun Co-authored-by: hagen1778 --- docs/README.md | 2 + docs/changelog/CHANGELOG.md | 1 + docs/sd_configs.md | 87 +++++++++ docs/vmagent.md | 4 +- lib/promscrape/config.go | 15 ++ lib/promscrape/discovery/ovhcloud/api.go | 76 ++++++++ lib/promscrape/discovery/ovhcloud/api_test.go | 35 ++++ lib/promscrape/discovery/ovhcloud/common.go | 103 +++++++++++ .../discovery/ovhcloud/dedicated_server.go | 152 +++++++++++++++ .../ovhcloud/dedicated_server_test.go | 99 ++++++++++ .../discovery/ovhcloud/mock_server_test.go | 40 ++++ lib/promscrape/discovery/ovhcloud/ovhcloud.go | 54 ++++++ .../discovery/ovhcloud/ovhcloud_test.go | 22 +++ lib/promscrape/discovery/ovhcloud/vps.go | 174 ++++++++++++++++++ lib/promscrape/discovery/ovhcloud/vps_test.go | 104 +++++++++++ lib/promscrape/scraper.go | 2 + 16 files changed, 968 insertions(+), 2 deletions(-) create mode 100644 lib/promscrape/discovery/ovhcloud/api.go create mode 100644 lib/promscrape/discovery/ovhcloud/api_test.go create mode 100644 lib/promscrape/discovery/ovhcloud/common.go create mode 100644 lib/promscrape/discovery/ovhcloud/dedicated_server.go create mode 100644 lib/promscrape/discovery/ovhcloud/dedicated_server_test.go create mode 100644 lib/promscrape/discovery/ovhcloud/mock_server_test.go create mode 100644 lib/promscrape/discovery/ovhcloud/ovhcloud.go create mode 100644 lib/promscrape/discovery/ovhcloud/ovhcloud_test.go create mode 100644 lib/promscrape/discovery/ovhcloud/vps.go create mode 100644 lib/promscrape/discovery/ovhcloud/vps_test.go diff --git a/docs/README.md b/docs/README.md index 91168c8ee..b82f018d1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 diff --git a/docs/changelog/CHANGELOG.md b/docs/changelog/CHANGELOG.md index 3e332c86f..41a918a2f 100644 --- a/docs/changelog/CHANGELOG.md +++ b/docs/changelog/CHANGELOG.md @@ -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) diff --git a/docs/sd_configs.md b/docs/sd_configs.md index 5d43206c2..e7b81e5e7 100644 --- a/docs/sd_configs.md +++ b/docs/sd_configs.md @@ -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 `` address or `` 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. diff --git a/docs/vmagent.md b/docs/vmagent.md index 72e71f131..794d4bbbc 100644 --- a/docs/vmagent.md +++ b/docs/vmagent.md @@ -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 diff --git a/lib/promscrape/config.go b/lib/promscrape/config.go index 0b4af38d9..9221d7f43 100644 --- a/lib/promscrape/config.go +++ b/lib/promscrape/config.go @@ -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)) { diff --git a/lib/promscrape/discovery/ovhcloud/api.go b/lib/promscrape/discovery/ovhcloud/api.go new file mode 100644 index 000000000..c5fbc2ca9 --- /dev/null +++ b/lib/promscrape/discovery/ovhcloud/api.go @@ -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 +} diff --git a/lib/promscrape/discovery/ovhcloud/api_test.go b/lib/promscrape/discovery/ovhcloud/api_test.go new file mode 100644 index 000000000..85c23afd7 --- /dev/null +++ b/lib/promscrape/discovery/ovhcloud/api_test.go @@ -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) + } + }) +} diff --git a/lib/promscrape/discovery/ovhcloud/common.go b/lib/promscrape/discovery/ovhcloud/common.go new file mode 100644 index 000000000..e46500cc3 --- /dev/null +++ b/lib/promscrape/discovery/ovhcloud/common.go @@ -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 +} diff --git a/lib/promscrape/discovery/ovhcloud/dedicated_server.go b/lib/promscrape/discovery/ovhcloud/dedicated_server.go new file mode 100644 index 000000000..7ed860418 --- /dev/null +++ b/lib/promscrape/discovery/ovhcloud/dedicated_server.go @@ -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 +} diff --git a/lib/promscrape/discovery/ovhcloud/dedicated_server_test.go b/lib/promscrape/discovery/ovhcloud/dedicated_server_test.go new file mode 100644 index 000000000..c95ff1df9 --- /dev/null +++ b/lib/promscrape/discovery/ovhcloud/dedicated_server_test.go @@ -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" + } +}`) diff --git a/lib/promscrape/discovery/ovhcloud/mock_server_test.go b/lib/promscrape/discovery/ovhcloud/mock_server_test.go new file mode 100644 index 000000000..0b037da71 --- /dev/null +++ b/lib/promscrape/discovery/ovhcloud/mock_server_test.go @@ -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) +} diff --git a/lib/promscrape/discovery/ovhcloud/ovhcloud.go b/lib/promscrape/discovery/ovhcloud/ovhcloud.go new file mode 100644 index 000000000..32fd372a0 --- /dev/null +++ b/lib/promscrape/discovery/ovhcloud/ovhcloud.go @@ -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() + } +} diff --git a/lib/promscrape/discovery/ovhcloud/ovhcloud_test.go b/lib/promscrape/discovery/ovhcloud/ovhcloud_test.go new file mode 100644 index 000000000..b5fc85227 --- /dev/null +++ b/lib/promscrape/discovery/ovhcloud/ovhcloud_test.go @@ -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) + } + }) +} diff --git a/lib/promscrape/discovery/ovhcloud/vps.go b/lib/promscrape/discovery/ovhcloud/vps.go new file mode 100644 index 000000000..62ab6ca92 --- /dev/null +++ b/lib/promscrape/discovery/ovhcloud/vps.go @@ -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 +} diff --git a/lib/promscrape/discovery/ovhcloud/vps_test.go b/lib/promscrape/discovery/ovhcloud/vps_test.go new file mode 100644 index 000000000..a80b6a583 --- /dev/null +++ b/lib/promscrape/discovery/ovhcloud/vps_test.go @@ -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" + } +}`) diff --git a/lib/promscrape/scraper.go b/lib/promscrape/scraper.go index d60511b8c..b34cf1592 100644 --- a/lib/promscrape/scraper.go +++ b/lib/promscrape/scraper.go @@ -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() })