VictoriaMetrics/lib/promscrape/discovery/ovhcloud/vps.go
Zhu Jiekun d1d59d6348
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>
2024-09-30 15:06:14 +02:00

175 lines
5.8 KiB
Go

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
}