github.com/m3db/m3@v1.5.0/src/integration/resources/inprocess/aggregator_test.go (about) 1 // +build test_harness 2 // Copyright (c) 2021 Uber Technologies, Inc. 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 package inprocess 23 24 import ( 25 "errors" 26 "fmt" 27 "testing" 28 "time" 29 30 m3agg "github.com/m3db/m3/src/aggregator/aggregator" 31 "github.com/m3db/m3/src/cluster/generated/proto/placementpb" 32 "github.com/m3db/m3/src/cluster/placementhandler/handleroptions" 33 "github.com/m3db/m3/src/integration/resources" 34 "github.com/m3db/m3/src/msg/generated/proto/topicpb" 35 "github.com/m3db/m3/src/query/generated/proto/admin" 36 "github.com/m3db/m3/src/query/generated/proto/prompb" 37 "github.com/m3db/m3/src/query/storage" 38 xtime "github.com/m3db/m3/src/x/time" 39 "github.com/prometheus/common/model" 40 41 "github.com/stretchr/testify/assert" 42 "github.com/stretchr/testify/require" 43 ) 44 45 func TestNewAggregator(t *testing.T) { 46 coord, closer := setupCoordinator(t) 47 defer closer() 48 require.NoError(t, coord.WaitForNamespace("")) 49 50 agg, err := NewAggregatorFromYAML(defaultAggregatorConfig, AggregatorOptions{}) 51 require.NoError(t, err) 52 setupPlacement(t, coord, resources.Aggregators{agg}) 53 setupM3msgTopic(t, coord) 54 agg.Start() 55 require.NoError(t, resources.Retry(agg.IsHealthy)) 56 require.NoError(t, agg.Close()) 57 58 // re-construct and restart an aggregator instance 59 agg, err = NewAggregatorFromYAML(defaultAggregatorConfig, AggregatorOptions{Start: true}) 60 require.NoError(t, err) 61 require.NoError(t, resources.Retry(agg.IsHealthy)) 62 require.NoError(t, agg.Close()) 63 } 64 65 func TestMultiAggregators(t *testing.T) { 66 coord, closer := setupCoordinator(t) 67 defer closer() 68 require.NoError(t, coord.WaitForNamespace("")) 69 70 aggOpts := AggregatorOptions{ 71 GenerateHostID: true, 72 GeneratePorts: true, 73 Start: false, 74 } 75 76 agg1, err := NewAggregatorFromYAML(defaultAggregatorConfig, aggOpts) 77 defer func() { 78 assert.NoError(t, agg1.Close()) 79 }() 80 require.NoError(t, err) 81 82 agg2, err := NewAggregatorFromYAML(defaultAggregatorConfig, aggOpts) 83 defer func() { 84 assert.NoError(t, agg2.Close()) 85 }() 86 require.NoError(t, err) 87 88 setupPlacement(t, coord, resources.Aggregators{agg1, agg2}) 89 setupM3msgTopic(t, coord) 90 91 agg1.Start() 92 require.NoError(t, resources.Retry(agg1.IsHealthy)) 93 94 agg2.Start() 95 require.NoError(t, resources.Retry(agg2.IsHealthy)) 96 } 97 98 func TestAggregatorStatus(t *testing.T) { 99 coord, closer := setupCoordinator(t) 100 defer closer() 101 require.NoError(t, coord.WaitForNamespace("")) 102 103 agg, err := NewAggregatorFromYAML( 104 defaultAggregatorConfig, 105 AggregatorOptions{GenerateHostID: true, GeneratePorts: true}, 106 ) 107 require.NoError(t, err) 108 defer func() { 109 assert.NoError(t, agg.Close()) 110 }() 111 112 setupPlacement(t, coord, resources.Aggregators{agg}) 113 setupM3msgTopic(t, coord) 114 agg.Start() 115 require.NoError(t, resources.Retry(agg.IsHealthy)) 116 117 followerStatus := m3agg.RuntimeStatus{ 118 FlushStatus: m3agg.FlushStatus{ 119 ElectionState: m3agg.FollowerState, 120 CanLead: false, 121 }, 122 } 123 124 status, err := agg.Status() 125 require.NoError(t, err) 126 require.Equal(t, followerStatus, status) 127 128 // A follower remains a follower after resigning 129 require.NoError(t, agg.Resign()) 130 status, err = agg.Status() 131 require.NoError(t, err) 132 require.Equal(t, followerStatus, status) 133 } 134 135 func TestAggregatorWriteWithCluster(t *testing.T) { 136 cfgs, err := NewClusterConfigsFromYAML(defaultDBNodeConfig, aggregatorCoordConfig, defaultAggregatorConfig) 137 require.NoError(t, err) 138 139 cluster, err := NewCluster(cfgs, 140 resources.ClusterOptions{ 141 DBNode: resources.NewDBNodeClusterOptions(), 142 }, 143 ) 144 require.NoError(t, err) 145 defer func() { 146 assert.NoError(t, cluster.Cleanup()) 147 }() 148 149 coord := cluster.Coordinator() 150 agg, err := NewAggregatorFromYAML(defaultAggregatorConfig, AggregatorOptions{Start: false}) 151 defer func() { 152 assert.NoError(t, agg.Close()) 153 }() 154 require.NoError(t, err) 155 156 setupPlacement(t, coord, resources.Aggregators{agg}) 157 setupM3msgTopic(t, coord) 158 159 agg.Start() 160 require.NoError(t, resources.Retry(agg.IsHealthy)) 161 162 testAggMetrics(t, coord) 163 } 164 165 func setupCoordinator(t *testing.T) (resources.Coordinator, func()) { 166 dbnode, err := NewDBNodeFromYAML(defaultDBNodeConfig, DBNodeOptions{Start: true}) 167 require.NoError(t, err) 168 169 coord, err := NewCoordinatorFromYAML(aggregatorCoordConfig, CoordinatorOptions{Start: true}) 170 require.NoError(t, err) 171 172 return coord, func() { 173 assert.NoError(t, coord.Close()) 174 assert.NoError(t, dbnode.Close()) 175 } 176 } 177 178 func setupM3msgTopic(t *testing.T, coord resources.Coordinator) { 179 m3msgTopicOpts := resources.M3msgTopicOptions{ 180 Zone: "embedded", 181 Env: "default_env", 182 TopicName: "aggregator_ingest", 183 } 184 185 _, err := coord.InitM3msgTopic(m3msgTopicOpts, admin.TopicInitRequest{NumberOfShards: 4}) 186 require.NoError(t, err) 187 188 _, err = coord.AddM3msgTopicConsumer(m3msgTopicOpts, admin.TopicAddRequest{ 189 ConsumerService: &topicpb.ConsumerService{ 190 ServiceId: &topicpb.ServiceID{ 191 Name: handleroptions.M3AggregatorServiceName, 192 Environment: m3msgTopicOpts.Env, 193 Zone: m3msgTopicOpts.Zone, 194 }, 195 ConsumptionType: topicpb.ConsumptionType_REPLICATED, 196 MessageTtlNanos: 600000000000, // 10 mins 197 }, 198 }) 199 require.NoError(t, err) 200 201 aggregatedTopicOpts := resources.M3msgTopicOptions{ 202 Zone: "embedded", 203 Env: "default_env", 204 TopicName: "aggregated_metrics", 205 } 206 _, err = coord.InitM3msgTopic(aggregatedTopicOpts, admin.TopicInitRequest{NumberOfShards: 4}) 207 require.NoError(t, err) 208 209 _, err = coord.AddM3msgTopicConsumer(aggregatedTopicOpts, admin.TopicAddRequest{ 210 ConsumerService: &topicpb.ConsumerService{ 211 ServiceId: &topicpb.ServiceID{ 212 Name: handleroptions.M3CoordinatorServiceName, 213 Environment: aggregatedTopicOpts.Env, 214 Zone: aggregatedTopicOpts.Zone, 215 }, 216 ConsumptionType: topicpb.ConsumptionType_SHARED, 217 MessageTtlNanos: 600000000000, // 10 mins 218 }, 219 }) 220 require.NoError(t, err) 221 } 222 223 func setupPlacement(t *testing.T, coord resources.Coordinator, aggs resources.Aggregators) { 224 instances := make([]*placementpb.Instance, 0, len(aggs)) 225 for _, agg := range aggs { 226 info, err := agg.HostDetails() 227 require.NoError(t, err) 228 instance := &placementpb.Instance{ 229 Id: info.ID, 230 IsolationGroup: info.ID, 231 Zone: info.Zone, 232 Weight: 1, 233 Endpoint: fmt.Sprintf("%s:%d", info.M3msgAddress, info.M3msgPort), 234 Hostname: info.ID, 235 Port: info.M3msgPort, 236 } 237 238 instances = append(instances, instance) 239 } 240 241 _, err := coord.InitPlacement( 242 resources.PlacementRequestOptions{ 243 Service: resources.ServiceTypeM3Aggregator, 244 Zone: "embedded", 245 Env: "default_env", 246 }, 247 admin.PlacementInitRequest{ 248 NumShards: 4, 249 ReplicationFactor: 1, 250 Instances: instances, 251 }, 252 ) 253 require.NoError(t, err) 254 255 _, err = coord.InitPlacement( 256 resources.PlacementRequestOptions{ 257 Service: resources.ServiceTypeM3Coordinator, 258 Zone: "embedded", 259 Env: "default_env", 260 }, 261 admin.PlacementInitRequest{ 262 Instances: []*placementpb.Instance{ 263 { 264 Id: "m3coordinator01", 265 Zone: "embedded", 266 Endpoint: "0.0.0.0:7507", 267 Hostname: "m3coordinator01", 268 Port: 7507, 269 }, 270 }, 271 }, 272 ) 273 require.NoError(t, err) 274 } 275 276 func testAggMetrics(t *testing.T, coord resources.Coordinator) { 277 var ( 278 ts = time.Now() 279 ts1 = xtime.ToUnixNano(ts) 280 ts2 = xtime.ToUnixNano(ts.Add(1 * time.Millisecond)) 281 ts3 = xtime.ToUnixNano(ts.Add(2 * time.Millisecond)) 282 samples = []prompb.Sample{ 283 {Value: 1, Timestamp: storage.TimeToPromTimestamp(ts1)}, 284 {Value: 2, Timestamp: storage.TimeToPromTimestamp(ts2)}, 285 {Value: 3, Timestamp: storage.TimeToPromTimestamp(ts3)}, 286 } 287 // 6=1+2+3 is the sum of all three samples. 288 expectedValue = model.SampleValue(6) 289 ) 290 assert.NoError(t, resources.Retry(func() error { 291 return coord.WriteProm("cpu", map[string]string{"host": "host1"}, samples, nil) 292 })) 293 294 queryHeaders := resources.Headers{"M3-Metrics-Type": {"aggregated"}, "M3-Storage-Policy": {"5s:6h"}} 295 296 // Instant Query 297 require.NoError(t, resources.Retry(func() error { 298 result, err := coord.InstantQuery(resources.QueryRequest{Query: "cpu"}, queryHeaders) 299 if err != nil { 300 return err 301 } 302 if len(result) != 1 { 303 return errors.New("wrong amount of datapoints") 304 } 305 if result[0].Value != expectedValue { 306 return errors.New("wrong data point value") 307 } 308 return nil 309 })) 310 311 // Range Query 312 require.NoError(t, resources.Retry(func() error { 313 result, err := coord.RangeQuery( 314 resources.RangeQueryRequest{ 315 Query: "cpu", 316 Start: time.Now().Add(-30 * time.Second), 317 End: time.Now(), 318 Step: 1 * time.Second, 319 }, 320 queryHeaders, 321 ) 322 if err != nil { 323 return err 324 } 325 if len(result) != 1 { 326 return errors.New("wrong amount of series in the range query result") 327 } 328 if len(result[0].Values) == 0 { 329 return errors.New("empty range query result") 330 } 331 if result[0].Values[0].Value != expectedValue { 332 return errors.New("wrong range query value") 333 } 334 return nil 335 })) 336 } 337 338 const defaultAggregatorConfig = `{}` 339 340 const aggregatorCoordConfig = ` 341 clusters: 342 - client: 343 config: 344 service: 345 env: default_env 346 zone: embedded 347 service: m3db 348 etcdClusters: 349 - zone: embedded 350 endpoints: 351 - 127.0.0.1:2379 352 downsample: 353 rules: 354 mappingRules: 355 - name: "agged metrics" 356 filter: "host:*" 357 aggregations: ["Sum"] 358 storagePolicies: 359 - resolution: 5s 360 retention: 6h 361 ingest: 362 ingester: 363 workerPoolSize: 10000 364 m3msg: 365 server: 366 listenAddress: "0.0.0.0:7507" 367 `