1package v2
2
3import (
4	"errors"
5	"fmt"
6	"net/url"
7	"path"
8	"sort"
9	"time"
10
11	jsoniter "github.com/json-iterator/go"
12	"github.com/robfig/cron"
13	utilstrings "github.com/sensu/sensu-go/util/strings"
14)
15
16const (
17	// CheckRequestType is the message type string for check request.
18	CheckRequestType = "check_request"
19
20	// ChecksResource is the name of this resource type
21	ChecksResource = "checks"
22
23	// DefaultSplayCoverage is the default splay coverage for proxy check requests
24	DefaultSplayCoverage = 90.0
25
26	// NagiosOutputMetricFormat is the accepted string to represent the output metric format of
27	// Nagios Perf Data
28	NagiosOutputMetricFormat = "nagios_perfdata"
29
30	// GraphiteOutputMetricFormat is the accepted string to represent the output metric format of
31	// Graphite Plain Text
32	GraphiteOutputMetricFormat = "graphite_plaintext"
33
34	// OpenTSDBOutputMetricFormat is the accepted string to represent the output metric format of
35	// OpenTSDB Line
36	OpenTSDBOutputMetricFormat = "opentsdb_line"
37
38	// InfluxDBOutputMetricFormat is the accepted string to represent the output metric format of
39	// InfluxDB Line
40	InfluxDBOutputMetricFormat = "influxdb_line"
41)
42
43// OutputMetricFormats represents all the accepted output_metric_format's a check can have
44var OutputMetricFormats = []string{NagiosOutputMetricFormat, GraphiteOutputMetricFormat, OpenTSDBOutputMetricFormat, InfluxDBOutputMetricFormat}
45
46// FixtureCheck returns a fixture for a Check object.
47func FixtureCheck(id string) *Check {
48	t := time.Now().Unix()
49	config := FixtureCheckConfig(id)
50	history := make([]CheckHistory, 21)
51	for i := 0; i < 21; i++ {
52		history[i] = CheckHistory{
53			Status:   0,
54			Executed: t - (60 * int64(i+1)),
55		}
56	}
57
58	c := NewCheck(config)
59	c.Issued = t
60	c.Executed = t + 1
61	c.Duration = 1.0
62	c.History = history
63
64	return c
65}
66
67// NewCheck creates a new Check. It copies the fields from CheckConfig that
68// match with Check's fields.
69//
70// Because CheckConfig uses extended attributes, embedding CheckConfig was
71// deemed to be too complicated, due to interactions between promoted methods
72// and encoding/json.
73func NewCheck(c *CheckConfig) *Check {
74	check := &Check{
75		ObjectMeta: ObjectMeta{
76			Name:        c.Name,
77			Namespace:   c.Namespace,
78			Labels:      c.Labels,
79			Annotations: c.Annotations,
80		},
81		Command:              c.Command,
82		Handlers:             c.Handlers,
83		HighFlapThreshold:    c.HighFlapThreshold,
84		Interval:             c.Interval,
85		LowFlapThreshold:     c.LowFlapThreshold,
86		Publish:              c.Publish,
87		RuntimeAssets:        c.RuntimeAssets,
88		Subscriptions:        c.Subscriptions,
89		ProxyEntityName:      c.ProxyEntityName,
90		CheckHooks:           c.CheckHooks,
91		Stdin:                c.Stdin,
92		Subdue:               c.Subdue,
93		Cron:                 c.Cron,
94		Ttl:                  c.Ttl,
95		Timeout:              c.Timeout,
96		ProxyRequests:        c.ProxyRequests,
97		RoundRobin:           c.RoundRobin,
98		OutputMetricFormat:   c.OutputMetricFormat,
99		OutputMetricHandlers: c.OutputMetricHandlers,
100		EnvVars:              c.EnvVars,
101		DiscardOutput:        c.DiscardOutput,
102		MaxOutputSize:        c.MaxOutputSize,
103	}
104	if check.Labels == nil {
105		check.Labels = make(map[string]string)
106	}
107	if check.Annotations == nil {
108		check.Annotations = make(map[string]string)
109	}
110	return check
111}
112
113// SetNamespace sets the namespace of the resource.
114func (c *Check) SetNamespace(namespace string) {
115	c.Namespace = namespace
116}
117
118// StorePrefix returns the path prefix to this resource in the store
119func (c *Check) StorePrefix() string {
120	return ChecksResource
121}
122
123// URIPath returns the path component of a check URI.
124func (c *Check) URIPath() string {
125	return path.Join(URLPrefix, "namespaces", url.PathEscape(c.Namespace), ChecksResource, url.PathEscape(c.Name))
126}
127
128// Validate returns an error if the check does not pass validation tests.
129func (c *Check) Validate() error {
130	if err := ValidateName(c.Name); err != nil {
131		return errors.New("check name " + err.Error())
132	}
133	if c.Cron != "" {
134		if c.Interval > 0 {
135			return errors.New("must only specify either an interval or a cron schedule")
136		}
137
138		if _, err := cron.ParseStandard(c.Cron); err != nil {
139			return errors.New("check cron string is invalid")
140		}
141	} else {
142		if c.Interval < 1 {
143			return errors.New("check interval must be greater than or equal to 1")
144		}
145	}
146
147	if c.Ttl > 0 && c.Ttl <= int64(c.Interval) {
148		return errors.New("ttl must be greater than check interval")
149	}
150	if c.Ttl > 0 && c.Ttl < 5 {
151		return errors.New("minimum ttl is 5 seconds")
152	}
153
154	for _, assetName := range c.RuntimeAssets {
155		if err := ValidateAssetName(assetName); err != nil {
156			return fmt.Errorf("asset's %s", err)
157		}
158	}
159
160	// The entity can be empty but can't contain invalid characters (only
161	// alphanumeric string)
162	if c.ProxyEntityName != "" {
163		if err := ValidateName(c.ProxyEntityName); err != nil {
164			return errors.New("proxy entity name " + err.Error())
165		}
166	}
167
168	if c.ProxyRequests != nil {
169		if err := c.ProxyRequests.Validate(); err != nil {
170			return err
171		}
172	}
173
174	if c.OutputMetricFormat != "" {
175		if err := ValidateOutputMetricFormat(c.OutputMetricFormat); err != nil {
176			return err
177		}
178	}
179
180	if c.LowFlapThreshold != 0 && c.HighFlapThreshold != 0 && c.LowFlapThreshold >= c.HighFlapThreshold {
181		return errors.New("invalid flap thresholds")
182	}
183
184	if err := ValidateEnvVars(c.EnvVars); err != nil {
185		return err
186	}
187
188	if c.MaxOutputSize < 0 {
189		return fmt.Errorf("MaxOutputSize must be >= 0")
190	}
191
192	return c.Subdue.Validate()
193}
194
195// MarshalJSON implements the json.Marshaler interface.
196func (c *Check) MarshalJSON() ([]byte, error) {
197	if c == nil {
198		return []byte("null"), nil
199	}
200	if c.Subscriptions == nil {
201		c.Subscriptions = []string{}
202	}
203	if c.Handlers == nil {
204		c.Handlers = []string{}
205	}
206
207	type Clone Check
208	clone := &Clone{}
209	*clone = Clone(*c)
210
211	return jsoniter.Marshal(clone)
212}
213
214// MergeWith updates the current Check with the history of the check given as
215// an argument, updating the current check's history appropriately.
216func (c *Check) MergeWith(prevCheck *Check) {
217	history := prevCheck.History
218	histEntry := CheckHistory{
219		Status:   c.Status,
220		Executed: c.Executed,
221	}
222
223	history = append(history, histEntry)
224	sort.Sort(ByExecuted(history))
225	if len(history) > 21 {
226		history = history[1:]
227	}
228
229	c.History = history
230	c.LastOK = prevCheck.LastOK
231	c.Occurrences = prevCheck.Occurrences
232	c.OccurrencesWatermark = prevCheck.OccurrencesWatermark
233	updateCheckState(c)
234}
235
236// ValidateOutputMetricFormat returns an error if the string is not a valid metric
237// format
238func ValidateOutputMetricFormat(format string) error {
239	if utilstrings.InArray(format, OutputMetricFormats) {
240		return nil
241	}
242	return errors.New("output metric format is not valid")
243}
244
245// ByExecuted implements the sort.Interface for []CheckHistory based on the
246// Executed field.
247//
248// Example:
249//
250// sort.Sort(ByExecuted(check.History))
251type ByExecuted []CheckHistory
252
253func (b ByExecuted) Len() int           { return len(b) }
254func (b ByExecuted) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
255func (b ByExecuted) Less(i, j int) bool { return b[i].Executed < b[j].Executed }
256