mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
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:
parent
aafa9262c5
commit
7bb8853a5c
16 changed files with 968 additions and 2 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
|
|
76
lib/promscrape/discovery/ovhcloud/api.go
Normal file
76
lib/promscrape/discovery/ovhcloud/api.go
Normal 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
|
||||
}
|
35
lib/promscrape/discovery/ovhcloud/api_test.go
Normal file
35
lib/promscrape/discovery/ovhcloud/api_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
103
lib/promscrape/discovery/ovhcloud/common.go
Normal file
103
lib/promscrape/discovery/ovhcloud/common.go
Normal 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
|
||||
}
|
152
lib/promscrape/discovery/ovhcloud/dedicated_server.go
Normal file
152
lib/promscrape/discovery/ovhcloud/dedicated_server.go
Normal 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
|
||||
}
|
99
lib/promscrape/discovery/ovhcloud/dedicated_server_test.go
Normal file
99
lib/promscrape/discovery/ovhcloud/dedicated_server_test.go
Normal 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"
|
||||
}
|
||||
}`)
|
40
lib/promscrape/discovery/ovhcloud/mock_server_test.go
Normal file
40
lib/promscrape/discovery/ovhcloud/mock_server_test.go
Normal 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)
|
||||
}
|
54
lib/promscrape/discovery/ovhcloud/ovhcloud.go
Normal file
54
lib/promscrape/discovery/ovhcloud/ovhcloud.go
Normal 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()
|
||||
}
|
||||
}
|
22
lib/promscrape/discovery/ovhcloud/ovhcloud_test.go
Normal file
22
lib/promscrape/discovery/ovhcloud/ovhcloud_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
174
lib/promscrape/discovery/ovhcloud/vps.go
Normal file
174
lib/promscrape/discovery/ovhcloud/vps.go
Normal 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
|
||||
}
|
104
lib/promscrape/discovery/ovhcloud/vps_test.go
Normal file
104
lib/promscrape/discovery/ovhcloud/vps_test.go
Normal 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"
|
||||
}
|
||||
}`)
|
|
@ -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() })
|
||||
|
|
Loading…
Reference in a new issue