2017-01-04 02:44:32 +00:00
// Copyright 2017 clair authors
2015-11-13 19:11:28 +00:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2017-01-26 23:19:32 +00:00
package clair
2015-11-13 19:11:28 +00:00
import (
"time"
2015-12-15 16:24:58 +00:00
"github.com/coreos/pkg/timeutil"
2015-11-24 04:43:33 +00:00
"github.com/pborman/uuid"
2016-01-22 20:59:46 +00:00
"github.com/prometheus/client_golang/prometheus"
2017-05-04 17:21:25 +00:00
log "github.com/sirupsen/logrus"
2015-11-24 04:43:33 +00:00
2015-11-13 19:11:28 +00:00
"github.com/coreos/clair/database"
2017-01-04 02:44:32 +00:00
"github.com/coreos/clair/ext/notification"
2017-01-18 03:18:03 +00:00
"github.com/coreos/clair/pkg/stopper"
2015-11-13 19:11:28 +00:00
)
const (
2017-01-26 23:19:32 +00:00
notifierCheckInterval = 5 * time . Minute
notifierMaxBackOff = 15 * time . Minute
notifierLockRefreshDuration = time . Minute * 2
notifierLockDuration = time . Minute * 8 + notifierLockRefreshDuration
2017-05-04 17:21:25 +00:00
logSenderName = "sender name"
logNotiName = "notification name"
2015-11-13 19:11:28 +00:00
)
2016-01-22 20:59:46 +00:00
var (
2016-01-24 03:02:34 +00:00
promNotifierLatencyMilliseconds = prometheus . NewHistogram ( prometheus . HistogramOpts {
Name : "clair_notifier_latency_milliseconds" ,
2016-01-22 20:59:46 +00:00
Help : "Time it takes to send a notification after it's been created." ,
} )
2016-01-24 03:02:34 +00:00
promNotifierBackendErrorsTotal = prometheus . NewCounterVec ( prometheus . CounterOpts {
Name : "clair_notifier_backend_errors_total" ,
Help : "Number of errors that notifier backends generated." ,
} , [ ] string { "backend" } )
2016-01-22 20:59:46 +00:00
)
2015-11-13 19:11:28 +00:00
2016-01-24 03:02:34 +00:00
func init ( ) {
prometheus . MustRegister ( promNotifierLatencyMilliseconds )
prometheus . MustRegister ( promNotifierBackendErrorsTotal )
}
2017-01-26 23:19:32 +00:00
// RunNotifier begins a process that checks for new notifications that should
// be sent out to third parties.
2017-01-27 01:14:44 +00:00
func RunNotifier ( config * notification . Config , datastore database . Datastore , stopper * stopper . Stopper ) {
2015-11-24 04:43:33 +00:00
defer stopper . End ( )
2015-12-07 21:38:50 +00:00
2015-12-15 16:36:06 +00:00
// Configure registered notifiers.
2017-01-13 07:33:19 +00:00
for senderName , sender := range notification . Senders ( ) {
2017-01-04 02:44:32 +00:00
if configured , err := sender . Configure ( config ) ; configured {
2017-05-04 17:21:25 +00:00
log . WithField ( logSenderName , senderName ) . Info ( "sender configured" )
2015-12-15 16:36:06 +00:00
} else {
2017-01-13 07:33:19 +00:00
notification . UnregisterSender ( senderName )
2015-12-15 16:36:06 +00:00
if err != nil {
2017-05-04 17:21:25 +00:00
log . WithError ( err ) . WithField ( logSenderName , senderName ) . Error ( "could not configure notifier" )
2015-12-15 16:36:06 +00:00
}
}
}
// Do not run the updater if there is no notifier enabled.
2017-01-13 07:33:19 +00:00
if len ( notification . Senders ( ) ) == 0 {
2017-05-04 17:21:25 +00:00
log . Info ( "notifier service is disabled" )
2015-12-07 21:38:50 +00:00
return
}
2015-12-15 16:36:06 +00:00
whoAmI := uuid . New ( )
2017-05-04 17:21:25 +00:00
log . WithField ( "lock identifier" , whoAmI ) . Info ( "notifier service started" )
2015-11-13 19:11:28 +00:00
2015-12-15 18:23:57 +00:00
for running := true ; running ; {
2015-11-24 04:43:33 +00:00
// Find task.
2016-01-22 20:59:46 +00:00
notification := findTask ( datastore , config . RenotifyInterval , whoAmI , stopper )
if notification == nil {
2015-12-15 18:23:57 +00:00
// Interrupted while finding a task, Clair is stopping.
2015-11-24 04:43:33 +00:00
break
2015-11-13 19:11:28 +00:00
}
2015-11-24 04:43:33 +00:00
// Handle task.
done := make ( chan bool , 1 )
go func ( ) {
2016-01-22 20:59:46 +00:00
success , interrupted := handleTask ( * notification , stopper , config . Attempts )
2015-12-15 18:23:57 +00:00
if success {
2017-07-26 23:22:29 +00:00
err := markNotificationNotified ( datastore , notification . Name )
if err != nil {
log . WithError ( err ) . Error ( "Failed to mark notification notified" )
}
2017-01-18 01:33:20 +00:00
promNotifierLatencyMilliseconds . Observe ( float64 ( time . Since ( notification . Created ) . Nanoseconds ( ) ) / float64 ( time . Millisecond ) )
2015-11-13 19:11:28 +00:00
}
2015-12-15 18:23:57 +00:00
if interrupted {
running = false
}
2017-07-26 23:22:29 +00:00
unlock ( datastore , notification . Name , whoAmI )
2015-11-24 04:43:33 +00:00
done <- true
} ( )
// Refresh task lock until done.
outer :
for {
select {
case <- done :
break outer
2017-01-26 23:19:32 +00:00
case <- time . After ( notifierLockRefreshDuration ) :
2017-07-26 23:22:29 +00:00
lock ( datastore , notification . Name , whoAmI , notifierLockDuration , true )
case <- stopper . Chan ( ) :
running = false
break
2015-11-13 19:11:28 +00:00
}
2015-11-24 04:43:33 +00:00
}
}
2015-11-13 19:11:28 +00:00
2015-11-24 04:43:33 +00:00
log . Info ( "notifier service stopped" )
}
2015-11-13 19:11:28 +00:00
2017-07-26 23:22:29 +00:00
func findTask ( datastore database . Datastore , renotifyInterval time . Duration , whoAmI string , stopper * stopper . Stopper ) * database . NotificationHook {
2015-11-24 04:43:33 +00:00
for {
2017-07-26 23:22:29 +00:00
notification , ok , err := findNewNotification ( datastore , renotifyInterval )
if err != nil || ! ok {
if ! ok {
2017-05-04 17:21:25 +00:00
log . WithError ( err ) . Warning ( "could not get notification to send" )
2016-01-21 23:09:23 +00:00
}
2015-11-13 19:11:28 +00:00
2016-01-21 23:09:23 +00:00
// Wait.
2017-01-26 23:19:32 +00:00
if ! stopper . Sleep ( notifierCheckInterval ) {
2016-01-22 20:59:46 +00:00
return nil
2015-11-13 19:11:28 +00:00
}
2016-01-21 23:09:23 +00:00
2015-11-24 04:43:33 +00:00
continue
}
2015-11-13 19:11:28 +00:00
2015-11-24 04:43:33 +00:00
// Lock the notification.
2017-07-26 23:22:29 +00:00
if hasLock , _ := lock ( datastore , notification . Name , whoAmI , notifierLockDuration , false ) ; hasLock {
2017-05-04 17:21:25 +00:00
log . WithField ( logNotiName , notification . Name ) . Info ( "found and locked a notification" )
2016-01-22 20:59:46 +00:00
return & notification
2015-11-24 04:43:33 +00:00
}
}
}
2017-07-26 23:22:29 +00:00
func handleTask ( n database . NotificationHook , st * stopper . Stopper , maxAttempts int ) ( bool , bool ) {
2015-11-24 04:43:33 +00:00
// Send notification.
2017-01-13 07:33:19 +00:00
for senderName , sender := range notification . Senders ( ) {
2015-12-15 16:24:58 +00:00
var attempts int
var backOff time . Duration
for {
// Max attempts exceeded.
if attempts >= maxAttempts {
2017-05-04 17:21:25 +00:00
log . WithFields ( log . Fields { logNotiName : n . Name , logSenderName : senderName , "max attempts" : maxAttempts } ) . Info ( "giving up on sending notification : max attempts exceeded" )
2015-12-15 18:23:57 +00:00
return false , false
2015-12-15 16:24:58 +00:00
}
// Backoff.
if backOff > 0 {
2017-05-04 17:21:25 +00:00
log . WithFields ( log . Fields { "duration" : backOff , logNotiName : n . Name , logSenderName : senderName , "attempts" : attempts + 1 , "max attempts" : maxAttempts } ) . Info ( "waiting before retrying to send notification" )
2015-12-15 16:24:58 +00:00
if ! st . Sleep ( backOff ) {
2015-12-15 18:23:57 +00:00
return false , true
2015-12-15 16:24:58 +00:00
}
}
// Send using the current notifier.
2017-07-26 23:22:29 +00:00
if err := sender . Send ( n . Name ) ; err != nil {
2016-01-21 23:09:23 +00:00
// Send failed; increase attempts/backoff and retry.
2017-01-04 02:44:32 +00:00
promNotifierBackendErrorsTotal . WithLabelValues ( senderName ) . Inc ( )
2017-05-04 17:21:25 +00:00
log . WithError ( err ) . WithFields ( log . Fields { logSenderName : senderName , logNotiName : n . Name } ) . Error ( "could not send notification via notifier" )
2017-01-26 23:19:32 +00:00
backOff = timeutil . ExpBackoff ( backOff , notifierMaxBackOff )
2016-01-21 23:09:23 +00:00
attempts ++
2016-02-09 21:07:57 +00:00
continue
2015-12-15 16:24:58 +00:00
}
2016-01-21 23:09:23 +00:00
// Send has been successful. Go to the next notifier.
break
2015-12-15 16:36:06 +00:00
}
2015-11-13 19:11:28 +00:00
}
2017-05-04 17:21:25 +00:00
log . WithField ( logNotiName , n . Name ) . Info ( "successfully sent notification" )
2015-12-15 18:23:57 +00:00
return true , false
2015-11-13 19:11:28 +00:00
}
2017-07-26 23:22:29 +00:00
func findNewNotification ( datastore database . Datastore , renotifyInterval time . Duration ) ( database . NotificationHook , bool , error ) {
tx , err := datastore . Begin ( )
if err != nil {
return database . NotificationHook { } , false , err
}
defer tx . Rollback ( )
return tx . FindNewNotification ( time . Now ( ) . Add ( - renotifyInterval ) )
}
func markNotificationNotified ( datastore database . Datastore , name string ) error {
tx , err := datastore . Begin ( )
if err != nil {
log . WithError ( err ) . Error ( "an error happens when beginning database transaction" )
}
defer tx . Rollback ( )
if err := tx . MarkNotificationNotified ( name ) ; err != nil {
return err
}
return tx . Commit ( )
}
// unlock removes a lock with provided name, owner. Internally, it handles
// database transaction and catches error.
func unlock ( datastore database . Datastore , name , owner string ) {
tx , err := datastore . Begin ( )
if err != nil {
return
}
defer tx . Rollback ( )
if err := tx . Unlock ( name , owner ) ; err != nil {
return
}
if err := tx . Commit ( ) ; err != nil {
return
}
}
func lock ( datastore database . Datastore , name string , owner string , duration time . Duration , renew bool ) ( bool , time . Time ) {
// any error will cause the function to catch the error and return false.
tx , err := datastore . Begin ( )
if err != nil {
return false , time . Time { }
}
defer tx . Rollback ( )
locked , t , err := tx . Lock ( name , owner , duration , renew )
if err != nil {
return false , time . Time { }
}
if locked {
if err := tx . Commit ( ) ; err != nil {
return false , time . Time { }
}
}
return locked , t
}