VictoriaMetrics/lib/promscrape/discovery/gce/instance.go
Aliaksandr Valialkin 86394b4179
lib/promscrape: optimize discoveryutils.SanitizeLabelName()
Cache sanitized label names and return them next time.
This reduces the number of allocations and speeds up the SanitizeLabelName()
function for common case when the number of unique label names is smaller than 100k
2022-08-27 00:18:19 +03:00

174 lines
5.2 KiB
Go

package gce
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
)
// getInstancesLabels returns labels for gce instances obtained from the given cfg
func getInstancesLabels(cfg *apiConfig) []map[string]string {
insts := getInstances(cfg)
var ms []map[string]string
for _, inst := range insts {
ms = inst.appendTargetLabels(ms, cfg.project, cfg.tagSeparator, cfg.port)
}
return ms
}
func getInstances(cfg *apiConfig) []Instance {
// Collect instances for each zone in parallel
type result struct {
zone string
insts []Instance
err error
}
ch := make(chan result, len(cfg.zones))
for _, zone := range cfg.zones {
go func(zone string) {
insts, err := getInstancesForProjectAndZone(cfg.client, cfg.project, zone, cfg.filter)
ch <- result{
zone: zone,
insts: insts,
err: err,
}
}(zone)
}
var insts []Instance
for range cfg.zones {
r := <-ch
if r.err != nil {
logger.Errorf("cannot collect instances from zone %q: %s", r.zone, r.err)
continue
}
insts = append(insts, r.insts...)
}
return insts
}
func getInstancesForProjectAndZone(client *http.Client, project, zone, filter string) ([]Instance, error) {
// See https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
instsURL := fmt.Sprintf("https://compute.googleapis.com/compute/v1/projects/%s/zones/%s/instances", project, zone)
var insts []Instance
pageToken := ""
for {
data, err := getAPIResponse(client, instsURL, filter, pageToken)
if err != nil {
return nil, fmt.Errorf("cannot obtain instances: %w", err)
}
il, err := parseInstanceList(data)
if err != nil {
return nil, fmt.Errorf("cannot parse instance list from %q: %w", instsURL, err)
}
insts = append(insts, il.Items...)
if len(il.NextPageToken) == 0 {
return insts, nil
}
pageToken = il.NextPageToken
}
}
// InstanceList is response to https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
type InstanceList struct {
Items []Instance
NextPageToken string
}
// Instance is instance from https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
type Instance struct {
ID string `json:"id"`
Name string
Status string
MachineType string
Zone string
NetworkInterfaces []NetworkInterface
Tags TagList
Metadata MetadataList
Labels discoveryutils.SortedLabels
}
// NetworkInterface is network interface from https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
type NetworkInterface struct {
Name string
Network string
Subnetwork string
NetworkIP string
AccessConfigs []AccessConfig
}
// AccessConfig is access config from https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
type AccessConfig struct {
Type string
NatIP string
}
// TagList is tag list from https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
type TagList struct {
Items []string
}
// MetadataList is metadataList from https://cloud.google.com/compute/docs/reference/rest/v1/instances/list
type MetadataList struct {
Items []MetadataEntry
}
// MetadataEntry is a single entry from metadata
type MetadataEntry struct {
Key string
Value string
}
// parseInstanceList parses InstanceList from data.
func parseInstanceList(data []byte) (*InstanceList, error) {
var il InstanceList
if err := json.Unmarshal(data, &il); err != nil {
return nil, fmt.Errorf("cannot unmarshal InstanceList from %q: %w", data, err)
}
return &il, nil
}
func (inst *Instance) appendTargetLabels(ms []map[string]string, project, tagSeparator string, port int) []map[string]string {
if len(inst.NetworkInterfaces) == 0 {
return ms
}
iface := inst.NetworkInterfaces[0]
addr := discoveryutils.JoinHostPort(iface.NetworkIP, port)
m := map[string]string{
"__address__": addr,
"__meta_gce_instance_id": inst.ID,
"__meta_gce_instance_status": inst.Status,
"__meta_gce_instance_name": inst.Name,
"__meta_gce_machine_type": inst.MachineType,
"__meta_gce_network": iface.Network,
"__meta_gce_private_ip": iface.NetworkIP,
"__meta_gce_project": project,
"__meta_gce_subnetwork": iface.Subnetwork,
"__meta_gce_zone": inst.Zone,
}
for _, iface := range inst.NetworkInterfaces {
m[discoveryutils.SanitizeLabelName("__meta_gce_interface_ipv4_"+iface.Name)] = iface.NetworkIP
}
if len(inst.Tags.Items) > 0 {
// We surround the separated list with the separator as well. This way regular expressions
// in relabeling rules don't have to consider tag positions.
m["__meta_gce_tags"] = tagSeparator + strings.Join(inst.Tags.Items, tagSeparator) + tagSeparator
}
for _, item := range inst.Metadata.Items {
m[discoveryutils.SanitizeLabelName("__meta_gce_metadata_"+item.Key)] = item.Value
}
for _, label := range inst.Labels {
m[discoveryutils.SanitizeLabelName("__meta_gce_label_"+label.Name)] = label.Value
}
if len(iface.AccessConfigs) > 0 {
ac := iface.AccessConfigs[0]
if ac.Type == "ONE_TO_ONE_NAT" {
m["__meta_gce_public_ip"] = ac.NatIP
}
}
ms = append(ms, m)
return ms
}