...

Source file src/github.com/prometheus/alertmanager/test/with_api_v2/collector.go

Documentation: github.com/prometheus/alertmanager/test/with_api_v2

     1  // Copyright 2018 Prometheus Team
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package test
    15  
    16  import (
    17  	"encoding/json"
    18  	"fmt"
    19  	"sync"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/prometheus/alertmanager/api/v2/models"
    24  )
    25  
    26  // Collector gathers alerts received by a notification receiver
    27  // and verifies whether all arrived and within the correct time boundaries.
    28  type Collector struct {
    29  	t    *testing.T
    30  	name string
    31  	opts *AcceptanceOpts
    32  
    33  	collected map[float64][]models.GettableAlerts
    34  	expected  map[Interval][]models.GettableAlerts
    35  
    36  	mtx sync.RWMutex
    37  }
    38  
    39  func (c *Collector) String() string {
    40  	return c.name
    41  }
    42  
    43  // Collected returns a map of alerts collected by the collector indexed with the
    44  // receive timestamp.
    45  func (c *Collector) Collected() map[float64][]models.GettableAlerts {
    46  	c.mtx.RLock()
    47  	defer c.mtx.RUnlock()
    48  	return c.collected
    49  }
    50  
    51  func batchesEqual(as, bs models.GettableAlerts, opts *AcceptanceOpts) bool {
    52  	if len(as) != len(bs) {
    53  		return false
    54  	}
    55  
    56  	for _, a := range as {
    57  		found := false
    58  		for _, b := range bs {
    59  			if equalAlerts(a, b, opts) {
    60  				found = true
    61  				break
    62  			}
    63  		}
    64  		if !found {
    65  			return false
    66  		}
    67  	}
    68  	return true
    69  }
    70  
    71  // latest returns the latest relative point in time where a notification is
    72  // expected.
    73  func (c *Collector) latest() float64 {
    74  	c.mtx.RLock()
    75  	defer c.mtx.RUnlock()
    76  	var latest float64
    77  	for iv := range c.expected {
    78  		if iv.end > latest {
    79  			latest = iv.end
    80  		}
    81  	}
    82  	return latest
    83  }
    84  
    85  // Want declares that the Collector expects to receive the given alerts
    86  // within the given time boundaries.
    87  func (c *Collector) Want(iv Interval, alerts ...*TestAlert) {
    88  	c.mtx.Lock()
    89  	defer c.mtx.Unlock()
    90  	var nas models.GettableAlerts
    91  	for _, a := range alerts {
    92  		nas = append(nas, a.nativeAlert(c.opts))
    93  	}
    94  
    95  	c.expected[iv] = append(c.expected[iv], nas)
    96  }
    97  
    98  // add the given alerts to the collected alerts.
    99  func (c *Collector) add(alerts ...*models.GettableAlert) {
   100  	c.mtx.Lock()
   101  	defer c.mtx.Unlock()
   102  	arrival := c.opts.relativeTime(time.Now())
   103  
   104  	c.collected[arrival] = append(c.collected[arrival], models.GettableAlerts(alerts))
   105  }
   106  
   107  func (c *Collector) Check() string {
   108  	report := fmt.Sprintf("\ncollector %q:\n\n", c)
   109  
   110  	c.mtx.RLock()
   111  	defer c.mtx.RUnlock()
   112  	for iv, expected := range c.expected {
   113  		report += fmt.Sprintf("interval %v\n", iv)
   114  
   115  		var alerts []models.GettableAlerts
   116  		for at, got := range c.collected {
   117  			if iv.contains(at) {
   118  				alerts = append(alerts, got...)
   119  			}
   120  		}
   121  
   122  		for _, exp := range expected {
   123  			found := len(exp) == 0 && len(alerts) == 0
   124  
   125  			report += "---\n"
   126  
   127  			for _, e := range exp {
   128  				report += fmt.Sprintf("- %v\n", c.opts.alertString(e))
   129  			}
   130  
   131  			for _, a := range alerts {
   132  				if batchesEqual(exp, a, c.opts) {
   133  					found = true
   134  					break
   135  				}
   136  			}
   137  
   138  			if found {
   139  				report += "  [ ✓ ]\n"
   140  			} else {
   141  				c.t.Fail()
   142  				report += "  [ ✗ ]\n"
   143  			}
   144  		}
   145  	}
   146  
   147  	// Detect unexpected notifications.
   148  	var totalExp, totalAct int
   149  	for _, exp := range c.expected {
   150  		for _, e := range exp {
   151  			totalExp += len(e)
   152  		}
   153  	}
   154  	for _, act := range c.collected {
   155  		for _, a := range act {
   156  			if len(a) == 0 {
   157  				c.t.Error("received empty notifications")
   158  			}
   159  			totalAct += len(a)
   160  		}
   161  	}
   162  	if totalExp != totalAct {
   163  		c.t.Fail()
   164  		report += fmt.Sprintf("\nExpected total of %d alerts, got %d", totalExp, totalAct)
   165  	}
   166  
   167  	if c.t.Failed() {
   168  		report += "\nreceived:\n"
   169  
   170  		for at, col := range c.collected {
   171  			for _, alerts := range col {
   172  				report += fmt.Sprintf("@ %v\n", at)
   173  				for _, a := range alerts {
   174  					report += fmt.Sprintf("- %v\n", c.opts.alertString(a))
   175  				}
   176  			}
   177  		}
   178  	}
   179  
   180  	return report
   181  }
   182  
   183  // alertsToString returns a string representation of the given Alerts. Use for
   184  // debugging.
   185  func alertsToString(as []*models.GettableAlert) (string, error) {
   186  	b, err := json.Marshal(as)
   187  	if err != nil {
   188  		return "", err
   189  	}
   190  
   191  	return string(b), nil
   192  }
   193  
   194  // CompareCollectors compares two collectors based on their collected alerts
   195  func CompareCollectors(a, b *Collector, opts *AcceptanceOpts) (bool, error) {
   196  	f := func(collected map[float64][]models.GettableAlerts) []*models.GettableAlert {
   197  		result := []*models.GettableAlert{}
   198  		for _, batches := range collected {
   199  			for _, batch := range batches {
   200  				for _, alert := range batch {
   201  					result = append(result, alert)
   202  				}
   203  			}
   204  		}
   205  		return result
   206  	}
   207  
   208  	aAlerts := f(a.Collected())
   209  	bAlerts := f(b.Collected())
   210  
   211  	if len(aAlerts) != len(bAlerts) {
   212  		aAsString, err := alertsToString(aAlerts)
   213  		if err != nil {
   214  			return false, err
   215  		}
   216  		bAsString, err := alertsToString(bAlerts)
   217  		if err != nil {
   218  			return false, err
   219  		}
   220  
   221  		err = fmt.Errorf(
   222  			"first collector has %v alerts, second collector has %v alerts\n%v\n%v",
   223  			len(aAlerts), len(bAlerts),
   224  			aAsString, bAsString,
   225  		)
   226  		return false, err
   227  	}
   228  
   229  	for _, aAlert := range aAlerts {
   230  		found := false
   231  		for _, bAlert := range bAlerts {
   232  			if equalAlerts(aAlert, bAlert, opts) {
   233  				found = true
   234  				break
   235  			}
   236  		}
   237  
   238  		if !found {
   239  			aAsString, err := alertsToString([]*models.GettableAlert{aAlert})
   240  			if err != nil {
   241  				return false, err
   242  			}
   243  			bAsString, err := alertsToString(bAlerts)
   244  			if err != nil {
   245  				return false, err
   246  			}
   247  
   248  			err = fmt.Errorf(
   249  				"could not find matching alert for alert from first collector\n%v\nin alerts of second collector\n%v",
   250  				aAsString, bAsString,
   251  			)
   252  
   253  			return false, err
   254  		}
   255  	}
   256  
   257  	return true, nil
   258  }
   259  

View as plain text