Skip to content

Commit 65ff039

Browse files
authored
Added support for proxy and devapp monitored resources (#189)
Implements matching of resource type based on metric labels.
1 parent ee77fae commit 65ff039

File tree

5 files changed

+288
-51
lines changed

5 files changed

+288
-51
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48 // indirect
2323
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
2424
github.com/golang/protobuf v1.3.2
25+
github.com/google/go-cmp v0.3.0
2526
github.com/googleapis/gnostic v0.3.0 // indirect
2627
github.com/gophercloud/gophercloud v0.3.0 // indirect
2728
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0

retrieval/resource_map.go

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ func constValue(labelName string) labelTranslation {
4242
type ResourceMap struct {
4343
// The name of the Stackdriver MonitoredResource.
4444
Type string
45+
// MatchLabel must exist in the set of Prometheus labels in order for this map to match. Ignored if empty.
46+
MatchLabel string
4547
// Mapping from Prometheus to Stackdriver labels
4648
LabelMap map[string]labelTranslation
4749
}
@@ -88,8 +90,37 @@ var GKEResourceMap = ResourceMap{
8890
},
8991
}
9092

91-
// TODO(jkohen): ensure these are sorted from more specific to less specific.
92-
var ResourceMappings = []ResourceMap{
93+
var DevappResourceMap = ResourceMap{
94+
Type: "devapp",
95+
MatchLabel: "__meta_kubernetes_pod_label_type_devapp",
96+
LabelMap: map[string]labelTranslation{
97+
ProjectIDLabel: constValue("resource_container"),
98+
KubernetesLocationLabel: constValue("location"),
99+
"__meta_kubernetes_pod_label_org": constValue("org"),
100+
"__meta_kubernetes_pod_label_env": constValue("env"),
101+
"api_product_name": constValue("api_product_name"),
102+
},
103+
}
104+
105+
var ProxyResourceMap = ResourceMap{
106+
Type: "proxy",
107+
MatchLabel: "__meta_kubernetes_pod_label_type_proxy",
108+
LabelMap: map[string]labelTranslation{
109+
ProjectIDLabel: constValue("resource_container"),
110+
KubernetesLocationLabel: constValue("location"),
111+
"__meta_kubernetes_pod_label_org": constValue("org"),
112+
"__meta_kubernetes_pod_label_env": constValue("env"),
113+
"proxy_name": constValue("proxy_name"),
114+
"revision": constValue("revision"),
115+
},
116+
}
117+
118+
type ResourceMapList []ResourceMap
119+
120+
// When you add new elements, you also probably want to update TestResourceMappingsOrder.
121+
var ResourceMappings = ResourceMapList{
122+
ProxyResourceMap,
123+
DevappResourceMap,
93124
{
94125
Type: "k8s_container",
95126
LabelMap: map[string]labelTranslation{
@@ -134,40 +165,57 @@ var ResourceMappings = []ResourceMap{
134165
},
135166
}
136167

137-
func (m *ResourceMap) Translate(discovered, final labels.Labels) map[string]string {
138-
stackdriverLabels := m.tryTranslate(discovered, final)
168+
// Translate translates labels to a monitored resource and entry labels, if
169+
// possible. Returns the resource and the modified entry labels.
170+
//
171+
// The labels in `discovered` and `entryLabels` are used as input. If a label
172+
// exists in both sets, the one in `entryLabels` takes precedence. Whenever a
173+
// label from `entryLabels` is used, it is removed from the set that is
174+
// returned.
175+
func (m *ResourceMap) Translate(discovered, entryLabels labels.Labels) (map[string]string, labels.Labels) {
176+
stackdriverLabels, entryLabels := m.tryTranslate(discovered, entryLabels)
139177
if len(m.LabelMap) == len(stackdriverLabels) {
140-
return stackdriverLabels
178+
return stackdriverLabels, entryLabels
141179
}
142-
return nil
180+
return nil, nil
143181
}
144182

145183
// BestEffortTranslate translates labels to resource with best effort. If the resource label
146184
// cannot be filled, use empty string instead.
147-
func (m *ResourceMap) BestEffortTranslate(discovered, final labels.Labels) map[string]string {
148-
stackdriverLabels := m.tryTranslate(discovered, final)
185+
func (m *ResourceMap) BestEffortTranslate(discovered, entryLabels labels.Labels) (map[string]string, labels.Labels) {
186+
stackdriverLabels, entryLabels := m.tryTranslate(discovered, entryLabels)
149187
for _, t := range m.LabelMap {
150188
if _, ok := stackdriverLabels[t.stackdriverLabelName]; !ok {
151189
stackdriverLabels[t.stackdriverLabelName] = ""
152190
}
153191
}
154-
return stackdriverLabels
192+
return stackdriverLabels, entryLabels
155193
}
156194

157-
func (m *ResourceMap) tryTranslate(discovered, final labels.Labels) map[string]string {
195+
func (m *ResourceMap) tryTranslate(discovered, entryLabels labels.Labels) (map[string]string, labels.Labels) {
196+
matched := false
158197
stackdriverLabels := make(map[string]string, len(m.LabelMap))
159198
for _, l := range discovered {
199+
if l.Name == m.MatchLabel {
200+
matched = true
201+
}
160202
if translator, ok := m.LabelMap[l.Name]; ok {
161203
stackdriverLabels[translator.stackdriverLabelName] = translator.convert(l.Value)
162204
}
163205
}
164-
// The final labels are applied second so they overwrite mappings from discovered labels.
206+
// The entryLabels labels are applied second so they overwrite mappings from discovered labels.
165207
// This ensures, that the Prometheus's relabeling rules are respected for labels that
166208
// appear in both label sets, e.g. the "job" label for generic resources.
167-
for _, l := range final {
209+
var finalLabels labels.Labels
210+
for _, l := range entryLabels {
168211
if translator, ok := m.LabelMap[l.Name]; ok {
169212
stackdriverLabels[translator.stackdriverLabelName] = translator.convert(l.Value)
213+
} else {
214+
finalLabels = append(finalLabels, l)
170215
}
171216
}
172-
return stackdriverLabels
217+
if len(m.MatchLabel) > 0 && !matched {
218+
return nil, finalLabels
219+
}
220+
return stackdriverLabels, finalLabels
173221
}

retrieval/resource_map_test.go

Lines changed: 159 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ import (
1717
"reflect"
1818
"testing"
1919

20+
"github.com/google/go-cmp/cmp"
2021
"github.com/prometheus/prometheus/pkg/labels"
2122
)
2223

2324
func TestTranslate(t *testing.T) {
2425
r := ResourceMap{
25-
Type: "my_type",
26+
Type: "my_type",
27+
MatchLabel: "__match_type",
2628
LabelMap: map[string]labelTranslation{
2729
"__target1": constValue("sdt1"),
2830
"__target2": constValue("sdt2"),
@@ -33,29 +35,40 @@ func TestTranslate(t *testing.T) {
3335
noMatchTarget := labels.Labels{
3436
{"ignored", "x"},
3537
{"__target2", "y"},
38+
{"__match_type", "true"},
3639
}
37-
if labels := r.Translate(noMatchTarget, nil); labels != nil {
40+
if labels, _ := r.Translate(noMatchTarget, nil); labels != nil {
3841
t.Errorf("Expected no match, matched %v", labels)
3942
}
4043
matchTargetDiscovered := labels.Labels{
4144
{"ignored", "x"},
4245
{"__target2", "y"},
4346
{"__target1", "z"},
47+
{"__match_type", "true"},
4448
}
4549
matchTargetFinal := labels.Labels{
4650
{"__target1", "z2"},
4751
{"__target3", "v"},
52+
{"__match_type", "true"},
4853
}
4954
expectedLabels := map[string]string{
5055
"sdt1": "z2",
5156
"sdt2": "y",
5257
"sdt3": "v",
5358
}
54-
if labels := r.Translate(matchTargetDiscovered, matchTargetFinal); labels == nil {
59+
if labels, _ := r.Translate(matchTargetDiscovered, matchTargetFinal); labels == nil {
5560
t.Errorf("Expected %v, actual nil", expectedLabels)
5661
} else if !reflect.DeepEqual(labels, expectedLabels) {
5762
t.Errorf("Expected %v, actual %v", expectedLabels, labels)
5863
}
64+
missingType := labels.Labels{
65+
{"__target1", "x"},
66+
{"__target2", "y"},
67+
{"__target3", "z"},
68+
}
69+
if labels, _ := r.Translate(missingType, nil); labels != nil {
70+
t.Errorf("Expected no match, matched %v", labels)
71+
}
5972
}
6073

6174
func TestTranslateEc2Instance(t *testing.T) {
@@ -71,7 +84,7 @@ func TestTranslateEc2Instance(t *testing.T) {
7184
"region": "aws:us-east-1b",
7285
"aws_account": "12345678",
7386
}
74-
if labels := EC2ResourceMap.Translate(target, nil); labels == nil {
87+
if labels, _ := EC2ResourceMap.Translate(target, nil); labels == nil {
7588
t.Errorf("Expected %v, actual nil", expectedLabels)
7689
} else if !reflect.DeepEqual(labels, expectedLabels) {
7790
t.Errorf("Expected %v, actual %v", expectedLabels, labels)
@@ -89,7 +102,7 @@ func TestTranslateGceInstance(t *testing.T) {
89102
"zone": "us-central1-a",
90103
"instance_id": "1234110975759588",
91104
}
92-
if labels := GCEResourceMap.Translate(target, nil); labels == nil {
105+
if labels, _ := GCEResourceMap.Translate(target, nil); labels == nil {
93106
t.Errorf("Expected %v, actual nil", expectedLabels)
94107
} else if !reflect.DeepEqual(labels, expectedLabels) {
95108
t.Errorf("Expected %v, actual %v", expectedLabels, labels)
@@ -111,13 +124,152 @@ func TestBestEffortTranslate(t *testing.T) {
111124
"pod_id": "",
112125
"container_name": "",
113126
}
114-
if labels := GKEResourceMap.BestEffortTranslate(target, nil); labels == nil {
127+
if labels, _ := GKEResourceMap.BestEffortTranslate(target, nil); labels == nil {
115128
t.Errorf("Expected %v, actual nil", expectedLabels)
116129
} else if !reflect.DeepEqual(labels, expectedLabels) {
117130
t.Errorf("Expected %v, actual %v", expectedLabels, labels)
118131
}
119132
}
120133

134+
func TestTranslateDevapp(t *testing.T) {
135+
discoveredLabels := labels.Labels{
136+
{"__meta_kubernetes_pod_label_type_devapp", "true"},
137+
{ProjectIDLabel, "my-project"},
138+
{KubernetesLocationLabel, "us-central1-a"},
139+
{"__meta_kubernetes_pod_label_org", "my-org"},
140+
{"__meta_kubernetes_pod_label_env", "my-env"},
141+
}
142+
metricLabels := labels.Labels{
143+
{"api_product_name", "my-name"},
144+
{"extra_label", "my-label"},
145+
}
146+
expectedLabels := map[string]string{
147+
"resource_container": "my-project",
148+
"location": "us-central1-a",
149+
"org": "my-org",
150+
"env": "my-env",
151+
"api_product_name": "my-name",
152+
}
153+
expectedFinalLabels := labels.Labels{
154+
{"extra_label", "my-label"},
155+
}
156+
if labels, finalLabels := DevappResourceMap.Translate(discoveredLabels, metricLabels); labels == nil {
157+
t.Errorf("Expected %v, actual nil", expectedLabels)
158+
} else {
159+
if diff := cmp.Diff(expectedLabels, labels); len(diff) > 0 {
160+
t.Error(diff)
161+
}
162+
if diff := cmp.Diff(expectedFinalLabels, finalLabels); len(diff) > 0 {
163+
t.Error(diff)
164+
}
165+
}
166+
}
167+
168+
func TestTranslateProxy(t *testing.T) {
169+
discoveredLabels := labels.Labels{
170+
{"__meta_kubernetes_pod_label_type_proxy", "true"},
171+
{ProjectIDLabel, "my-project"},
172+
{KubernetesLocationLabel, "us-central1-a"},
173+
{"__meta_kubernetes_pod_label_org", "my-org"},
174+
{"__meta_kubernetes_pod_label_env", "my-env"},
175+
}
176+
metricLabels := labels.Labels{
177+
{"proxy_name", "my-name"},
178+
{"revision", "my-revision"},
179+
{"extra_label", "my-label"},
180+
}
181+
expectedLabels := map[string]string{
182+
"resource_container": "my-project",
183+
"location": "us-central1-a",
184+
"org": "my-org",
185+
"env": "my-env",
186+
"proxy_name": "my-name",
187+
"revision": "my-revision",
188+
}
189+
expectedFinalLabels := labels.Labels{
190+
{"extra_label", "my-label"},
191+
}
192+
if labels, finalLabels := ProxyResourceMap.Translate(discoveredLabels, metricLabels); labels == nil {
193+
t.Errorf("Expected %v, actual nil", expectedLabels)
194+
} else {
195+
if diff := cmp.Diff(expectedLabels, labels); len(diff) > 0 {
196+
t.Error(diff)
197+
}
198+
if diff := cmp.Diff(expectedFinalLabels, finalLabels); len(diff) > 0 {
199+
t.Error(diff)
200+
}
201+
}
202+
}
203+
204+
func (m *ResourceMapList) getByType(t string) (*ResourceMap, bool) {
205+
for _, m := range *m {
206+
if m.Type == t {
207+
return &m, true
208+
}
209+
}
210+
return nil, false
211+
}
212+
213+
func (m *ResourceMapList) matchType(matchLabels labels.Labels) string {
214+
for _, m := range *m {
215+
if lset, _ := m.Translate(matchLabels, nil); lset != nil {
216+
return m.Type
217+
}
218+
}
219+
return ""
220+
}
221+
222+
func TestResourceMappingsOrder(t *testing.T) {
223+
// For each pair of resource types on the input, ensure that the first
224+
// one is picked if there are labels that match both. This guarantees
225+
// that more specific resource types are picked, e.g. k8s_container before
226+
// k8s_pod, and k8s_node before gce_instance.
227+
cases := []struct {
228+
first string // Higher priority.
229+
second string // Lower priority.
230+
}{
231+
{"k8s_container", "k8s_pod"},
232+
{"k8s_pod", "k8s_node"},
233+
{"k8s_node", "gce_instance"},
234+
{"k8s_node", "aws_ec2_instance"},
235+
{"proxy", "k8s_container"},
236+
{"devapp", "k8s_container"},
237+
}
238+
for _, c := range cases {
239+
var (
240+
first, second *ResourceMap
241+
ok bool
242+
)
243+
if first, ok = ResourceMappings.getByType(c.first); !ok {
244+
t.Fatalf("invalid test case, missing %v", c.first)
245+
}
246+
if second, ok = ResourceMappings.getByType(c.second); !ok {
247+
t.Fatalf("invalid test case, missing %v", c.second)
248+
}
249+
// The values are uninteresting for this test.
250+
combinedKeys := make(map[string]string)
251+
for k, _ := range first.LabelMap {
252+
combinedKeys[k] = ""
253+
}
254+
if len(first.MatchLabel) > 0 {
255+
combinedKeys[first.MatchLabel] = ""
256+
}
257+
for k, _ := range second.LabelMap {
258+
combinedKeys[k] = ""
259+
}
260+
if len(second.MatchLabel) > 0 {
261+
combinedKeys[second.MatchLabel] = ""
262+
}
263+
combinedLabels := labels.FromMap(combinedKeys)
264+
if match := ResourceMappings.matchType(combinedLabels); match != c.first {
265+
t.Errorf("expected to match %v, got %v", c.first, match)
266+
}
267+
if match := ResourceMappings.matchType(combinedLabels); match == c.second {
268+
t.Errorf("unexpected match %v", match)
269+
}
270+
}
271+
}
272+
121273
func BenchmarkTranslate(b *testing.B) {
122274
r := ResourceMap{
123275
Type: "gke_container",
@@ -149,7 +301,7 @@ func BenchmarkTranslate(b *testing.B) {
149301
b.ReportAllocs()
150302

151303
for i := 0; i < b.N; i++ {
152-
if labels := r.Translate(discoveredLabels, finalLabels); labels == nil {
304+
if labels, _ := r.Translate(discoveredLabels, finalLabels); labels == nil {
153305
b.Fail()
154306
}
155307
}

0 commit comments

Comments
 (0)