github.com/m3db/m3@v1.5.0/src/integration/legacy/carbon_test.go (about) 1 // +build cluster_integration 2 // 3 // Copyright (c) 2020 Uber Technologies, Inc. 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the "Software"), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 // 12 // The above copyright notice and this permission notice shall be included in 13 // all copies or substantial portions of the Software. 14 // 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 // THE SOFTWARE. 22 23 package legacy 24 25 import ( 26 "encoding/json" 27 "fmt" 28 "testing" 29 "time" 30 31 "github.com/m3db/m3/src/integration/resources" 32 33 "github.com/stretchr/testify/assert" 34 ) 35 36 func findVerifier(expected string) resources.ResponseVerifier { 37 return func(status int, _ map[string][]string, s string, err error) error { 38 if err != nil { 39 return err 40 } 41 42 if status/100 != 2 { 43 return fmt.Errorf("expected 200 status code, got %v", status) 44 } 45 46 if s == expected { 47 return nil 48 } 49 50 return fmt.Errorf("%s does not match expected:%s", s, expected) 51 } 52 } 53 54 func renderVerifier(v float64) resources.ResponseVerifier { 55 type graphiteRender struct { 56 Target string `json:"target"` 57 Datapoints [][]float64 `json:"datapoints"` 58 } 59 60 return func(status int, _ map[string][]string, s string, err error) error { 61 if err != nil { 62 return err 63 } 64 65 if status/100 != 2 { 66 return fmt.Errorf("expected 200 status code, got %v", status) 67 } 68 69 var render []graphiteRender 70 if err := json.Unmarshal([]byte(s), &render); err != nil { 71 return err 72 } 73 74 if len(render) != 1 { 75 return fmt.Errorf("expected one result, got %d", len(render)) 76 } 77 78 for _, dps := range render[0].Datapoints { 79 // NB: graphite presents datapoints as an array [value, timestamp] 80 // i.e. value: dps[0], timestamp: dps[1] 81 if len(dps) != 2 { 82 return fmt.Errorf("expected two values in result, got %d", len(dps)) 83 } 84 85 if dps[0] == v { 86 return nil 87 } 88 } 89 90 return fmt.Errorf("could not find point with value %f", v) 91 } 92 } 93 94 func graphiteQuery(target string, start time.Time) string { 95 from := start.Add(time.Minute * -2).Unix() 96 until := start.Add(time.Minute * 2).Unix() 97 return fmt.Sprintf("api/v1/graphite/render?target=%s&from=%d&until=%d", 98 target, from, until) 99 } 100 101 func graphiteFind(query string) string { 102 return fmt.Sprintf("api/v1/graphite/metrics/find?query=%s", query) 103 } 104 105 func TestCarbon(t *testing.T) { 106 // NB(nate): This test wasn't hooked up to a CI pipeline and has either rotted or never worked. 107 // Test fails with the following mismatch: 108 // 109 // actual: [{"id":"a.bar","text":"bar","leaf":0,"expandable":1,"allowChildren":1},{"id":"a.bag","text":"bag","leaf":1,"expandable":0,"allowChildren":0}] 110 // expected: [{"id":"a.bag","text":"bag","leaf":1,"expandable":0,"allowChildren":0},{"id":"a.bar","text":"bar","leaf":1,"expandable":0,"allowChildren":0},{"id":"a.bar","text":"bar","leaf":0,"expandable":1,"allowChildren":1}] 111 // 112 // Skipping until we decide to fix or delete. 113 t.Skip("skipping. see comment above.") 114 115 coord := m3.Coordinator() 116 117 timestamp := time.Now() 118 119 write := func(metric string, value float64) { 120 assert.NoError(t, coord.WriteCarbon(7204, metric, value, timestamp)) 121 } 122 123 read := func(metric string, expected float64) { 124 assert.NoError(t, coord.RunQuery( 125 renderVerifier(expected), 126 graphiteQuery(metric, timestamp), 127 nil)) 128 } 129 130 // NB: since carbon writes are aggregated, it might be up to 10 seconds for 131 // these points to appear in queries. Because of this, write data for all 132 // test cases at once, then query all of them to reduce test duration. 133 aggMetric := "foo.min.aggregate.baz" 134 write(aggMetric, 41) 135 write(aggMetric, 42) 136 write(aggMetric, 40) 137 138 unaggregatedMetric := "foo.min.already-aggregated.baz" 139 write(unaggregatedMetric, 41) 140 write(unaggregatedMetric, 42) 141 142 meanMetric := "foo.min.catch-all.baz" 143 write(meanMetric, 10) 144 write(meanMetric, 20) 145 146 colonMetric := "foo.min:biz.baz.qux" 147 write(colonMetric, 42) 148 149 // Seed some points for find endpoints. 150 findMetrics := []string{ 151 "a", 152 "a.bar", 153 "a.biz", 154 "a.biz.cake", 155 "a.bar.caw.daz", 156 "a.bag", 157 "c:bar.c:baz", 158 } 159 160 for _, m := range findMetrics { 161 write(m, 0) 162 } 163 164 // Test for min aggregation. 40 should win since it has min aggergation. 165 read(aggMetric, 40) 166 // Test that unaggregated metrics upsert. 167 read(unaggregatedMetric, 42) 168 // Test that unaggregated metrics upsert. 169 read(meanMetric, 15) 170 // Test that metrics with colons in them are written correctly. 171 read(colonMetric, 42) 172 173 // Test find endpoints with various queries. 174 findResults := map[string]string{ 175 "a.b*.caw.*": `[{"id":"a.b*.caw.daz","text":"daz","leaf":1,"expandable":0,"allowChildren":0}]`, 176 "a.b*.c*": `[{"id":"a.b*.cake","text":"cake","leaf":1,"expandable":0,"allowChildren":0},{"id":"a.b*.caw","text":"caw","leaf":0,"expandable":1,"allowChildren":1}]`, 177 "a.b*": `[{"id":"a.bar","text":"bar","leaf":1,"expandable":0,"allowChildren":0},{"id":"a.bar","text":"bar","leaf":0,"expandable":1,"allowChildren":1},{"id":"a.biz","text":"biz","leaf":1,"expandable":0,"allowChildren":0},{"id":"a.biz","text":"biz","leaf":0,"expandable":1,"allowChildren":1},{"id":"a.bag","text":"bag","leaf":1,"expandable":0,"allowChildren":0}]`, 178 "a.ba[rg]": `[{"id":"a.bag","text":"bag","leaf":1,"expandable":0,"allowChildren":0},{"id":"a.bar","text":"bar","leaf":1,"expandable":0,"allowChildren":0},{"id":"a.bar","text":"bar","leaf":0,"expandable":1,"allowChildren":1}]`, 179 "a*": `[{"id":"a","text":"a","leaf":1,"expandable":0,"allowChildren":0},{"id":"a","text":"a","leaf":0,"expandable":1,"allowChildren":1}]`, 180 "c:*": `[{"id":"c:bar","text":"c:bar","leaf":0,"expandable":1,"allowChildren":1}]`, 181 "c:bar.*": `[{"id":"c:bar.c:baz","text":"c:baz","leaf":1,"expandable":0,"allowChildren":0}]`, 182 "x": "[]", 183 "a.d": "[]", 184 "*.*.*.*.*": "[]", 185 } 186 187 for query, ex := range findResults { 188 assert.NoError(t, coord.RunQuery(findVerifier(ex), graphiteFind(query), nil)) 189 } 190 }