2015-11-13 19:11:28 +00:00
// Copyright 2015 clair authors
//
// 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.
2016-01-22 20:59:46 +00:00
// Package notifier fetches notifications from the database and informs the specified remote handler
// about their existences, inviting the third party to actively query the API about it.
2015-11-13 19:11:28 +00:00
package notifier
import (
"time"
"github.com/coreos/pkg/capnslog"
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"
2015-11-24 04:43:33 +00:00
2015-12-07 21:38:50 +00:00
"github.com/coreos/clair/config"
2015-11-13 19:11:28 +00:00
"github.com/coreos/clair/database"
"github.com/coreos/clair/utils"
2016-01-21 23:09:23 +00:00
cerrors "github.com/coreos/clair/utils/errors"
2015-11-13 19:11:28 +00:00
)
const (
2015-12-15 16:24:58 +00:00
checkInterval = 5 * time . Minute
2015-11-24 04:43:33 +00:00
refreshLockDuration = time . Minute * 2
lockDuration = time . Minute * 8 + refreshLockDuration
2015-12-15 16:24:58 +00:00
maxBackOff = 15 * time . Minute
2015-11-13 19:11:28 +00:00
)
2016-01-22 20:59:46 +00:00
var (
log = capnslog . NewPackageLogger ( "github.com/coreos/clair" , "notifier" )
2015-11-13 19:11:28 +00:00
2016-01-22 20:59:46 +00:00
notifiers = make ( map [ string ] Notifier )
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
2015-12-15 16:36:06 +00:00
// Notifier represents anything that can transmit notifications.
type Notifier interface {
// Configure attempts to initialize the notifier with the provided configuration.
// It returns whether the notifier is enabled or not.
Configure ( * config . NotifierConfig ) ( bool , error )
2016-01-22 20:59:46 +00:00
// Send informs the existence of the specified notification.
Send ( notification database . VulnerabilityNotification ) error
2015-12-15 16:36:06 +00:00
}
2015-12-07 21:38:50 +00:00
2016-01-24 03:02:34 +00:00
func init ( ) {
prometheus . MustRegister ( promNotifierLatencyMilliseconds )
prometheus . MustRegister ( promNotifierBackendErrorsTotal )
}
2015-12-15 16:36:06 +00:00
// RegisterNotifier makes a Fetcher available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func RegisterNotifier ( name string , n Notifier ) {
if name == "" {
panic ( "notifier: could not register a Notifier with an empty name" )
2015-11-24 04:43:33 +00:00
}
2015-12-15 16:36:06 +00:00
if n == nil {
panic ( "notifier: could not register a nil Notifier" )
2015-11-24 04:43:33 +00:00
}
2015-11-13 19:11:28 +00:00
2015-12-15 16:36:06 +00:00
if _ , dup := notifiers [ name ] ; dup {
panic ( "notifier: RegisterNotifier called twice for " + name )
2015-11-24 04:43:33 +00:00
}
2015-11-13 19:11:28 +00:00
2015-12-15 16:36:06 +00:00
notifiers [ name ] = n
2015-11-24 04:43:33 +00:00
}
2015-11-13 19:11:28 +00:00
2015-12-15 16:36:06 +00:00
// Run starts the Notifier service.
2016-01-21 23:09:23 +00:00
func Run ( config * config . NotifierConfig , datastore database . Datastore , stopper * utils . 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.
for notifierName , notifier := range notifiers {
if configured , err := notifier . Configure ( config ) ; configured {
log . Infof ( "notifier '%s' configured\n" , notifierName )
} else {
delete ( notifiers , notifierName )
if err != nil {
log . Errorf ( "could not configure notifier '%s': %s" , notifierName , err )
}
}
}
// Do not run the updater if there is no notifier enabled.
if len ( notifiers ) == 0 {
log . Infof ( "notifier service is disabled" )
2015-12-07 21:38:50 +00:00
return
}
2015-12-15 16:36:06 +00:00
whoAmI := uuid . New ( )
log . Infof ( "notifier service started. lock identifier: %s\n" , whoAmI )
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 {
2016-01-24 03:02:34 +00:00
utils . PrometheusObserveTimeMilliseconds ( promNotifierLatencyMilliseconds , notification . Created )
2016-01-22 20:59:46 +00:00
datastore . SetNotificationNotified ( notification . Name )
2015-11-13 19:11:28 +00:00
}
2015-12-15 18:23:57 +00:00
if interrupted {
running = false
}
2016-01-22 20:59:46 +00:00
datastore . Unlock ( 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
case <- time . After ( refreshLockDuration ) :
2016-01-22 20:59:46 +00:00
datastore . Lock ( notification . Name , whoAmI , lockDuration , true )
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
2016-01-22 20:59:46 +00:00
func findTask ( datastore database . Datastore , renotifyInterval time . Duration , whoAmI string , stopper * utils . Stopper ) * database . VulnerabilityNotification {
2015-11-24 04:43:33 +00:00
for {
// Find a notification to send.
2016-01-22 20:59:46 +00:00
notification , err := datastore . GetAvailableNotification ( renotifyInterval )
2015-11-24 04:43:33 +00:00
if err != nil {
2016-02-24 23:00:36 +00:00
// There is no notification or an error occurred.
2016-01-21 23:09:23 +00:00
if err != cerrors . ErrNotFound {
log . Warningf ( "could not get notification to send: %s" , err )
}
2015-11-13 19:11:28 +00:00
2016-01-21 23:09:23 +00:00
// Wait.
2015-11-24 04:43:33 +00:00
if ! stopper . Sleep ( checkInterval ) {
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.
2016-01-22 20:59:46 +00:00
if hasLock , _ := datastore . Lock ( notification . Name , whoAmI , lockDuration , false ) ; hasLock {
log . Infof ( "found and locked a notification: %s" , notification . Name )
return & notification
2015-11-24 04:43:33 +00:00
}
}
}
2016-01-22 20:59:46 +00:00
func handleTask ( notification database . VulnerabilityNotification , st * utils . Stopper , maxAttempts int ) ( bool , bool ) {
2015-11-24 04:43:33 +00:00
// Send notification.
2015-12-15 16:36:06 +00:00
for notifierName , notifier := range notifiers {
2015-12-15 16:24:58 +00:00
var attempts int
var backOff time . Duration
for {
// Max attempts exceeded.
if attempts >= maxAttempts {
2016-02-09 21:07:57 +00:00
log . Infof ( "giving up on sending notification '%s' via notifier '%s': max attempts exceeded (%d)\n" , notification . Name , notifierName , maxAttempts )
2015-12-15 18:23:57 +00:00
return false , false
2015-12-15 16:24:58 +00:00
}
// Backoff.
if backOff > 0 {
2016-02-09 21:07:57 +00:00
log . Infof ( "waiting %v before retrying to send notification '%s' via notifier '%s' (Attempt %d / %d)\n" , backOff , notification . Name , notifierName , attempts + 1 , maxAttempts )
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.
2016-01-22 20:59:46 +00:00
if err := notifier . Send ( notification ) ; err != nil {
2016-01-21 23:09:23 +00:00
// Send failed; increase attempts/backoff and retry.
2016-01-24 03:02:34 +00:00
promNotifierBackendErrorsTotal . WithLabelValues ( notifierName ) . Inc ( )
2016-02-09 21:07:57 +00:00
log . Errorf ( "could not send notification '%s' via notifier '%s': %v" , notification . Name , notifierName , err )
2016-01-21 23:09:23 +00:00
backOff = timeutil . ExpBackoff ( backOff , maxBackOff )
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
}
2016-01-22 20:59:46 +00:00
log . Infof ( "successfully sent notification '%s'\n" , notification . Name )
2015-12-15 18:23:57 +00:00
return true , false
2015-11-13 19:11:28 +00:00
}