2021-03-05 10:36:05 +00:00
package kubernetes
import (
"encoding/json"
"errors"
2021-03-11 14:41:09 +00:00
"flag"
2021-03-05 10:36:05 +00:00
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
2021-03-11 14:41:09 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
2021-03-05 10:36:05 +00:00
"github.com/VictoriaMetrics/metrics"
)
2021-03-11 14:41:09 +00:00
var apiServerTimeout = flag . Duration ( "promscrape.kubernetes.apiServerTimeout" , 30 * time . Minute , "How frequently to reload the full state from Kuberntes API server" )
2021-03-05 10:36:05 +00:00
// WatchEvent is a watch event returned from API server endpoints if `watch=1` query arg is set.
//
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes
type WatchEvent struct {
Type string
Object json . RawMessage
}
// object is any Kubernetes object.
type object interface {
key ( ) string
getTargetLabels ( aw * apiWatcher ) [ ] map [ string ] string
}
// parseObjectFunc must parse object from the given data.
type parseObjectFunc func ( data [ ] byte ) ( object , error )
2021-03-11 14:41:09 +00:00
// parseObjectListFunc must parse objectList from the given r.
type parseObjectListFunc func ( r io . Reader ) ( map [ string ] object , ListMeta , error )
2021-03-05 10:36:05 +00:00
// apiWatcher is used for watching for Kuberntes object changes and caching their latest states.
type apiWatcher struct {
// Kubenetes API server address in the form http://api-server
apiServer string
2021-03-11 14:41:09 +00:00
// ac contains auth config for communicating with apiServer
ac * promauth . Config
2021-03-05 10:36:05 +00:00
2021-03-11 14:41:09 +00:00
// sdc contains the related SDConfig
sdc * SDConfig
2021-03-05 10:36:05 +00:00
2021-03-11 14:41:09 +00:00
// Constructor for creating ScrapeWork objects from labels
2021-03-05 10:36:05 +00:00
swcFunc ScrapeWorkConstructorFunc
2021-03-11 14:41:09 +00:00
// swos contains a map of ScrapeWork objects for the given apiWatcher
swosByKey map [ string ] [ ] interface { }
swosByKeyLock sync . Mutex
2021-03-05 10:36:05 +00:00
// a map of watchers keyed by request urls
2021-03-11 14:41:09 +00:00
watchersByURL map [ string ] * urlWatcher
watchersByURLLock sync . Mutex
2021-03-05 10:36:05 +00:00
2021-03-11 14:41:09 +00:00
stopCh chan struct { }
wg sync . WaitGroup
2021-03-05 10:36:05 +00:00
}
2021-03-11 14:41:09 +00:00
func newAPIWatcher ( apiServer string , ac * promauth . Config , sdc * SDConfig , swcFunc ScrapeWorkConstructorFunc ) * apiWatcher {
2021-03-05 10:36:05 +00:00
return & apiWatcher {
2021-03-11 14:41:09 +00:00
apiServer : apiServer ,
ac : ac ,
sdc : sdc ,
swcFunc : swcFunc ,
2021-03-05 10:36:05 +00:00
2021-03-11 14:41:09 +00:00
swosByKey : make ( map [ string ] [ ] interface { } ) ,
2021-03-05 10:36:05 +00:00
watchersByURL : make ( map [ string ] * urlWatcher ) ,
2021-03-11 14:41:09 +00:00
stopCh : make ( chan struct { } ) ,
2021-03-05 10:36:05 +00:00
}
}
2021-03-11 14:41:09 +00:00
func ( aw * apiWatcher ) mustStop ( ) {
close ( aw . stopCh )
aw . wg . Wait ( )
}
func ( aw * apiWatcher ) reloadScrapeWorks ( objectsByKey map [ string ] object ) {
swosByKey := make ( map [ string ] [ ] interface { } )
for key , o := range objectsByKey {
labels := o . getTargetLabels ( aw )
swos := getScrapeWorkObjectsForLabels ( aw . swcFunc , labels )
if len ( swos ) > 0 {
swosByKey [ key ] = swos
2021-03-05 10:36:05 +00:00
}
2021-03-11 14:41:09 +00:00
}
aw . swosByKeyLock . Lock ( )
aw . swosByKey = swosByKey
aw . swosByKeyLock . Unlock ( )
}
func ( aw * apiWatcher ) setScrapeWorks ( key string , labels [ ] map [ string ] string ) {
swos := getScrapeWorkObjectsForLabels ( aw . swcFunc , labels )
aw . swosByKeyLock . Lock ( )
if len ( swos ) > 0 {
aw . swosByKey [ key ] = swos
} else {
delete ( aw . swosByKey , key )
}
aw . swosByKeyLock . Unlock ( )
}
func ( aw * apiWatcher ) removeScrapeWorks ( key string ) {
aw . swosByKeyLock . Lock ( )
delete ( aw . swosByKey , key )
aw . swosByKeyLock . Unlock ( )
}
func getScrapeWorkObjectsForLabels ( swcFunc ScrapeWorkConstructorFunc , labelss [ ] map [ string ] string ) [ ] interface { } {
swos := make ( [ ] interface { } , 0 , len ( labelss ) )
for _ , labels := range labelss {
swo := swcFunc ( labels )
// The reflect check is needed because of https://mangatmodi.medium.com/go-check-nil-interface-the-right-way-d142776edef1
if swo != nil && ! reflect . ValueOf ( swo ) . IsNil ( ) {
swos = append ( swos , swo )
2021-03-05 10:36:05 +00:00
}
}
2021-03-11 14:41:09 +00:00
return swos
}
// getScrapeWorkObjects returns all the ScrapeWork objects for the given aw.
func ( aw * apiWatcher ) getScrapeWorkObjects ( ) [ ] interface { } {
2021-03-13 13:18:47 +00:00
aw . startWatchersForRole ( aw . sdc . Role , true )
2021-03-11 14:41:09 +00:00
aw . swosByKeyLock . Lock ( )
defer aw . swosByKeyLock . Unlock ( )
size := 0
for _ , swosLocal := range aw . swosByKey {
size += len ( swosLocal )
}
swos := make ( [ ] interface { } , 0 , size )
for _ , swosLocal := range aw . swosByKey {
swos = append ( swos , swosLocal ... )
}
2021-03-05 10:36:05 +00:00
return swos
}
// getObjectByRole returns an object with the given (namespace, name) key and the given role.
func ( aw * apiWatcher ) getObjectByRole ( role , namespace , name string ) object {
if aw == nil {
2021-03-11 14:41:09 +00:00
// this is needed for testing
2021-03-05 10:36:05 +00:00
return nil
}
key := namespace + "/" + name
2021-03-13 13:18:47 +00:00
aw . startWatchersForRole ( role , false )
2021-03-11 14:41:09 +00:00
aw . watchersByURLLock . Lock ( )
defer aw . watchersByURLLock . Unlock ( )
2021-03-05 10:36:05 +00:00
for _ , uw := range aw . watchersByURL {
if uw . role != role {
continue
}
2021-03-11 14:41:09 +00:00
uw . mu . Lock ( )
o := uw . objectsByKey [ key ]
uw . mu . Unlock ( )
2021-03-05 10:36:05 +00:00
if o != nil {
2021-03-11 14:41:09 +00:00
return o
2021-03-05 10:36:05 +00:00
}
}
2021-03-11 14:41:09 +00:00
return nil
2021-03-05 10:36:05 +00:00
}
2021-03-13 13:18:47 +00:00
func ( aw * apiWatcher ) startWatchersForRole ( role string , registerAPIWatcher bool ) {
2021-03-11 14:41:09 +00:00
paths := getAPIPaths ( role , aw . sdc . Namespaces . Names , aw . sdc . Selectors )
2021-03-05 10:36:05 +00:00
for _ , path := range paths {
apiURL := aw . apiServer + path
2021-03-13 13:18:47 +00:00
aw . startWatcherForURL ( role , apiURL , registerAPIWatcher )
2021-03-05 10:36:05 +00:00
}
}
2021-03-13 13:18:47 +00:00
func ( aw * apiWatcher ) startWatcherForURL ( role , apiURL string , registerAPIWatcher bool ) {
2021-03-11 14:41:09 +00:00
aw . watchersByURLLock . Lock ( )
2021-03-05 10:36:05 +00:00
if aw . watchersByURL [ apiURL ] != nil {
// Watcher for the given path already exists.
2021-03-11 14:41:09 +00:00
aw . watchersByURLLock . Unlock ( )
2021-03-05 10:36:05 +00:00
return
}
2021-03-11 14:41:09 +00:00
uw := getURLWatcher ( role , apiURL , aw . sdc . ProxyURL . URL ( ) , aw . ac )
2021-03-05 10:36:05 +00:00
aw . watchersByURL [ apiURL ] = uw
2021-03-11 14:41:09 +00:00
aw . watchersByURLLock . Unlock ( )
2021-03-13 13:18:47 +00:00
uw . initOnce ( )
if registerAPIWatcher {
uw . addAPIWatcher ( aw )
}
2021-03-05 10:36:05 +00:00
aw . wg . Add ( 1 )
go func ( ) {
defer aw . wg . Done ( )
2021-03-11 14:41:09 +00:00
<- aw . stopCh
2021-03-13 13:18:47 +00:00
if registerAPIWatcher {
uw . removeAPIWatcher ( aw )
}
2021-03-11 14:41:09 +00:00
aw . watchersByURLLock . Lock ( )
2021-03-05 10:36:05 +00:00
delete ( aw . watchersByURL , apiURL )
2021-03-11 14:41:09 +00:00
aw . watchersByURLLock . Unlock ( )
2021-03-05 10:36:05 +00:00
} ( )
}
2021-03-11 14:41:09 +00:00
func getURLWatcher ( role , apiURL string , proxyURL * url . URL , ac * promauth . Config ) * urlWatcher {
key := fmt . Sprintf ( "url=%s, proxyURL=%v, authConfig=%s" , apiURL , proxyURL , ac . String ( ) )
urlWatchersLock . Lock ( )
uw := urlWatchers [ key ]
logger . Infof ( "found watcher for key=%s" , key )
if uw == nil {
uw = newURLWatcher ( role , apiURL , proxyURL , ac )
urlWatchers [ key ] = uw
logger . Infof ( "registered watcher for key=%s" , key )
2021-03-05 10:36:05 +00:00
}
2021-03-11 14:41:09 +00:00
urlWatchersLock . Unlock ( )
return uw
2021-03-05 10:36:05 +00:00
}
2021-03-11 14:41:09 +00:00
var (
urlWatchersLock sync . Mutex
urlWatchers = make ( map [ string ] * urlWatcher )
)
2021-03-05 10:36:05 +00:00
// urlWatcher watches for an apiURL and updates object states in objectsByKey.
type urlWatcher struct {
2021-03-11 14:41:09 +00:00
role string
apiURL string
authorization string
client * http . Client
2021-03-05 10:36:05 +00:00
parseObject parseObjectFunc
parseObjectList parseObjectListFunc
2021-03-13 13:18:47 +00:00
// once is used for initializing the urlWatcher only once
once sync . Once
2021-03-11 14:41:09 +00:00
// mu protects aws, objectsByKey and resourceVersion
mu sync . Mutex
// aws contains registered apiWatcher objects
aws map [ * apiWatcher ] struct { }
2021-03-05 10:36:05 +00:00
// objectsByKey contains the latest state for objects obtained from apiURL
2021-03-11 14:41:09 +00:00
objectsByKey map [ string ] object
2021-03-05 15:29:54 +00:00
2021-03-07 17:50:01 +00:00
resourceVersion string
2021-03-05 10:36:05 +00:00
2021-03-11 14:41:09 +00:00
objectsCount * metrics . Counter
objectsAdded * metrics . Counter
objectsRemoved * metrics . Counter
objectsUpdated * metrics . Counter
2021-03-05 10:36:05 +00:00
}
2021-03-11 14:41:09 +00:00
func newURLWatcher ( role , apiURL string , proxyURL * url . URL , ac * promauth . Config ) * urlWatcher {
var proxy func ( * http . Request ) ( * url . URL , error )
if proxyURL != nil {
proxy = http . ProxyURL ( proxyURL )
}
client := & http . Client {
Transport : & http . Transport {
TLSClientConfig : ac . NewTLSConfig ( ) ,
Proxy : proxy ,
TLSHandshakeTimeout : 10 * time . Second ,
IdleConnTimeout : * apiServerTimeout ,
} ,
Timeout : * apiServerTimeout ,
}
parseObject , parseObjectList := getObjectParsersForRole ( role )
metrics . GetOrCreateCounter ( fmt . Sprintf ( ` vm_promscrape_discovery_kubernetes_url_watchers { role=%q} ` , role ) ) . Inc ( )
uw := & urlWatcher {
role : role ,
apiURL : apiURL ,
authorization : ac . Authorization ,
client : client ,
2021-03-05 10:36:05 +00:00
parseObject : parseObject ,
parseObjectList : parseObjectList ,
2021-03-11 14:41:09 +00:00
aws : make ( map [ * apiWatcher ] struct { } ) ,
objectsByKey : make ( map [ string ] object ) ,
2021-03-05 10:36:05 +00:00
2021-03-11 14:41:09 +00:00
objectsCount : metrics . GetOrCreateCounter ( fmt . Sprintf ( ` vm_promscrape_discovery_kubernetes_objects { role=%q} ` , role ) ) ,
objectsAdded : metrics . GetOrCreateCounter ( fmt . Sprintf ( ` vm_promscrape_discovery_kubernetes_objects_added_total { role=%q} ` , role ) ) ,
objectsRemoved : metrics . GetOrCreateCounter ( fmt . Sprintf ( ` vm_promscrape_discovery_kubernetes_objects_removed_total { role=%q} ` , role ) ) ,
objectsUpdated : metrics . GetOrCreateCounter ( fmt . Sprintf ( ` vm_promscrape_discovery_kubernetes_objects_updated_total { role=%q} ` , role ) ) ,
}
return uw
}
2021-03-05 10:36:05 +00:00
2021-03-13 13:18:47 +00:00
func ( uw * urlWatcher ) initOnce ( ) {
uw . once . Do ( func ( ) {
uw . reloadObjects ( )
go uw . watchForUpdates ( )
} )
}
2021-03-11 14:41:09 +00:00
func ( uw * urlWatcher ) addAPIWatcher ( aw * apiWatcher ) {
uw . mu . Lock ( )
if _ , ok := uw . aws [ aw ] ; ok {
logger . Panicf ( "BUG: aw=%p has been already added" , aw )
2021-03-05 10:36:05 +00:00
}
2021-03-11 14:41:09 +00:00
uw . aws [ aw ] = struct { } { }
2021-03-13 13:18:47 +00:00
aw . reloadScrapeWorks ( uw . objectsByKey )
2021-03-11 14:41:09 +00:00
uw . mu . Unlock ( )
2021-03-05 10:36:05 +00:00
}
2021-03-11 14:41:09 +00:00
func ( uw * urlWatcher ) removeAPIWatcher ( aw * apiWatcher ) {
uw . mu . Lock ( )
if _ , ok := uw . aws [ aw ] ; ! ok {
logger . Panicf ( "BUG: aw=%p is missing" , aw )
}
delete ( uw . aws , aw )
uw . mu . Unlock ( )
}
// doRequest performs http request to the given requestURL.
func ( uw * urlWatcher ) doRequest ( requestURL string ) ( * http . Response , error ) {
req , err := http . NewRequest ( "GET" , requestURL , nil )
if err != nil {
logger . Fatalf ( "cannot create a request for %q: %s" , requestURL , err )
}
if uw . authorization != "" {
req . Header . Set ( "Authorization" , uw . authorization )
}
return uw . client . Do ( req )
2021-03-07 17:50:01 +00:00
}
2021-03-10 13:06:33 +00:00
func ( uw * urlWatcher ) setResourceVersion ( resourceVersion string ) {
2021-03-07 17:50:01 +00:00
uw . mu . Lock ( )
2021-03-10 13:06:33 +00:00
uw . resourceVersion = resourceVersion
2021-03-07 17:50:01 +00:00
uw . mu . Unlock ( )
}
2021-03-05 10:36:05 +00:00
// reloadObjects reloads objects to the latest state and returns resourceVersion for the latest state.
func ( uw * urlWatcher ) reloadObjects ( ) string {
2021-03-07 17:50:01 +00:00
uw . mu . Lock ( )
resourceVersion := uw . resourceVersion
uw . mu . Unlock ( )
if resourceVersion != "" {
2021-03-11 14:41:09 +00:00
// Fast path - there is no need in reloading the objects.
2021-03-07 17:50:01 +00:00
return resourceVersion
}
2021-03-05 10:36:05 +00:00
requestURL := uw . apiURL
2021-03-11 14:41:09 +00:00
resp , err := uw . doRequest ( requestURL )
2021-03-05 10:36:05 +00:00
if err != nil {
2021-03-11 14:41:09 +00:00
logger . Errorf ( "cannot perform request to %q: %s" , requestURL , err )
2021-03-05 10:36:05 +00:00
return ""
}
if resp . StatusCode != http . StatusOK {
2021-03-11 14:41:09 +00:00
body , _ := ioutil . ReadAll ( resp . Body )
_ = resp . Body . Close ( )
2021-03-05 10:36:05 +00:00
logger . Errorf ( "unexpected status code for request to %q: %d; want %d; response: %q" , requestURL , resp . StatusCode , http . StatusOK , body )
return ""
}
2021-03-11 14:41:09 +00:00
objectsByKey , metadata , err := uw . parseObjectList ( resp . Body )
_ = resp . Body . Close ( )
2021-03-05 10:36:05 +00:00
if err != nil {
2021-03-11 14:41:09 +00:00
logger . Errorf ( "cannot parse objects from %q: %s" , requestURL , err )
2021-03-05 10:36:05 +00:00
return ""
}
2021-03-11 14:41:09 +00:00
uw . mu . Lock ( )
var updated , removed , added int
for key := range uw . objectsByKey {
if o , ok := objectsByKey [ key ] ; ok {
uw . objectsByKey [ key ] = o
updated ++
} else {
delete ( uw . objectsByKey , key )
removed ++
2021-03-05 10:36:05 +00:00
}
}
2021-03-11 14:41:09 +00:00
for key , o := range objectsByKey {
if _ , ok := uw . objectsByKey [ key ] ; ! ok {
uw . objectsByKey [ key ] = o
added ++
}
}
uw . objectsUpdated . Add ( updated )
uw . objectsRemoved . Add ( removed )
uw . objectsAdded . Add ( added )
uw . objectsCount . Add ( added - removed )
2021-03-07 17:50:01 +00:00
uw . resourceVersion = metadata . ResourceVersion
2021-03-05 10:36:05 +00:00
uw . mu . Unlock ( )
2021-03-07 17:50:01 +00:00
2021-03-11 14:41:09 +00:00
for _ , aw := range uw . getAPIWatchers ( ) {
aw . reloadScrapeWorks ( objectsByKey )
}
logger . Infof ( "loaded %d objects from %q" , len ( objectsByKey ) , requestURL )
2021-03-05 10:36:05 +00:00
return metadata . ResourceVersion
}
2021-03-11 14:41:09 +00:00
func ( uw * urlWatcher ) getAPIWatchers ( ) [ ] * apiWatcher {
uw . mu . Lock ( )
aws := make ( [ ] * apiWatcher , 0 , len ( uw . aws ) )
for aw := range uw . aws {
aws = append ( aws , aw )
2021-03-05 10:36:05 +00:00
}
2021-03-11 14:41:09 +00:00
uw . mu . Unlock ( )
return aws
2021-03-05 10:36:05 +00:00
}
2021-03-07 17:50:01 +00:00
// watchForUpdates watches for object updates starting from uw.resourceVersion and updates the corresponding objects to the latest state.
2021-03-05 10:36:05 +00:00
//
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes
2021-03-07 17:50:01 +00:00
func ( uw * urlWatcher ) watchForUpdates ( ) {
2021-03-05 10:36:05 +00:00
backoffDelay := time . Second
maxBackoffDelay := 30 * time . Second
backoffSleep := func ( ) {
time . Sleep ( backoffDelay )
backoffDelay *= 2
if backoffDelay > maxBackoffDelay {
backoffDelay = maxBackoffDelay
}
}
apiURL := uw . apiURL
delimiter := "?"
if strings . Contains ( apiURL , "?" ) {
delimiter = "&"
}
2021-03-11 14:41:09 +00:00
timeoutSeconds := time . Duration ( 0.9 * float64 ( uw . client . Timeout ) ) . Seconds ( )
2021-03-10 13:06:33 +00:00
apiURL += delimiter + "watch=1&allowWatchBookmarks=true&timeoutSeconds=" + strconv . Itoa ( int ( timeoutSeconds ) )
2021-03-05 10:36:05 +00:00
for {
2021-03-07 17:50:01 +00:00
resourceVersion := uw . reloadObjects ( )
2021-03-05 10:36:05 +00:00
requestURL := apiURL
if resourceVersion != "" {
requestURL += "&resourceVersion=" + url . QueryEscape ( resourceVersion )
}
2021-03-11 14:41:09 +00:00
resp , err := uw . doRequest ( requestURL )
2021-03-05 10:36:05 +00:00
if err != nil {
2021-03-13 13:18:47 +00:00
logger . Errorf ( "cannot perform request to %q: %s" , requestURL , err )
2021-03-05 10:36:05 +00:00
backoffSleep ( )
continue
}
if resp . StatusCode != http . StatusOK {
body , _ := ioutil . ReadAll ( resp . Body )
_ = resp . Body . Close ( )
logger . Errorf ( "unexpected status code for request to %q: %d; want %d; response: %q" , requestURL , resp . StatusCode , http . StatusOK , body )
if resp . StatusCode == 410 {
// There is no need for sleep on 410 error. See https://kubernetes.io/docs/reference/using-api/api-concepts/#410-gone-responses
backoffDelay = time . Second
2021-03-10 13:06:33 +00:00
uw . setResourceVersion ( "" )
2021-03-05 10:36:05 +00:00
} else {
backoffSleep ( )
}
continue
}
backoffDelay = time . Second
err = uw . readObjectUpdateStream ( resp . Body )
_ = resp . Body . Close ( )
if err != nil {
if ! errors . Is ( err , io . EOF ) {
logger . Errorf ( "error when reading WatchEvent stream from %q: %s" , requestURL , err )
}
backoffSleep ( )
continue
}
}
}
// readObjectUpdateStream reads Kuberntes watch events from r and updates locally cached objects according to the received events.
func ( uw * urlWatcher ) readObjectUpdateStream ( r io . Reader ) error {
d := json . NewDecoder ( r )
var we WatchEvent
for {
if err := d . Decode ( & we ) ; err != nil {
return err
}
switch we . Type {
case "ADDED" , "MODIFIED" :
2021-03-11 11:06:40 +00:00
o , err := uw . parseObject ( we . Object )
if err != nil {
return err
}
key := o . key ( )
2021-03-05 10:36:05 +00:00
uw . mu . Lock ( )
2021-03-11 14:41:09 +00:00
if _ , ok := uw . objectsByKey [ key ] ; ! ok {
uw . objectsCount . Inc ( )
uw . objectsAdded . Inc ( )
2021-03-05 10:36:05 +00:00
} else {
2021-03-11 14:41:09 +00:00
uw . objectsUpdated . Inc ( )
2021-03-05 10:36:05 +00:00
}
2021-03-11 14:41:09 +00:00
uw . objectsByKey [ key ] = o
2021-03-05 10:36:05 +00:00
uw . mu . Unlock ( )
2021-03-11 14:41:09 +00:00
for _ , aw := range uw . getAPIWatchers ( ) {
labels := o . getTargetLabels ( aw )
aw . setScrapeWorks ( key , labels )
}
2021-03-05 10:36:05 +00:00
case "DELETED" :
2021-03-11 11:06:40 +00:00
o , err := uw . parseObject ( we . Object )
if err != nil {
return err
}
key := o . key ( )
2021-03-05 10:36:05 +00:00
uw . mu . Lock ( )
2021-03-11 14:41:09 +00:00
if _ , ok := uw . objectsByKey [ key ] ; ok {
uw . objectsCount . Dec ( )
uw . objectsRemoved . Inc ( )
delete ( uw . objectsByKey , key )
}
2021-03-05 10:36:05 +00:00
uw . mu . Unlock ( )
2021-03-11 14:41:09 +00:00
for _ , aw := range uw . getAPIWatchers ( ) {
aw . removeScrapeWorks ( key )
}
2021-03-10 13:06:33 +00:00
case "BOOKMARK" :
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#watch-bookmarks
2021-03-11 11:06:40 +00:00
bm , err := parseBookmark ( we . Object )
if err != nil {
return fmt . Errorf ( "cannot parse bookmark from %q: %w" , we . Object , err )
}
uw . setResourceVersion ( bm . Metadata . ResourceVersion )
2021-03-05 10:36:05 +00:00
default :
return fmt . Errorf ( "unexpected WatchEvent type %q for role %q" , we . Type , uw . role )
}
}
}
2021-03-11 11:06:40 +00:00
// Bookmark is a bookmark from Kubernetes Watch API.
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#watch-bookmarks
type Bookmark struct {
Metadata struct {
ResourceVersion string
}
}
func parseBookmark ( data [ ] byte ) ( * Bookmark , error ) {
var bm Bookmark
if err := json . Unmarshal ( data , & bm ) ; err != nil {
return nil , err
}
return & bm , nil
}
2021-03-05 10:36:05 +00:00
func getAPIPaths ( role string , namespaces [ ] string , selectors [ ] Selector ) [ ] string {
objectName := getObjectNameByRole ( role )
if objectName == "nodes" || len ( namespaces ) == 0 {
query := joinSelectors ( role , selectors )
path := getAPIPath ( objectName , "" , query )
return [ ] string { path }
}
query := joinSelectors ( role , selectors )
paths := make ( [ ] string , len ( namespaces ) )
for i , namespace := range namespaces {
paths [ i ] = getAPIPath ( objectName , namespace , query )
}
return paths
}
func getAPIPath ( objectName , namespace , query string ) string {
suffix := objectName
if namespace != "" {
suffix = "namespaces/" + namespace + "/" + objectName
}
if len ( query ) > 0 {
suffix += "?" + query
}
if objectName == "ingresses" {
return "/apis/networking.k8s.io/v1beta1/" + suffix
}
if objectName == "endpointslices" {
return "/apis/discovery.k8s.io/v1beta1/" + suffix
}
return "/api/v1/" + suffix
}
func joinSelectors ( role string , selectors [ ] Selector ) string {
var labelSelectors , fieldSelectors [ ] string
for _ , s := range selectors {
if s . Role != role {
continue
}
if s . Label != "" {
labelSelectors = append ( labelSelectors , s . Label )
}
if s . Field != "" {
fieldSelectors = append ( fieldSelectors , s . Field )
}
}
var args [ ] string
if len ( labelSelectors ) > 0 {
args = append ( args , "labelSelector=" + url . QueryEscape ( strings . Join ( labelSelectors , "," ) ) )
}
if len ( fieldSelectors ) > 0 {
args = append ( args , "fieldSelector=" + url . QueryEscape ( strings . Join ( fieldSelectors , "," ) ) )
}
return strings . Join ( args , "&" )
}
func getObjectNameByRole ( role string ) string {
switch role {
case "node" :
return "nodes"
case "pod" :
return "pods"
case "service" :
return "services"
case "endpoints" :
return "endpoints"
case "endpointslices" :
return "endpointslices"
case "ingress" :
return "ingresses"
default :
logger . Panicf ( "BUG: unknonw role=%q" , role )
return ""
}
}
func getObjectParsersForRole ( role string ) ( parseObjectFunc , parseObjectListFunc ) {
switch role {
case "node" :
return parseNode , parseNodeList
case "pod" :
return parsePod , parsePodList
case "service" :
return parseService , parseServiceList
case "endpoints" :
return parseEndpoints , parseEndpointsList
case "endpointslices" :
return parseEndpointSlice , parseEndpointSliceList
case "ingress" :
return parseIngress , parseIngressList
default :
logger . Panicf ( "BUG: unsupported role=%q" , role )
return nil , nil
}
}