Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for proxy and devapp monitored resources #189

Merged
merged 15 commits into from
Oct 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48 // indirect
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
github.com/golang/protobuf v1.3.2
github.com/google/go-cmp v0.3.0
github.com/googleapis/gnostic v0.3.0 // indirect
github.com/gophercloud/gophercloud v0.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
Expand Down
74 changes: 61 additions & 13 deletions retrieval/resource_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func constValue(labelName string) labelTranslation {
type ResourceMap struct {
// The name of the Stackdriver MonitoredResource.
Type string
// MatchLabel must exist in the set of Prometheus labels in order for this map to match. Ignored if empty.
MatchLabel string
// Mapping from Prometheus to Stackdriver labels
LabelMap map[string]labelTranslation
}
Expand Down Expand Up @@ -88,8 +90,37 @@ var GKEResourceMap = ResourceMap{
},
}

// TODO(jkohen): ensure these are sorted from more specific to less specific.
var ResourceMappings = []ResourceMap{
var DevappResourceMap = ResourceMap{
Type: "devapp",
MatchLabel: "__meta_kubernetes_pod_label_type_devapp",
LabelMap: map[string]labelTranslation{
ProjectIDLabel: constValue("resource_container"),
KubernetesLocationLabel: constValue("location"),
"__meta_kubernetes_pod_label_org": constValue("org"),
"__meta_kubernetes_pod_label_env": constValue("env"),
"api_product_name": constValue("api_product_name"),
},
}

var ProxyResourceMap = ResourceMap{
Type: "proxy",
MatchLabel: "__meta_kubernetes_pod_label_type_proxy",
LabelMap: map[string]labelTranslation{
ProjectIDLabel: constValue("resource_container"),
KubernetesLocationLabel: constValue("location"),
"__meta_kubernetes_pod_label_org": constValue("org"),
"__meta_kubernetes_pod_label_env": constValue("env"),
"proxy_name": constValue("proxy_name"),
"revision": constValue("revision"),
},
}

type ResourceMapList []ResourceMap

// When you add new elements, you also probably want to update TestResourceMappingsOrder.
var ResourceMappings = ResourceMapList{
ProxyResourceMap,
DevappResourceMap,
{
Type: "k8s_container",
LabelMap: map[string]labelTranslation{
Expand Down Expand Up @@ -134,40 +165,57 @@ var ResourceMappings = []ResourceMap{
},
}

func (m *ResourceMap) Translate(discovered, final labels.Labels) map[string]string {
stackdriverLabels := m.tryTranslate(discovered, final)
// Translate translates labels to a monitored resource and entry labels, if
// possible. Returns the resource and the modified entry labels.
//
// The labels in `discovered` and `entryLabels` are used as input. If a label
// exists in both sets, the one in `entryLabels` takes precedence. Whenever a
// label from `entryLabels` is used, it is removed from the set that is
// returned.
func (m *ResourceMap) Translate(discovered, entryLabels labels.Labels) (map[string]string, labels.Labels) {
stackdriverLabels, entryLabels := m.tryTranslate(discovered, entryLabels)
if len(m.LabelMap) == len(stackdriverLabels) {
return stackdriverLabels
return stackdriverLabels, entryLabels
}
return nil
return nil, nil
}

// BestEffortTranslate translates labels to resource with best effort. If the resource label
// cannot be filled, use empty string instead.
func (m *ResourceMap) BestEffortTranslate(discovered, final labels.Labels) map[string]string {
stackdriverLabels := m.tryTranslate(discovered, final)
func (m *ResourceMap) BestEffortTranslate(discovered, entryLabels labels.Labels) (map[string]string, labels.Labels) {
stackdriverLabels, entryLabels := m.tryTranslate(discovered, entryLabels)
for _, t := range m.LabelMap {
if _, ok := stackdriverLabels[t.stackdriverLabelName]; !ok {
stackdriverLabels[t.stackdriverLabelName] = ""
}
}
return stackdriverLabels
return stackdriverLabels, entryLabels
}

func (m *ResourceMap) tryTranslate(discovered, final labels.Labels) map[string]string {
func (m *ResourceMap) tryTranslate(discovered, entryLabels labels.Labels) (map[string]string, labels.Labels) {
matched := false
stackdriverLabels := make(map[string]string, len(m.LabelMap))
for _, l := range discovered {
if l.Name == m.MatchLabel {
StevenYCChou marked this conversation as resolved.
Show resolved Hide resolved
matched = true
}
if translator, ok := m.LabelMap[l.Name]; ok {
stackdriverLabels[translator.stackdriverLabelName] = translator.convert(l.Value)
}
}
// The final labels are applied second so they overwrite mappings from discovered labels.
// The entryLabels labels are applied second so they overwrite mappings from discovered labels.
// This ensures, that the Prometheus's relabeling rules are respected for labels that
// appear in both label sets, e.g. the "job" label for generic resources.
for _, l := range final {
var finalLabels labels.Labels
for _, l := range entryLabels {
if translator, ok := m.LabelMap[l.Name]; ok {
stackdriverLabels[translator.stackdriverLabelName] = translator.convert(l.Value)
} else {
finalLabels = append(finalLabels, l)
}
}
return stackdriverLabels
if len(m.MatchLabel) > 0 && !matched {
return nil, finalLabels
}
return stackdriverLabels, finalLabels
}
166 changes: 159 additions & 7 deletions retrieval/resource_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import (
"reflect"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/prometheus/prometheus/pkg/labels"
)

func TestTranslate(t *testing.T) {
r := ResourceMap{
Type: "my_type",
Type: "my_type",
MatchLabel: "__match_type",
LabelMap: map[string]labelTranslation{
"__target1": constValue("sdt1"),
"__target2": constValue("sdt2"),
Expand All @@ -33,29 +35,40 @@ func TestTranslate(t *testing.T) {
noMatchTarget := labels.Labels{
{"ignored", "x"},
{"__target2", "y"},
{"__match_type", "true"},
}
if labels := r.Translate(noMatchTarget, nil); labels != nil {
if labels, _ := r.Translate(noMatchTarget, nil); labels != nil {
t.Errorf("Expected no match, matched %v", labels)
}
matchTargetDiscovered := labels.Labels{
{"ignored", "x"},
{"__target2", "y"},
{"__target1", "z"},
{"__match_type", "true"},
}
matchTargetFinal := labels.Labels{
{"__target1", "z2"},
{"__target3", "v"},
{"__match_type", "true"},
}
expectedLabels := map[string]string{
"sdt1": "z2",
"sdt2": "y",
"sdt3": "v",
}
if labels := r.Translate(matchTargetDiscovered, matchTargetFinal); labels == nil {
if labels, _ := r.Translate(matchTargetDiscovered, matchTargetFinal); labels == nil {
t.Errorf("Expected %v, actual nil", expectedLabels)
} else if !reflect.DeepEqual(labels, expectedLabels) {
t.Errorf("Expected %v, actual %v", expectedLabels, labels)
}
missingType := labels.Labels{
{"__target1", "x"},
{"__target2", "y"},
{"__target3", "z"},
}
if labels, _ := r.Translate(missingType, nil); labels != nil {
t.Errorf("Expected no match, matched %v", labels)
}
}

func TestTranslateEc2Instance(t *testing.T) {
Expand All @@ -71,7 +84,7 @@ func TestTranslateEc2Instance(t *testing.T) {
"region": "aws:us-east-1b",
"aws_account": "12345678",
}
if labels := EC2ResourceMap.Translate(target, nil); labels == nil {
if labels, _ := EC2ResourceMap.Translate(target, nil); labels == nil {
t.Errorf("Expected %v, actual nil", expectedLabels)
} else if !reflect.DeepEqual(labels, expectedLabels) {
t.Errorf("Expected %v, actual %v", expectedLabels, labels)
Expand All @@ -89,7 +102,7 @@ func TestTranslateGceInstance(t *testing.T) {
"zone": "us-central1-a",
"instance_id": "1234110975759588",
}
if labels := GCEResourceMap.Translate(target, nil); labels == nil {
if labels, _ := GCEResourceMap.Translate(target, nil); labels == nil {
t.Errorf("Expected %v, actual nil", expectedLabels)
} else if !reflect.DeepEqual(labels, expectedLabels) {
t.Errorf("Expected %v, actual %v", expectedLabels, labels)
Expand All @@ -111,13 +124,152 @@ func TestBestEffortTranslate(t *testing.T) {
"pod_id": "",
"container_name": "",
}
if labels := GKEResourceMap.BestEffortTranslate(target, nil); labels == nil {
if labels, _ := GKEResourceMap.BestEffortTranslate(target, nil); labels == nil {
t.Errorf("Expected %v, actual nil", expectedLabels)
} else if !reflect.DeepEqual(labels, expectedLabels) {
t.Errorf("Expected %v, actual %v", expectedLabels, labels)
}
}

func TestTranslateDevapp(t *testing.T) {
discoveredLabels := labels.Labels{
{"__meta_kubernetes_pod_label_type_devapp", "true"},
{ProjectIDLabel, "my-project"},
{KubernetesLocationLabel, "us-central1-a"},
{"__meta_kubernetes_pod_label_org", "my-org"},
{"__meta_kubernetes_pod_label_env", "my-env"},
}
metricLabels := labels.Labels{
{"api_product_name", "my-name"},
{"extra_label", "my-label"},
}
expectedLabels := map[string]string{
"resource_container": "my-project",
"location": "us-central1-a",
"org": "my-org",
"env": "my-env",
"api_product_name": "my-name",
}
expectedFinalLabels := labels.Labels{
{"extra_label", "my-label"},
}
if labels, finalLabels := DevappResourceMap.Translate(discoveredLabels, metricLabels); labels == nil {
t.Errorf("Expected %v, actual nil", expectedLabels)
} else {
if diff := cmp.Diff(expectedLabels, labels); len(diff) > 0 {
t.Error(diff)
}
if diff := cmp.Diff(expectedFinalLabels, finalLabels); len(diff) > 0 {
t.Error(diff)
}
}
}

func TestTranslateProxy(t *testing.T) {
discoveredLabels := labels.Labels{
{"__meta_kubernetes_pod_label_type_proxy", "true"},
{ProjectIDLabel, "my-project"},
{KubernetesLocationLabel, "us-central1-a"},
{"__meta_kubernetes_pod_label_org", "my-org"},
{"__meta_kubernetes_pod_label_env", "my-env"},
}
metricLabels := labels.Labels{
{"proxy_name", "my-name"},
{"revision", "my-revision"},
{"extra_label", "my-label"},
}
expectedLabels := map[string]string{
"resource_container": "my-project",
"location": "us-central1-a",
"org": "my-org",
"env": "my-env",
"proxy_name": "my-name",
"revision": "my-revision",
}
expectedFinalLabels := labels.Labels{
{"extra_label", "my-label"},
}
if labels, finalLabels := ProxyResourceMap.Translate(discoveredLabels, metricLabels); labels == nil {
t.Errorf("Expected %v, actual nil", expectedLabels)
} else {
if diff := cmp.Diff(expectedLabels, labels); len(diff) > 0 {
t.Error(diff)
}
if diff := cmp.Diff(expectedFinalLabels, finalLabels); len(diff) > 0 {
t.Error(diff)
}
}
}

func (m *ResourceMapList) getByType(t string) (*ResourceMap, bool) {
for _, m := range *m {
if m.Type == t {
return &m, true
}
}
return nil, false
}

func (m *ResourceMapList) matchType(matchLabels labels.Labels) string {
for _, m := range *m {
if lset, _ := m.Translate(matchLabels, nil); lset != nil {
return m.Type
}
}
return ""
}

func TestResourceMappingsOrder(t *testing.T) {
// For each pair of resource types on the input, ensure that the first
// one is picked if there are labels that match both. This guarantees
// that more specific resource types are picked, e.g. k8s_container before
// k8s_pod, and k8s_node before gce_instance.
cases := []struct {
first string // Higher priority.
second string // Lower priority.
}{
{"k8s_container", "k8s_pod"},
{"k8s_pod", "k8s_node"},
{"k8s_node", "gce_instance"},
{"k8s_node", "aws_ec2_instance"},
{"proxy", "k8s_container"},
{"devapp", "k8s_container"},
}
for _, c := range cases {
var (
first, second *ResourceMap
ok bool
)
if first, ok = ResourceMappings.getByType(c.first); !ok {
t.Fatalf("invalid test case, missing %v", c.first)
}
if second, ok = ResourceMappings.getByType(c.second); !ok {
t.Fatalf("invalid test case, missing %v", c.second)
}
// The values are uninteresting for this test.
combinedKeys := make(map[string]string)
for k, _ := range first.LabelMap {
combinedKeys[k] = ""
}
if len(first.MatchLabel) > 0 {
combinedKeys[first.MatchLabel] = ""
}
for k, _ := range second.LabelMap {
combinedKeys[k] = ""
}
if len(second.MatchLabel) > 0 {
combinedKeys[second.MatchLabel] = ""
}
combinedLabels := labels.FromMap(combinedKeys)
if match := ResourceMappings.matchType(combinedLabels); match != c.first {
t.Errorf("expected to match %v, got %v", c.first, match)
}
if match := ResourceMappings.matchType(combinedLabels); match == c.second {
t.Errorf("unexpected match %v", match)
}
}
}

func BenchmarkTranslate(b *testing.B) {
r := ResourceMap{
Type: "gke_container",
Expand Down Expand Up @@ -149,7 +301,7 @@ func BenchmarkTranslate(b *testing.B) {
b.ReportAllocs()

for i := 0; i < b.N; i++ {
if labels := r.Translate(discoveredLabels, finalLabels); labels == nil {
if labels, _ := r.Translate(discoveredLabels, finalLabels); labels == nil {
b.Fail()
}
}
Expand Down
Loading