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