mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -08:00
Refactor Kubernetes Discovery Part 2: Refactoring
- Do initial listing and syncing to scrape manager, then register event handlers may lost events happening in listing and syncing (if it lasted a long time). We should register event handlers at the very begining, before processing just wait until informers synced (sync in informer will list all objects and call OnUpdate event handler). - Use a queue then we don't block event callbacks and an object will be processed only once if added multiple times before it being processed. - Fix bug in `serviceUpdate` in endpoints.go, we should build endpoints when `exists && err == nil`. Add `^TestEndpointsDiscoveryWithService` tests to test this feature. Testing: - Use `k8s.io/client-go` testing framework and fake implementations which are more robust and reliable for testing. - `Test\w+DiscoveryBeforeRun` are used to test objects created before discoverer runs - `Test\w+DiscoveryAdd\w+` are used to test adding objects - `Test\w+DiscoveryDelete\w+` are used to test deleting objects - `Test\w+DiscoveryUpdate\w+` are used to test updating objects - `TestEndpointsDiscoveryWithService\w+` are used to test endpoints events triggered by services - `cache.DeletedFinalStateUnknown` related stuffs are removed, because we don't care deleted objects in store, we only need its name to send a specical `targetgroup.Group` to scrape manager Signed-off-by: Yecheng Fu <cofyc.jackson@gmail.com>
This commit is contained in:
parent
9bc6ced55d
commit
8ceb8f2ae8
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/prometheus/prometheus/discovery/targetgroup"
|
"github.com/prometheus/prometheus/discovery/targetgroup"
|
||||||
apiv1 "k8s.io/client-go/pkg/api/v1"
|
apiv1 "k8s.io/client-go/pkg/api/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Endpoints discovers new endpoint targets.
|
// Endpoints discovers new endpoint targets.
|
||||||
|
@ -38,6 +39,8 @@ type Endpoints struct {
|
||||||
podStore cache.Store
|
podStore cache.Store
|
||||||
endpointsStore cache.Store
|
endpointsStore cache.Store
|
||||||
serviceStore cache.Store
|
serviceStore cache.Store
|
||||||
|
|
||||||
|
queue *workqueue.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEndpoints returns a new endpoints discovery.
|
// NewEndpoints returns a new endpoints discovery.
|
||||||
|
@ -45,7 +48,7 @@ func NewEndpoints(l log.Logger, svc, eps, pod cache.SharedInformer) *Endpoints {
|
||||||
if l == nil {
|
if l == nil {
|
||||||
l = log.NewNopLogger()
|
l = log.NewNopLogger()
|
||||||
}
|
}
|
||||||
ep := &Endpoints{
|
e := &Endpoints{
|
||||||
logger: l,
|
logger: l,
|
||||||
endpointsInf: eps,
|
endpointsInf: eps,
|
||||||
endpointsStore: eps.GetStore(),
|
endpointsStore: eps.GetStore(),
|
||||||
|
@ -53,67 +56,21 @@ func NewEndpoints(l log.Logger, svc, eps, pod cache.SharedInformer) *Endpoints {
|
||||||
serviceStore: svc.GetStore(),
|
serviceStore: svc.GetStore(),
|
||||||
podInf: pod,
|
podInf: pod,
|
||||||
podStore: pod.GetStore(),
|
podStore: pod.GetStore(),
|
||||||
}
|
queue: workqueue.NewNamed("endpoints"),
|
||||||
|
|
||||||
return ep
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run implements the Discoverer interface.
|
|
||||||
func (e *Endpoints) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
|
||||||
// Send full initial set of endpoint targets.
|
|
||||||
var initial []*targetgroup.Group
|
|
||||||
|
|
||||||
for _, o := range e.endpointsStore.List() {
|
|
||||||
tg := e.buildEndpoints(o.(*apiv1.Endpoints))
|
|
||||||
initial = append(initial, tg)
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case ch <- initial:
|
|
||||||
}
|
|
||||||
// Send target groups for pod updates.
|
|
||||||
send := func(tg *targetgroup.Group) {
|
|
||||||
if tg == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
level.Debug(e.logger).Log("msg", "endpoints update", "tg", fmt.Sprintf("%#v", tg))
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case ch <- []*targetgroup.Group{tg}:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
e.endpointsInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
e.endpointsInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
AddFunc: func(o interface{}) {
|
AddFunc: func(o interface{}) {
|
||||||
eventCount.WithLabelValues("endpoints", "add").Inc()
|
eventCount.WithLabelValues("endpoints", "add").Inc()
|
||||||
|
e.enqueue(o)
|
||||||
eps, err := convertToEndpoints(o)
|
|
||||||
if err != nil {
|
|
||||||
level.Error(e.logger).Log("msg", "converting to Endpoints object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
send(e.buildEndpoints(eps))
|
|
||||||
},
|
},
|
||||||
UpdateFunc: func(_, o interface{}) {
|
UpdateFunc: func(_, o interface{}) {
|
||||||
eventCount.WithLabelValues("endpoints", "update").Inc()
|
eventCount.WithLabelValues("endpoints", "update").Inc()
|
||||||
|
e.enqueue(o)
|
||||||
eps, err := convertToEndpoints(o)
|
|
||||||
if err != nil {
|
|
||||||
level.Error(e.logger).Log("msg", "converting to Endpoints object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
send(e.buildEndpoints(eps))
|
|
||||||
},
|
},
|
||||||
DeleteFunc: func(o interface{}) {
|
DeleteFunc: func(o interface{}) {
|
||||||
eventCount.WithLabelValues("endpoints", "delete").Inc()
|
eventCount.WithLabelValues("endpoints", "delete").Inc()
|
||||||
|
e.enqueue(o)
|
||||||
eps, err := convertToEndpoints(o)
|
|
||||||
if err != nil {
|
|
||||||
level.Error(e.logger).Log("msg", "converting to Endpoints object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
send(&targetgroup.Group{Source: endpointsSource(eps)})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -128,9 +85,10 @@ func (e *Endpoints) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
ep.Namespace = svc.Namespace
|
ep.Namespace = svc.Namespace
|
||||||
ep.Name = svc.Name
|
ep.Name = svc.Name
|
||||||
obj, exists, err := e.endpointsStore.Get(ep)
|
obj, exists, err := e.endpointsStore.Get(ep)
|
||||||
if exists && err != nil {
|
if exists && err == nil {
|
||||||
send(e.buildEndpoints(obj.(*apiv1.Endpoints)))
|
e.enqueue(obj.(*apiv1.Endpoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
level.Error(e.logger).Log("msg", "retrieving endpoints failed", "err", err)
|
level.Error(e.logger).Log("msg", "retrieving endpoints failed", "err", err)
|
||||||
}
|
}
|
||||||
|
@ -152,31 +110,102 @@ func (e *Endpoints) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Endpoints) enqueue(obj interface{}) {
|
||||||
|
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.queue.Add(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run implements the Discoverer interface.
|
||||||
|
func (e *Endpoints) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
|
defer e.queue.ShutDown()
|
||||||
|
|
||||||
|
cacheSyncs := []cache.InformerSynced{
|
||||||
|
e.endpointsInf.HasSynced,
|
||||||
|
e.serviceInf.HasSynced,
|
||||||
|
e.podInf.HasSynced,
|
||||||
|
}
|
||||||
|
if !cache.WaitForCacheSync(ctx.Done(), cacheSyncs...) {
|
||||||
|
level.Error(e.logger).Log("msg", "endpoints informer unable to sync cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send target groups for pod updates.
|
||||||
|
send := func(tg *targetgroup.Group) {
|
||||||
|
if tg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
level.Debug(e.logger).Log("msg", "endpoints update", "tg", fmt.Sprintf("%#v", tg))
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case ch <- []*targetgroup.Group{tg}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for e.process(send) {
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Block until the target provider is explicitly canceled.
|
// Block until the target provider is explicitly canceled.
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Endpoints) process(send func(tg *targetgroup.Group)) bool {
|
||||||
|
keyObj, quit := e.queue.Get()
|
||||||
|
if quit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer e.queue.Done(keyObj)
|
||||||
|
key := keyObj.(string)
|
||||||
|
|
||||||
|
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||||
|
if err != nil {
|
||||||
|
level.Error(e.logger).Log("msg", "spliting key failed", "key", key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
o, exists, err := e.endpointsStore.GetByKey(key)
|
||||||
|
if err != nil {
|
||||||
|
level.Error(e.logger).Log("msg", "getting object from store failed", "key", key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
send(&targetgroup.Group{Source: endpointsSourceFromNamespaceAndName(namespace, name)})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
eps, err := convertToEndpoints(o)
|
||||||
|
if err != nil {
|
||||||
|
level.Error(e.logger).Log("msg", "converting to Endpoints object failed", "err", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
send(e.buildEndpoints(eps))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func convertToEndpoints(o interface{}) (*apiv1.Endpoints, error) {
|
func convertToEndpoints(o interface{}) (*apiv1.Endpoints, error) {
|
||||||
endpoints, ok := o.(*apiv1.Endpoints)
|
endpoints, ok := o.(*apiv1.Endpoints)
|
||||||
if ok {
|
if ok {
|
||||||
return endpoints, nil
|
return endpoints, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
deletedState, ok := o.(cache.DeletedFinalStateUnknown)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("Received unexpected object: %v", o)
|
return nil, fmt.Errorf("Received unexpected object: %v", o)
|
||||||
}
|
}
|
||||||
endpoints, ok = deletedState.Obj.(*apiv1.Endpoints)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("DeletedFinalStateUnknown contained non-Endpoints object: %v", deletedState.Obj)
|
|
||||||
}
|
|
||||||
return endpoints, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func endpointsSource(ep *apiv1.Endpoints) string {
|
func endpointsSource(ep *apiv1.Endpoints) string {
|
||||||
return "endpoints/" + ep.ObjectMeta.Namespace + "/" + ep.ObjectMeta.Name
|
return "endpoints/" + ep.ObjectMeta.Namespace + "/" + ep.ObjectMeta.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func endpointsSourceFromNamespaceAndName(namespace, name string) string {
|
||||||
|
return "endpoints/" + namespace + "/" + name
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
endpointsNameLabel = metaLabelPrefix + "endpoints_name"
|
endpointsNameLabel = metaLabelPrefix + "endpoints_name"
|
||||||
endpointReadyLabel = metaLabelPrefix + "endpoint_ready"
|
endpointReadyLabel = metaLabelPrefix + "endpoint_ready"
|
||||||
|
|
|
@ -21,24 +21,8 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/client-go/pkg/api/v1"
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func endpointsStoreKeyFunc(obj interface{}) (string, error) {
|
|
||||||
return obj.(*v1.Endpoints).ObjectMeta.Name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakeEndpointsInformer() *fakeInformer {
|
|
||||||
return newFakeInformer(endpointsStoreKeyFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTestEndpointsDiscovery() (*Endpoints, *fakeInformer, *fakeInformer, *fakeInformer) {
|
|
||||||
svc := newFakeServiceInformer()
|
|
||||||
eps := newFakeEndpointsInformer()
|
|
||||||
pod := newFakePodInformer()
|
|
||||||
return NewEndpoints(nil, svc, eps, pod), svc, eps, pod
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeEndpoints() *v1.Endpoints {
|
func makeEndpoints() *v1.Endpoints {
|
||||||
return &v1.Endpoints{
|
return &v1.Endpoints{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
@ -83,14 +67,19 @@ func makeEndpoints() *v1.Endpoints {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointsDiscoveryInitial(t *testing.T) {
|
func TestEndpointsDiscoveryBeforeRun(t *testing.T) {
|
||||||
n, _, eps, _ := makeTestEndpointsDiscovery()
|
n, c, w := makeDiscovery(RoleEndpoint, NamespaceDiscovery{})
|
||||||
eps.GetStore().Add(makeEndpoints())
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
expectedInitial: []*targetgroup.Group{
|
beforeRun: func() {
|
||||||
{
|
obj := makeEndpoints()
|
||||||
|
c.CoreV1().Endpoints(obj.Namespace).Create(obj)
|
||||||
|
w.Endpoints().Add(obj)
|
||||||
|
},
|
||||||
|
expectedMaxItems: 1,
|
||||||
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
|
"endpoints/default/testendpoints": {
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__address__": "1.2.3.4:9000",
|
"__address__": "1.2.3.4:9000",
|
||||||
|
@ -122,8 +111,7 @@ func TestEndpointsDiscoveryInitial(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointsDiscoveryAdd(t *testing.T) {
|
func TestEndpointsDiscoveryAdd(t *testing.T) {
|
||||||
n, _, eps, pods := makeTestEndpointsDiscovery()
|
obj := &v1.Pod{
|
||||||
pods.GetStore().Add(&v1.Pod{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "testpod",
|
Name: "testpod",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
|
@ -158,14 +146,13 @@ func TestEndpointsDiscoveryAdd(t *testing.T) {
|
||||||
HostIP: "2.3.4.5",
|
HostIP: "2.3.4.5",
|
||||||
PodIP: "1.2.3.4",
|
PodIP: "1.2.3.4",
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
n, c, w := makeDiscovery(RoleEndpoint, NamespaceDiscovery{}, obj)
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() {
|
afterStart: func() {
|
||||||
go func() {
|
obj := &v1.Endpoints{
|
||||||
eps.Add(
|
|
||||||
&v1.Endpoints{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "testendpoints",
|
Name: "testendpoints",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
|
@ -191,12 +178,13 @@ func TestEndpointsDiscoveryAdd(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
c.CoreV1().Endpoints(obj.Namespace).Create(obj)
|
||||||
|
w.Endpoints().Add(obj)
|
||||||
},
|
},
|
||||||
)
|
expectedMaxItems: 1,
|
||||||
}()
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
},
|
"endpoints/default/testendpoints": {
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__address__": "4.3.2.1:9000",
|
"__address__": "4.3.2.1:9000",
|
||||||
|
@ -239,29 +227,18 @@ func TestEndpointsDiscoveryAdd(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointsDiscoveryDelete(t *testing.T) {
|
func TestEndpointsDiscoveryDelete(t *testing.T) {
|
||||||
n, _, eps, _ := makeTestEndpointsDiscovery()
|
n, c, w := makeDiscovery(RoleEndpoint, NamespaceDiscovery{}, makeEndpoints())
|
||||||
eps.GetStore().Add(makeEndpoints())
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() { go func() { eps.Delete(makeEndpoints()) }() },
|
afterStart: func() {
|
||||||
expectedRes: []*targetgroup.Group{
|
obj := makeEndpoints()
|
||||||
{
|
c.CoreV1().Endpoints(obj.Namespace).Delete(obj.Name, &metav1.DeleteOptions{})
|
||||||
Source: "endpoints/default/testendpoints",
|
w.Endpoints().Delete(obj)
|
||||||
},
|
},
|
||||||
},
|
expectedMaxItems: 2,
|
||||||
}.Run(t)
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
}
|
"endpoints/default/testendpoints": {
|
||||||
|
|
||||||
func TestEndpointsDiscoveryDeleteUnknownCacheState(t *testing.T) {
|
|
||||||
n, _, eps, _ := makeTestEndpointsDiscovery()
|
|
||||||
eps.GetStore().Add(makeEndpoints())
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
|
||||||
discovery: n,
|
|
||||||
afterStart: func() { go func() { eps.Delete(cache.DeletedFinalStateUnknown{Obj: makeEndpoints()}) }() },
|
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Source: "endpoints/default/testendpoints",
|
Source: "endpoints/default/testendpoints",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -269,14 +246,12 @@ func TestEndpointsDiscoveryDeleteUnknownCacheState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointsDiscoveryUpdate(t *testing.T) {
|
func TestEndpointsDiscoveryUpdate(t *testing.T) {
|
||||||
n, _, eps, _ := makeTestEndpointsDiscovery()
|
n, c, w := makeDiscovery(RoleEndpoint, NamespaceDiscovery{}, makeEndpoints())
|
||||||
eps.GetStore().Add(makeEndpoints())
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() {
|
afterStart: func() {
|
||||||
go func() {
|
obj := &v1.Endpoints{
|
||||||
eps.Update(&v1.Endpoints{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "testendpoints",
|
Name: "testendpoints",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
|
@ -311,11 +286,13 @@ func TestEndpointsDiscoveryUpdate(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
}()
|
c.CoreV1().Endpoints(obj.Namespace).Update(obj)
|
||||||
|
w.Endpoints().Modify(obj)
|
||||||
},
|
},
|
||||||
expectedRes: []*targetgroup.Group{
|
expectedMaxItems: 2,
|
||||||
{
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
|
"endpoints/default/testendpoints": {
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__address__": "1.2.3.4:9000",
|
"__address__": "1.2.3.4:9000",
|
||||||
|
@ -341,24 +318,24 @@ func TestEndpointsDiscoveryUpdate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointsDiscoveryEmptySubsets(t *testing.T) {
|
func TestEndpointsDiscoveryEmptySubsets(t *testing.T) {
|
||||||
n, _, eps, _ := makeTestEndpointsDiscovery()
|
n, c, w := makeDiscovery(RoleEndpoint, NamespaceDiscovery{}, makeEndpoints())
|
||||||
eps.GetStore().Add(makeEndpoints())
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() {
|
afterStart: func() {
|
||||||
go func() {
|
obj := &v1.Endpoints{
|
||||||
eps.Update(&v1.Endpoints{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "testendpoints",
|
Name: "testendpoints",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
},
|
},
|
||||||
Subsets: []v1.EndpointSubset{},
|
Subsets: []v1.EndpointSubset{},
|
||||||
})
|
}
|
||||||
}()
|
c.CoreV1().Endpoints(obj.Namespace).Update(obj)
|
||||||
|
w.Endpoints().Modify(obj)
|
||||||
},
|
},
|
||||||
expectedRes: []*targetgroup.Group{
|
expectedMaxItems: 2,
|
||||||
{
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
|
"endpoints/default/testendpoints": {
|
||||||
Labels: model.LabelSet{
|
Labels: model.LabelSet{
|
||||||
"__meta_kubernetes_namespace": "default",
|
"__meta_kubernetes_namespace": "default",
|
||||||
"__meta_kubernetes_endpoints_name": "testendpoints",
|
"__meta_kubernetes_endpoints_name": "testendpoints",
|
||||||
|
@ -368,3 +345,124 @@ func TestEndpointsDiscoveryEmptySubsets(t *testing.T) {
|
||||||
},
|
},
|
||||||
}.Run(t)
|
}.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEndpointsDiscoveryWithService(t *testing.T) {
|
||||||
|
n, c, w := makeDiscovery(RoleEndpoint, NamespaceDiscovery{}, makeEndpoints())
|
||||||
|
|
||||||
|
k8sDiscoveryTest{
|
||||||
|
discovery: n,
|
||||||
|
beforeRun: func() {
|
||||||
|
obj := &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testendpoints",
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"app": "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.CoreV1().Services(obj.Namespace).Create(obj)
|
||||||
|
w.Services().Add(obj)
|
||||||
|
},
|
||||||
|
expectedMaxItems: 1,
|
||||||
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
|
"endpoints/default/testendpoints": {
|
||||||
|
Targets: []model.LabelSet{
|
||||||
|
{
|
||||||
|
"__address__": "1.2.3.4:9000",
|
||||||
|
"__meta_kubernetes_endpoint_port_name": "testport",
|
||||||
|
"__meta_kubernetes_endpoint_port_protocol": "TCP",
|
||||||
|
"__meta_kubernetes_endpoint_ready": "true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__address__": "2.3.4.5:9001",
|
||||||
|
"__meta_kubernetes_endpoint_port_name": "testport",
|
||||||
|
"__meta_kubernetes_endpoint_port_protocol": "TCP",
|
||||||
|
"__meta_kubernetes_endpoint_ready": "true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__address__": "2.3.4.5:9001",
|
||||||
|
"__meta_kubernetes_endpoint_port_name": "testport",
|
||||||
|
"__meta_kubernetes_endpoint_port_protocol": "TCP",
|
||||||
|
"__meta_kubernetes_endpoint_ready": "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Labels: model.LabelSet{
|
||||||
|
"__meta_kubernetes_namespace": "default",
|
||||||
|
"__meta_kubernetes_endpoints_name": "testendpoints",
|
||||||
|
"__meta_kubernetes_service_label_app": "test",
|
||||||
|
"__meta_kubernetes_service_name": "testendpoints",
|
||||||
|
},
|
||||||
|
Source: "endpoints/default/testendpoints",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}.Run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEndpointsDiscoveryWithServiceUpdate(t *testing.T) {
|
||||||
|
n, c, w := makeDiscovery(RoleEndpoint, NamespaceDiscovery{}, makeEndpoints())
|
||||||
|
|
||||||
|
k8sDiscoveryTest{
|
||||||
|
discovery: n,
|
||||||
|
beforeRun: func() {
|
||||||
|
obj := &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testendpoints",
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"app": "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.CoreV1().Services(obj.Namespace).Create(obj)
|
||||||
|
w.Services().Add(obj)
|
||||||
|
},
|
||||||
|
afterStart: func() {
|
||||||
|
obj := &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "testendpoints",
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"app": "svc",
|
||||||
|
"component": "testing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.CoreV1().Services(obj.Namespace).Update(obj)
|
||||||
|
w.Services().Modify(obj)
|
||||||
|
},
|
||||||
|
expectedMaxItems: 2,
|
||||||
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
|
"endpoints/default/testendpoints": {
|
||||||
|
Targets: []model.LabelSet{
|
||||||
|
{
|
||||||
|
"__address__": "1.2.3.4:9000",
|
||||||
|
"__meta_kubernetes_endpoint_port_name": "testport",
|
||||||
|
"__meta_kubernetes_endpoint_port_protocol": "TCP",
|
||||||
|
"__meta_kubernetes_endpoint_ready": "true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__address__": "2.3.4.5:9001",
|
||||||
|
"__meta_kubernetes_endpoint_port_name": "testport",
|
||||||
|
"__meta_kubernetes_endpoint_port_protocol": "TCP",
|
||||||
|
"__meta_kubernetes_endpoint_ready": "true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__address__": "2.3.4.5:9001",
|
||||||
|
"__meta_kubernetes_endpoint_port_name": "testport",
|
||||||
|
"__meta_kubernetes_endpoint_port_protocol": "TCP",
|
||||||
|
"__meta_kubernetes_endpoint_ready": "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Labels: model.LabelSet{
|
||||||
|
"__meta_kubernetes_namespace": "default",
|
||||||
|
"__meta_kubernetes_endpoints_name": "testendpoints",
|
||||||
|
"__meta_kubernetes_service_label_app": "svc",
|
||||||
|
"__meta_kubernetes_service_name": "testendpoints",
|
||||||
|
"__meta_kubernetes_service_label_component": "testing",
|
||||||
|
},
|
||||||
|
Source: "endpoints/default/testendpoints",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}.Run(t)
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/prometheus/prometheus/util/strutil"
|
"github.com/prometheus/prometheus/util/strutil"
|
||||||
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ingress implements discovery of Kubernetes ingresss.
|
// Ingress implements discovery of Kubernetes ingresss.
|
||||||
|
@ -31,25 +32,45 @@ type Ingress struct {
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
informer cache.SharedInformer
|
informer cache.SharedInformer
|
||||||
store cache.Store
|
store cache.Store
|
||||||
|
queue *workqueue.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIngress returns a new ingress discovery.
|
// NewIngress returns a new ingress discovery.
|
||||||
func NewIngress(l log.Logger, inf cache.SharedInformer) *Ingress {
|
func NewIngress(l log.Logger, inf cache.SharedInformer) *Ingress {
|
||||||
return &Ingress{logger: l, informer: inf, store: inf.GetStore()}
|
s := &Ingress{logger: l, informer: inf, store: inf.GetStore(), queue: workqueue.NewNamed("ingress")}
|
||||||
|
s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(o interface{}) {
|
||||||
|
eventCount.WithLabelValues("ingress", "add").Inc()
|
||||||
|
s.enqueue(o)
|
||||||
|
},
|
||||||
|
DeleteFunc: func(o interface{}) {
|
||||||
|
eventCount.WithLabelValues("ingress", "delete").Inc()
|
||||||
|
s.enqueue(o)
|
||||||
|
},
|
||||||
|
UpdateFunc: func(_, o interface{}) {
|
||||||
|
eventCount.WithLabelValues("ingress", "update").Inc()
|
||||||
|
s.enqueue(o)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Ingress) enqueue(obj interface{}) {
|
||||||
|
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.queue.Add(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run implements the Discoverer interface.
|
// Run implements the Discoverer interface.
|
||||||
func (s *Ingress) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
func (s *Ingress) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
// Send full initial set of pod targets.
|
defer s.queue.ShutDown()
|
||||||
var initial []*targetgroup.Group
|
|
||||||
for _, o := range s.store.List() {
|
if !cache.WaitForCacheSync(ctx.Done(), s.informer.HasSynced) {
|
||||||
tg := s.buildIngress(o.(*v1beta1.Ingress))
|
level.Error(s.logger).Log("msg", "ingress informer unable to sync cache")
|
||||||
initial = append(initial, tg)
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
return
|
||||||
case ch <- initial:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send target groups for ingress updates.
|
// Send target groups for ingress updates.
|
||||||
|
@ -59,64 +80,64 @@ func (s *Ingress) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
case ch <- []*targetgroup.Group{tg}:
|
case ch <- []*targetgroup.Group{tg}:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
||||||
AddFunc: func(o interface{}) {
|
|
||||||
eventCount.WithLabelValues("ingress", "add").Inc()
|
|
||||||
|
|
||||||
ingress, err := convertToIngress(o)
|
go func() {
|
||||||
if err != nil {
|
for s.process(send) {
|
||||||
level.Error(s.logger).Log("msg", "converting to Ingress object failed", "err", err.Error())
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
send(s.buildIngress(ingress))
|
}()
|
||||||
},
|
|
||||||
DeleteFunc: func(o interface{}) {
|
|
||||||
eventCount.WithLabelValues("ingress", "delete").Inc()
|
|
||||||
|
|
||||||
ingress, err := convertToIngress(o)
|
|
||||||
if err != nil {
|
|
||||||
level.Error(s.logger).Log("msg", "converting to Ingress object failed", "err", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
send(&targetgroup.Group{Source: ingressSource(ingress)})
|
|
||||||
},
|
|
||||||
UpdateFunc: func(_, o interface{}) {
|
|
||||||
eventCount.WithLabelValues("ingress", "update").Inc()
|
|
||||||
|
|
||||||
ingress, err := convertToIngress(o)
|
|
||||||
if err != nil {
|
|
||||||
level.Error(s.logger).Log("msg", "converting to Ingress object failed", "err", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
send(s.buildIngress(ingress))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Block until the target provider is explicitly canceled.
|
// Block until the target provider is explicitly canceled.
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Ingress) process(send func(tg *targetgroup.Group)) bool {
|
||||||
|
|
||||||
|
keyObj, quit := s.queue.Get()
|
||||||
|
if quit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer s.queue.Done(keyObj)
|
||||||
|
key := keyObj.(string)
|
||||||
|
|
||||||
|
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
o, exists, err := s.store.GetByKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
send(&targetgroup.Group{Source: ingressSourceFromNamespaceAndName(namespace, name)})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
eps, err := convertToIngress(o)
|
||||||
|
if err != nil {
|
||||||
|
level.Error(s.logger).Log("msg", "converting to Ingress object failed", "err", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
send(s.buildIngress(eps))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func convertToIngress(o interface{}) (*v1beta1.Ingress, error) {
|
func convertToIngress(o interface{}) (*v1beta1.Ingress, error) {
|
||||||
ingress, ok := o.(*v1beta1.Ingress)
|
ingress, ok := o.(*v1beta1.Ingress)
|
||||||
if ok {
|
if ok {
|
||||||
return ingress, nil
|
return ingress, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
deletedState, ok := o.(cache.DeletedFinalStateUnknown)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("Received unexpected object: %v", o)
|
return nil, fmt.Errorf("Received unexpected object: %v", o)
|
||||||
}
|
}
|
||||||
ingress, ok = deletedState.Obj.(*v1beta1.Ingress)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("DeletedFinalStateUnknown contained non-Ingress object: %v", deletedState.Obj)
|
|
||||||
}
|
|
||||||
return ingress, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ingressSource(s *v1beta1.Ingress) string {
|
func ingressSource(s *v1beta1.Ingress) string {
|
||||||
return "ingress/" + s.Namespace + "/" + s.Name
|
return "ingress/" + s.Namespace + "/" + s.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ingressSourceFromNamespaceAndName(namespace, name string) string {
|
||||||
|
return "ingress/" + namespace + "/" + name
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ingressNameLabel = metaLabelPrefix + "ingress_name"
|
ingressNameLabel = metaLabelPrefix + "ingress_name"
|
||||||
ingressLabelPrefix = metaLabelPrefix + "ingress_label_"
|
ingressLabelPrefix = metaLabelPrefix + "ingress_label_"
|
||||||
|
|
|
@ -22,19 +22,6 @@ import (
|
||||||
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ingressStoreKeyFunc(obj interface{}) (string, error) {
|
|
||||||
return obj.(*v1beta1.Ingress).ObjectMeta.Name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakeIngressInformer() *fakeInformer {
|
|
||||||
return newFakeInformer(ingressStoreKeyFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTestIngressDiscovery() (*Ingress, *fakeInformer) {
|
|
||||||
i := newFakeIngressInformer()
|
|
||||||
return NewIngress(nil, i), i
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeIngress(tls []v1beta1.IngressTLS) *v1beta1.Ingress {
|
func makeIngress(tls []v1beta1.IngressTLS) *v1beta1.Ingress {
|
||||||
return &v1beta1.Ingress{
|
return &v1beta1.Ingress{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
@ -77,13 +64,13 @@ func makeIngress(tls []v1beta1.IngressTLS) *v1beta1.Ingress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectedTargetGroups(tls bool) []*targetgroup.Group {
|
func expectedTargetGroups(tls bool) map[string]*targetgroup.Group {
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
if tls {
|
if tls {
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
}
|
}
|
||||||
return []*targetgroup.Group{
|
return map[string]*targetgroup.Group{
|
||||||
{
|
"ingress/default/testingress": {
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__meta_kubernetes_ingress_scheme": lv(scheme),
|
"__meta_kubernetes_ingress_scheme": lv(scheme),
|
||||||
|
@ -115,22 +102,32 @@ func expectedTargetGroups(tls bool) []*targetgroup.Group {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIngressDiscoveryInitial(t *testing.T) {
|
func TestIngressDiscoveryAdd(t *testing.T) {
|
||||||
n, i := makeTestIngressDiscovery()
|
n, c, w := makeDiscovery(RoleIngress, NamespaceDiscovery{Names: []string{"default"}})
|
||||||
i.GetStore().Add(makeIngress(nil))
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
expectedInitial: expectedTargetGroups(false),
|
afterStart: func() {
|
||||||
|
obj := makeIngress(nil)
|
||||||
|
c.ExtensionsV1beta1().Ingresses("default").Create(obj)
|
||||||
|
w.Ingresses().Add(obj)
|
||||||
|
},
|
||||||
|
expectedMaxItems: 1,
|
||||||
|
expectedRes: expectedTargetGroups(false),
|
||||||
}.Run(t)
|
}.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIngressDiscoveryInitialTLS(t *testing.T) {
|
func TestIngressDiscoveryAddTLS(t *testing.T) {
|
||||||
n, i := makeTestIngressDiscovery()
|
n, c, w := makeDiscovery(RoleIngress, NamespaceDiscovery{Names: []string{"default"}})
|
||||||
i.GetStore().Add(makeIngress([]v1beta1.IngressTLS{{}}))
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
expectedInitial: expectedTargetGroups(true),
|
afterStart: func() {
|
||||||
|
obj := makeIngress([]v1beta1.IngressTLS{{}})
|
||||||
|
c.ExtensionsV1beta1().Ingresses("default").Create(obj)
|
||||||
|
w.Ingresses().Add(obj)
|
||||||
|
},
|
||||||
|
expectedMaxItems: 1,
|
||||||
|
expectedRes: expectedTargetGroups(true),
|
||||||
}.Run(t)
|
}.Run(t)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,9 @@ import (
|
||||||
"github.com/prometheus/prometheus/discovery/targetgroup"
|
"github.com/prometheus/prometheus/discovery/targetgroup"
|
||||||
yaml_util "github.com/prometheus/prometheus/util/yaml"
|
yaml_util "github.com/prometheus/prometheus/util/yaml"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/pkg/api"
|
"k8s.io/client-go/pkg/api"
|
||||||
apiv1 "k8s.io/client-go/pkg/api/v1"
|
apiv1 "k8s.io/client-go/pkg/api/v1"
|
||||||
|
@ -152,13 +155,21 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discovery implements the Discoverer interface for discovering
|
// Copy of discovery.Discoverer to avoid import cycle.
|
||||||
|
// This is only for internal use.
|
||||||
|
type discoverer interface {
|
||||||
|
Run(ctx context.Context, up chan<- []*targetgroup.Group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discovery implements the discoverer interface for discovering
|
||||||
// targets from Kubernetes.
|
// targets from Kubernetes.
|
||||||
type Discovery struct {
|
type Discovery struct {
|
||||||
|
sync.RWMutex
|
||||||
client kubernetes.Interface
|
client kubernetes.Interface
|
||||||
role Role
|
role Role
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
namespaceDiscovery *NamespaceDiscovery
|
namespaceDiscovery *NamespaceDiscovery
|
||||||
|
discoverers []discoverer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Discovery) getNamespaces() []string {
|
func (d *Discovery) getNamespaces() []string {
|
||||||
|
@ -239,129 +250,156 @@ func New(l log.Logger, conf *SDConfig) (*Discovery, error) {
|
||||||
logger: l,
|
logger: l,
|
||||||
role: conf.Role,
|
role: conf.Role,
|
||||||
namespaceDiscovery: &conf.NamespaceDiscovery,
|
namespaceDiscovery: &conf.NamespaceDiscovery,
|
||||||
|
discoverers: make([]discoverer, 0),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const resyncPeriod = 10 * time.Minute
|
const resyncPeriod = 10 * time.Minute
|
||||||
|
|
||||||
// Run implements the Discoverer interface.
|
type hasSynced interface {
|
||||||
func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
// hasSynced returns true if all informers' store has synced.
|
||||||
rclient := d.client.Core().RESTClient()
|
// This is only used in testing to determine when the cache stores have synced.
|
||||||
reclient := d.client.Extensions().RESTClient()
|
hasSynced() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ hasSynced = &Discovery{}
|
||||||
|
|
||||||
|
func (d *Discovery) hasSynced() bool {
|
||||||
|
d.RLock()
|
||||||
|
defer d.RUnlock()
|
||||||
|
for _, discoverer := range d.discoverers {
|
||||||
|
if hasSynceddiscoverer, ok := discoverer.(hasSynced); ok {
|
||||||
|
if !hasSynceddiscoverer.hasSynced() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run implements the discoverer interface.
|
||||||
|
func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
|
d.Lock()
|
||||||
namespaces := d.getNamespaces()
|
namespaces := d.getNamespaces()
|
||||||
|
|
||||||
switch d.role {
|
switch d.role {
|
||||||
case "endpoints":
|
case "endpoints":
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range namespaces {
|
||||||
elw := cache.NewListWatchFromClient(rclient, "endpoints", namespace, nil)
|
elw := &cache.ListWatch{
|
||||||
slw := cache.NewListWatchFromClient(rclient, "services", namespace, nil)
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
plw := cache.NewListWatchFromClient(rclient, "pods", namespace, nil)
|
return d.client.CoreV1().Endpoints(namespace).List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
return d.client.CoreV1().Endpoints(namespace).Watch(options)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
slw := &cache.ListWatch{
|
||||||
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
|
return d.client.CoreV1().Services(namespace).List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
return d.client.CoreV1().Services(namespace).Watch(options)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
plw := &cache.ListWatch{
|
||||||
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
|
return d.client.CoreV1().Pods(namespace).List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
return d.client.CoreV1().Pods(namespace).Watch(options)
|
||||||
|
},
|
||||||
|
}
|
||||||
eps := NewEndpoints(
|
eps := NewEndpoints(
|
||||||
log.With(d.logger, "role", "endpoint"),
|
log.With(d.logger, "role", "endpoint"),
|
||||||
cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod),
|
cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod),
|
||||||
cache.NewSharedInformer(elw, &apiv1.Endpoints{}, resyncPeriod),
|
cache.NewSharedInformer(elw, &apiv1.Endpoints{}, resyncPeriod),
|
||||||
cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod),
|
cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod),
|
||||||
)
|
)
|
||||||
|
d.discoverers = append(d.discoverers, eps)
|
||||||
go eps.endpointsInf.Run(ctx.Done())
|
go eps.endpointsInf.Run(ctx.Done())
|
||||||
go eps.serviceInf.Run(ctx.Done())
|
go eps.serviceInf.Run(ctx.Done())
|
||||||
go eps.podInf.Run(ctx.Done())
|
go eps.podInf.Run(ctx.Done())
|
||||||
|
|
||||||
for !eps.serviceInf.HasSynced() {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
}
|
||||||
for !eps.endpointsInf.HasSynced() {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
for !eps.podInf.HasSynced() {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
eps.Run(ctx, ch)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
case "pod":
|
case "pod":
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range namespaces {
|
||||||
plw := cache.NewListWatchFromClient(rclient, "pods", namespace, nil)
|
plw := &cache.ListWatch{
|
||||||
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
|
return d.client.CoreV1().Pods(namespace).List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
return d.client.CoreV1().Pods(namespace).Watch(options)
|
||||||
|
},
|
||||||
|
}
|
||||||
pod := NewPod(
|
pod := NewPod(
|
||||||
log.With(d.logger, "role", "pod"),
|
log.With(d.logger, "role", "pod"),
|
||||||
cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod),
|
cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod),
|
||||||
)
|
)
|
||||||
|
d.discoverers = append(d.discoverers, pod)
|
||||||
go pod.informer.Run(ctx.Done())
|
go pod.informer.Run(ctx.Done())
|
||||||
|
|
||||||
for !pod.informer.HasSynced() {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
pod.Run(ctx, ch)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
case "service":
|
case "service":
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range namespaces {
|
||||||
slw := cache.NewListWatchFromClient(rclient, "services", namespace, nil)
|
slw := &cache.ListWatch{
|
||||||
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
|
return d.client.CoreV1().Services(namespace).List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
return d.client.CoreV1().Services(namespace).Watch(options)
|
||||||
|
},
|
||||||
|
}
|
||||||
svc := NewService(
|
svc := NewService(
|
||||||
log.With(d.logger, "role", "service"),
|
log.With(d.logger, "role", "service"),
|
||||||
cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod),
|
cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod),
|
||||||
)
|
)
|
||||||
|
d.discoverers = append(d.discoverers, svc)
|
||||||
go svc.informer.Run(ctx.Done())
|
go svc.informer.Run(ctx.Done())
|
||||||
|
|
||||||
for !svc.informer.HasSynced() {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
svc.Run(ctx, ch)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
case "ingress":
|
case "ingress":
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range namespaces {
|
||||||
ilw := cache.NewListWatchFromClient(reclient, "ingresses", namespace, nil)
|
ilw := &cache.ListWatch{
|
||||||
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
|
return d.client.ExtensionsV1beta1().Ingresses(namespace).List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
return d.client.ExtensionsV1beta1().Ingresses(namespace).Watch(options)
|
||||||
|
},
|
||||||
|
}
|
||||||
ingress := NewIngress(
|
ingress := NewIngress(
|
||||||
log.With(d.logger, "role", "ingress"),
|
log.With(d.logger, "role", "ingress"),
|
||||||
cache.NewSharedInformer(ilw, &extensionsv1beta1.Ingress{}, resyncPeriod),
|
cache.NewSharedInformer(ilw, &extensionsv1beta1.Ingress{}, resyncPeriod),
|
||||||
)
|
)
|
||||||
|
d.discoverers = append(d.discoverers, ingress)
|
||||||
go ingress.informer.Run(ctx.Done())
|
go ingress.informer.Run(ctx.Done())
|
||||||
|
|
||||||
for !ingress.informer.HasSynced() {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
ingress.Run(ctx, ch)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
case "node":
|
case "node":
|
||||||
nlw := cache.NewListWatchFromClient(rclient, "nodes", api.NamespaceAll, nil)
|
nlw := &cache.ListWatch{
|
||||||
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
|
return d.client.CoreV1().Nodes().List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
return d.client.CoreV1().Nodes().Watch(options)
|
||||||
|
},
|
||||||
|
}
|
||||||
node := NewNode(
|
node := NewNode(
|
||||||
log.With(d.logger, "role", "node"),
|
log.With(d.logger, "role", "node"),
|
||||||
cache.NewSharedInformer(nlw, &apiv1.Node{}, resyncPeriod),
|
cache.NewSharedInformer(nlw, &apiv1.Node{}, resyncPeriod),
|
||||||
)
|
)
|
||||||
|
d.discoverers = append(d.discoverers, node)
|
||||||
go node.informer.Run(ctx.Done())
|
go node.informer.Run(ctx.Done())
|
||||||
|
|
||||||
for !node.informer.HasSynced() {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
node.Run(ctx, ch)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
level.Error(d.logger).Log("msg", "unknown Kubernetes discovery kind", "role", d.role)
|
level.Error(d.logger).Log("msg", "unknown Kubernetes discovery kind", "role", d.role)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, dd := range d.discoverers {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(d discoverer) {
|
||||||
|
defer wg.Done()
|
||||||
|
d.Run(ctx, ch)
|
||||||
|
}(dd)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Unlock()
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
186
discovery/kubernetes/kubernetes_test.go
Normal file
186
discovery/kubernetes/kubernetes_test.go
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
// Copyright 2018 The Prometheus 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.
|
||||||
|
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-kit/kit/log"
|
||||||
|
"github.com/prometheus/prometheus/discovery/targetgroup"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
k8stesting "k8s.io/client-go/testing"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type watcherFactory struct {
|
||||||
|
sync.RWMutex
|
||||||
|
watchers map[schema.GroupVersionResource]*watch.FakeWatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wf *watcherFactory) watchFor(gvr schema.GroupVersionResource) *watch.FakeWatcher {
|
||||||
|
wf.Lock()
|
||||||
|
defer wf.Unlock()
|
||||||
|
|
||||||
|
var fakewatch *watch.FakeWatcher
|
||||||
|
fakewatch, ok := wf.watchers[gvr]
|
||||||
|
if !ok {
|
||||||
|
fakewatch = watch.NewFakeWithChanSize(128, true)
|
||||||
|
wf.watchers[gvr] = fakewatch
|
||||||
|
}
|
||||||
|
return fakewatch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wf *watcherFactory) Nodes() *watch.FakeWatcher {
|
||||||
|
return wf.watchFor(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "nodes"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wf *watcherFactory) Ingresses() *watch.FakeWatcher {
|
||||||
|
return wf.watchFor(schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wf *watcherFactory) Endpoints() *watch.FakeWatcher {
|
||||||
|
return wf.watchFor(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "endpoints"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wf *watcherFactory) Services() *watch.FakeWatcher {
|
||||||
|
return wf.watchFor(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wf *watcherFactory) Pods() *watch.FakeWatcher {
|
||||||
|
return wf.watchFor(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeDiscovery creates a kubernetes.Discovery instance for testing.
|
||||||
|
func makeDiscovery(role Role, nsDiscovery NamespaceDiscovery, objects ...runtime.Object) (*Discovery, kubernetes.Interface, *watcherFactory) {
|
||||||
|
clientset := fake.NewSimpleClientset(objects...)
|
||||||
|
// Current client-go we are using does not support push event on
|
||||||
|
// Add/Update/Create, so we need to emit event manually.
|
||||||
|
// See https://github.com/kubernetes/kubernetes/issues/54075.
|
||||||
|
// TODO update client-go thChanSizeand related packages to kubernetes-1.10.0+
|
||||||
|
wf := &watcherFactory{
|
||||||
|
watchers: make(map[schema.GroupVersionResource]*watch.FakeWatcher),
|
||||||
|
}
|
||||||
|
clientset.PrependWatchReactor("*", func(action k8stesting.Action) (handled bool, ret watch.Interface, err error) {
|
||||||
|
gvr := action.GetResource()
|
||||||
|
return true, wf.watchFor(gvr), nil
|
||||||
|
})
|
||||||
|
return &Discovery{
|
||||||
|
client: clientset,
|
||||||
|
logger: log.NewNopLogger(),
|
||||||
|
role: role,
|
||||||
|
namespaceDiscovery: &nsDiscovery,
|
||||||
|
}, clientset, wf
|
||||||
|
}
|
||||||
|
|
||||||
|
type k8sDiscoveryTest struct {
|
||||||
|
// discovery is instance of discovery.Discoverer
|
||||||
|
discovery discoverer
|
||||||
|
// beforeRun runs before discoverer run
|
||||||
|
beforeRun func()
|
||||||
|
// afterStart runs after discoverer has synced
|
||||||
|
afterStart func()
|
||||||
|
// expectedMaxItems is expected max items we may get from channel
|
||||||
|
expectedMaxItems int
|
||||||
|
// expectedRes is expected final result
|
||||||
|
expectedRes map[string]*targetgroup.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d k8sDiscoveryTest) Run(t *testing.T) {
|
||||||
|
ch := make(chan []*targetgroup.Group)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if d.beforeRun != nil {
|
||||||
|
d.beforeRun()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run discoverer and start a goroutine to read results.
|
||||||
|
go d.discovery.Run(ctx, ch)
|
||||||
|
resChan := make(chan map[string]*targetgroup.Group)
|
||||||
|
go readResultWithoutTimeout(t, ch, d.expectedMaxItems, time.Second, resChan)
|
||||||
|
|
||||||
|
if dd, ok := d.discovery.(hasSynced); ok {
|
||||||
|
if !cache.WaitForCacheSync(ctx.Done(), dd.hasSynced) {
|
||||||
|
t.Errorf("discoverer failed to sync: %v", dd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.afterStart != nil {
|
||||||
|
d.afterStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.expectedRes != nil {
|
||||||
|
res := <-resChan
|
||||||
|
requireTargetGroups(t, d.expectedRes, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readResultWithoutTimeout reads all targegroups from channel with timeout.
|
||||||
|
// It merges targegroups by source and sends the result to result channel.
|
||||||
|
func readResultWithoutTimeout(t *testing.T, ch <-chan []*targetgroup.Group, max int, timeout time.Duration, resChan chan<- map[string]*targetgroup.Group) {
|
||||||
|
allTgs := make([][]*targetgroup.Group, 0)
|
||||||
|
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case tgs := <-ch:
|
||||||
|
allTgs = append(allTgs, tgs)
|
||||||
|
if len(allTgs) == max {
|
||||||
|
// Reached max target groups we may get, break fast.
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
case <-time.After(timeout):
|
||||||
|
// Because we use queue, an object that is created then
|
||||||
|
// deleted or updated may be processed only once.
|
||||||
|
// So possibliy we may skip events, timed out here.
|
||||||
|
t.Logf("timed out, got %d (max: %d) items, some events are skipped", len(allTgs), max)
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge by source and sent it to channel.
|
||||||
|
res := make(map[string]*targetgroup.Group)
|
||||||
|
for _, tgs := range allTgs {
|
||||||
|
for _, tg := range tgs {
|
||||||
|
if tg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res[tg.Source] = tg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resChan <- res
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireTargetGroups(t *testing.T, expected, res map[string]*targetgroup.Group) {
|
||||||
|
b1, err := json.Marshal(expected)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
b2, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.JSONEq(t, string(b1), string(b2))
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"k8s.io/client-go/pkg/api"
|
"k8s.io/client-go/pkg/api"
|
||||||
apiv1 "k8s.io/client-go/pkg/api/v1"
|
apiv1 "k8s.io/client-go/pkg/api/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Node discovers Kubernetes nodes.
|
// Node discovers Kubernetes nodes.
|
||||||
|
@ -34,28 +35,55 @@ type Node struct {
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
informer cache.SharedInformer
|
informer cache.SharedInformer
|
||||||
store cache.Store
|
store cache.Store
|
||||||
|
queue *workqueue.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ discoverer = &Node{}
|
||||||
|
var _ hasSynced = &Node{}
|
||||||
|
|
||||||
// NewNode returns a new node discovery.
|
// NewNode returns a new node discovery.
|
||||||
func NewNode(l log.Logger, inf cache.SharedInformer) *Node {
|
func NewNode(l log.Logger, inf cache.SharedInformer) *Node {
|
||||||
if l == nil {
|
if l == nil {
|
||||||
l = log.NewNopLogger()
|
l = log.NewNopLogger()
|
||||||
}
|
}
|
||||||
return &Node{logger: l, informer: inf, store: inf.GetStore()}
|
n := &Node{logger: l, informer: inf, store: inf.GetStore(), queue: workqueue.NewNamed("node")}
|
||||||
|
n.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(o interface{}) {
|
||||||
|
eventCount.WithLabelValues("node", "add").Inc()
|
||||||
|
n.enqueue(o)
|
||||||
|
},
|
||||||
|
DeleteFunc: func(o interface{}) {
|
||||||
|
eventCount.WithLabelValues("node", "delete").Inc()
|
||||||
|
n.enqueue(o)
|
||||||
|
},
|
||||||
|
UpdateFunc: func(_, o interface{}) {
|
||||||
|
eventCount.WithLabelValues("node", "update").Inc()
|
||||||
|
n.enqueue(o)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Node) enqueue(obj interface{}) {
|
||||||
|
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.queue.Add(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) hasSynced() bool {
|
||||||
|
return n.informer.HasSynced()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run implements the Discoverer interface.
|
// Run implements the Discoverer interface.
|
||||||
func (n *Node) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
func (n *Node) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
// Send full initial set of pod targets.
|
defer n.queue.ShutDown()
|
||||||
var initial []*targetgroup.Group
|
|
||||||
for _, o := range n.store.List() {
|
if !cache.WaitForCacheSync(ctx.Done(), n.informer.HasSynced) {
|
||||||
tg := n.buildNode(o.(*apiv1.Node))
|
level.Error(n.logger).Log("msg", "node informer unable to sync cache")
|
||||||
initial = append(initial, tg)
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
return
|
||||||
case ch <- initial:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send target groups for service updates.
|
// Send target groups for service updates.
|
||||||
|
@ -68,64 +96,63 @@ func (n *Node) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
case ch <- []*targetgroup.Group{tg}:
|
case ch <- []*targetgroup.Group{tg}:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
||||||
AddFunc: func(o interface{}) {
|
|
||||||
eventCount.WithLabelValues("node", "add").Inc()
|
|
||||||
|
|
||||||
node, err := convertToNode(o)
|
go func() {
|
||||||
if err != nil {
|
for n.process(send) {
|
||||||
level.Error(n.logger).Log("msg", "converting to Node object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
send(n.buildNode(node))
|
}()
|
||||||
},
|
|
||||||
DeleteFunc: func(o interface{}) {
|
|
||||||
eventCount.WithLabelValues("node", "delete").Inc()
|
|
||||||
|
|
||||||
node, err := convertToNode(o)
|
|
||||||
if err != nil {
|
|
||||||
level.Error(n.logger).Log("msg", "converting to Node object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
send(&targetgroup.Group{Source: nodeSource(node)})
|
|
||||||
},
|
|
||||||
UpdateFunc: func(_, o interface{}) {
|
|
||||||
eventCount.WithLabelValues("node", "update").Inc()
|
|
||||||
|
|
||||||
node, err := convertToNode(o)
|
|
||||||
if err != nil {
|
|
||||||
level.Error(n.logger).Log("msg", "converting to Node object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
send(n.buildNode(node))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Block until the target provider is explicitly canceled.
|
// Block until the target provider is explicitly canceled.
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Node) process(send func(tg *targetgroup.Group)) bool {
|
||||||
|
keyObj, quit := n.queue.Get()
|
||||||
|
if quit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer n.queue.Done(keyObj)
|
||||||
|
key := keyObj.(string)
|
||||||
|
|
||||||
|
_, name, err := cache.SplitMetaNamespaceKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
o, exists, err := n.store.GetByKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
send(&targetgroup.Group{Source: nodeSourceFromName(name)})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
node, err := convertToNode(o)
|
||||||
|
if err != nil {
|
||||||
|
level.Error(n.logger).Log("msg", "converting to Node object failed", "err", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
send(n.buildNode(node))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func convertToNode(o interface{}) (*apiv1.Node, error) {
|
func convertToNode(o interface{}) (*apiv1.Node, error) {
|
||||||
node, ok := o.(*apiv1.Node)
|
node, ok := o.(*apiv1.Node)
|
||||||
if ok {
|
if ok {
|
||||||
return node, nil
|
return node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
deletedState, ok := o.(cache.DeletedFinalStateUnknown)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("Received unexpected object: %v", o)
|
return nil, fmt.Errorf("Received unexpected object: %v", o)
|
||||||
}
|
}
|
||||||
node, ok = deletedState.Obj.(*apiv1.Node)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("DeletedFinalStateUnknown contained non-Node object: %v", deletedState.Obj)
|
|
||||||
}
|
|
||||||
return node, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func nodeSource(n *apiv1.Node) string {
|
func nodeSource(n *apiv1.Node) string {
|
||||||
return "node/" + n.Name
|
return "node/" + n.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nodeSourceFromName(name string) string {
|
||||||
|
return "node/" + name
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
nodeNameLabel = metaLabelPrefix + "node_name"
|
nodeNameLabel = metaLabelPrefix + "node_name"
|
||||||
nodeLabelPrefix = metaLabelPrefix + "node_label_"
|
nodeLabelPrefix = metaLabelPrefix + "node_label_"
|
||||||
|
|
|
@ -14,152 +14,15 @@
|
||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
"github.com/prometheus/prometheus/discovery/targetgroup"
|
"github.com/prometheus/prometheus/discovery/targetgroup"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/pkg/api/v1"
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeInformer struct {
|
|
||||||
store cache.Store
|
|
||||||
handlers []cache.ResourceEventHandler
|
|
||||||
|
|
||||||
blockDeltas sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakeInformer(f func(obj interface{}) (string, error)) *fakeInformer {
|
|
||||||
i := &fakeInformer{
|
|
||||||
store: cache.NewStore(f),
|
|
||||||
}
|
|
||||||
// We want to make sure that all delta events (Add/Update/Delete) are blocked
|
|
||||||
// until our handlers to test have been added.
|
|
||||||
i.blockDeltas.Lock()
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *fakeInformer) AddEventHandler(h cache.ResourceEventHandler) {
|
|
||||||
i.handlers = append(i.handlers, h)
|
|
||||||
// Only now that there is a registered handler, we are able to handle deltas.
|
|
||||||
i.blockDeltas.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *fakeInformer) AddEventHandlerWithResyncPeriod(h cache.ResourceEventHandler, _ time.Duration) {
|
|
||||||
i.AddEventHandler(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *fakeInformer) GetStore() cache.Store {
|
|
||||||
return i.store
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *fakeInformer) GetController() cache.Controller {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *fakeInformer) Run(stopCh <-chan struct{}) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *fakeInformer) HasSynced() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *fakeInformer) LastSyncResourceVersion() string {
|
|
||||||
return "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *fakeInformer) Add(obj interface{}) {
|
|
||||||
i.blockDeltas.Lock()
|
|
||||||
defer i.blockDeltas.Unlock()
|
|
||||||
|
|
||||||
for _, h := range i.handlers {
|
|
||||||
h.OnAdd(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *fakeInformer) Delete(obj interface{}) {
|
|
||||||
i.blockDeltas.Lock()
|
|
||||||
defer i.blockDeltas.Unlock()
|
|
||||||
|
|
||||||
for _, h := range i.handlers {
|
|
||||||
h.OnDelete(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *fakeInformer) Update(obj interface{}) {
|
|
||||||
i.blockDeltas.Lock()
|
|
||||||
defer i.blockDeltas.Unlock()
|
|
||||||
|
|
||||||
for _, h := range i.handlers {
|
|
||||||
h.OnUpdate(nil, obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type discoverer interface {
|
|
||||||
Run(ctx context.Context, up chan<- []*targetgroup.Group)
|
|
||||||
}
|
|
||||||
|
|
||||||
type k8sDiscoveryTest struct {
|
|
||||||
discovery discoverer
|
|
||||||
afterStart func()
|
|
||||||
expectedInitial []*targetgroup.Group
|
|
||||||
expectedRes []*targetgroup.Group
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d k8sDiscoveryTest) Run(t *testing.T) {
|
|
||||||
ch := make(chan []*targetgroup.Group)
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*10)
|
|
||||||
defer cancel()
|
|
||||||
go func() {
|
|
||||||
d.discovery.Run(ctx, ch)
|
|
||||||
}()
|
|
||||||
|
|
||||||
initialRes := <-ch
|
|
||||||
if d.expectedInitial != nil {
|
|
||||||
requireTargetGroups(t, d.expectedInitial, initialRes)
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.afterStart != nil && d.expectedRes != nil {
|
|
||||||
d.afterStart()
|
|
||||||
res := <-ch
|
|
||||||
|
|
||||||
requireTargetGroups(t, d.expectedRes, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireTargetGroups(t *testing.T, expected, res []*targetgroup.Group) {
|
|
||||||
b1, err := json.Marshal(expected)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(res)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
require.JSONEq(t, string(b1), string(b2))
|
|
||||||
}
|
|
||||||
|
|
||||||
func nodeStoreKeyFunc(obj interface{}) (string, error) {
|
|
||||||
return obj.(*v1.Node).ObjectMeta.Name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakeNodeInformer() *fakeInformer {
|
|
||||||
return newFakeInformer(nodeStoreKeyFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTestNodeDiscovery() (*Node, *fakeInformer) {
|
|
||||||
i := newFakeNodeInformer()
|
|
||||||
return NewNode(nil, i), i
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeNode(name, address string, labels map[string]string, annotations map[string]string) *v1.Node {
|
func makeNode(name, address string, labels map[string]string, annotations map[string]string) *v1.Node {
|
||||||
return &v1.Node{
|
return &v1.Node{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
@ -187,19 +50,24 @@ func makeEnumeratedNode(i int) *v1.Node {
|
||||||
return makeNode(fmt.Sprintf("test%d", i), "1.2.3.4", map[string]string{}, map[string]string{})
|
return makeNode(fmt.Sprintf("test%d", i), "1.2.3.4", map[string]string{}, map[string]string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNodeDiscoveryInitial(t *testing.T) {
|
func TestNodeDiscoveryBeforeStart(t *testing.T) {
|
||||||
n, i := makeTestNodeDiscovery()
|
n, c, w := makeDiscovery(RoleNode, NamespaceDiscovery{})
|
||||||
i.GetStore().Add(makeNode(
|
|
||||||
|
k8sDiscoveryTest{
|
||||||
|
discovery: n,
|
||||||
|
beforeRun: func() {
|
||||||
|
obj := makeNode(
|
||||||
"test",
|
"test",
|
||||||
"1.2.3.4",
|
"1.2.3.4",
|
||||||
map[string]string{"testlabel": "testvalue"},
|
map[string]string{"testlabel": "testvalue"},
|
||||||
map[string]string{"testannotation": "testannotationvalue"},
|
map[string]string{"testannotation": "testannotationvalue"},
|
||||||
))
|
)
|
||||||
|
c.CoreV1().Nodes().Create(obj)
|
||||||
k8sDiscoveryTest{
|
w.Nodes().Add(obj)
|
||||||
discovery: n,
|
},
|
||||||
expectedInitial: []*targetgroup.Group{
|
expectedMaxItems: 1,
|
||||||
{
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
|
"node/test": {
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__address__": "1.2.3.4:10250",
|
"__address__": "1.2.3.4:10250",
|
||||||
|
@ -219,13 +87,18 @@ func TestNodeDiscoveryInitial(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNodeDiscoveryAdd(t *testing.T) {
|
func TestNodeDiscoveryAdd(t *testing.T) {
|
||||||
n, i := makeTestNodeDiscovery()
|
n, c, w := makeDiscovery(RoleNode, NamespaceDiscovery{})
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() { go func() { i.Add(makeEnumeratedNode(1)) }() },
|
afterStart: func() {
|
||||||
expectedRes: []*targetgroup.Group{
|
obj := makeEnumeratedNode(1)
|
||||||
{
|
c.CoreV1().Nodes().Create(obj)
|
||||||
|
w.Nodes().Add(obj)
|
||||||
|
},
|
||||||
|
expectedMaxItems: 1,
|
||||||
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
|
"node/test1": {
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__address__": "1.2.3.4:10250",
|
"__address__": "1.2.3.4:10250",
|
||||||
|
@ -243,59 +116,18 @@ func TestNodeDiscoveryAdd(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNodeDiscoveryDelete(t *testing.T) {
|
func TestNodeDiscoveryDelete(t *testing.T) {
|
||||||
n, i := makeTestNodeDiscovery()
|
obj := makeEnumeratedNode(0)
|
||||||
i.GetStore().Add(makeEnumeratedNode(0))
|
n, c, w := makeDiscovery(RoleNode, NamespaceDiscovery{}, obj)
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() { go func() { i.Delete(makeEnumeratedNode(0)) }() },
|
afterStart: func() {
|
||||||
expectedInitial: []*targetgroup.Group{
|
c.CoreV1().Nodes().Delete(obj.Name, &metav1.DeleteOptions{})
|
||||||
{
|
w.Nodes().Delete(obj)
|
||||||
Targets: []model.LabelSet{
|
|
||||||
{
|
|
||||||
"__address__": "1.2.3.4:10250",
|
|
||||||
"instance": "test0",
|
|
||||||
"__meta_kubernetes_node_address_InternalIP": "1.2.3.4",
|
|
||||||
},
|
},
|
||||||
},
|
expectedMaxItems: 2,
|
||||||
Labels: model.LabelSet{
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
"__meta_kubernetes_node_name": "test0",
|
"node/test0": {
|
||||||
},
|
|
||||||
Source: "node/test0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Source: "node/test0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNodeDiscoveryDeleteUnknownCacheState(t *testing.T) {
|
|
||||||
n, i := makeTestNodeDiscovery()
|
|
||||||
i.GetStore().Add(makeEnumeratedNode(0))
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
|
||||||
discovery: n,
|
|
||||||
afterStart: func() { go func() { i.Delete(cache.DeletedFinalStateUnknown{Obj: makeEnumeratedNode(0)}) }() },
|
|
||||||
expectedInitial: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Targets: []model.LabelSet{
|
|
||||||
{
|
|
||||||
"__address__": "1.2.3.4:10250",
|
|
||||||
"instance": "test0",
|
|
||||||
"__meta_kubernetes_node_address_InternalIP": "1.2.3.4",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Labels: model.LabelSet{
|
|
||||||
"__meta_kubernetes_node_name": "test0",
|
|
||||||
},
|
|
||||||
Source: "node/test0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Source: "node/test0",
|
Source: "node/test0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -303,40 +135,26 @@ func TestNodeDiscoveryDeleteUnknownCacheState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNodeDiscoveryUpdate(t *testing.T) {
|
func TestNodeDiscoveryUpdate(t *testing.T) {
|
||||||
n, i := makeTestNodeDiscovery()
|
n, c, w := makeDiscovery(RoleNode, NamespaceDiscovery{})
|
||||||
i.GetStore().Add(makeEnumeratedNode(0))
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() {
|
afterStart: func() {
|
||||||
go func() {
|
obj1 := makeEnumeratedNode(0)
|
||||||
i.Update(
|
c.CoreV1().Nodes().Create(obj1)
|
||||||
makeNode(
|
w.Nodes().Add(obj1)
|
||||||
|
obj2 := makeNode(
|
||||||
"test0",
|
"test0",
|
||||||
"1.2.3.4",
|
"1.2.3.4",
|
||||||
map[string]string{"Unschedulable": "true"},
|
map[string]string{"Unschedulable": "true"},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}()
|
c.CoreV1().Nodes().Update(obj2)
|
||||||
|
w.Nodes().Modify(obj2)
|
||||||
},
|
},
|
||||||
expectedInitial: []*targetgroup.Group{
|
expectedMaxItems: 2,
|
||||||
{
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
Targets: []model.LabelSet{
|
"node/test0": {
|
||||||
{
|
|
||||||
"__address__": "1.2.3.4:10250",
|
|
||||||
"instance": "test0",
|
|
||||||
"__meta_kubernetes_node_address_InternalIP": "1.2.3.4",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Labels: model.LabelSet{
|
|
||||||
"__meta_kubernetes_node_name": "test0",
|
|
||||||
},
|
|
||||||
Source: "node/test0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__address__": "1.2.3.4:10250",
|
"__address__": "1.2.3.4:10250",
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"k8s.io/client-go/pkg/api"
|
"k8s.io/client-go/pkg/api"
|
||||||
apiv1 "k8s.io/client-go/pkg/api/v1"
|
apiv1 "k8s.io/client-go/pkg/api/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
|
|
||||||
"github.com/prometheus/prometheus/discovery/targetgroup"
|
"github.com/prometheus/prometheus/discovery/targetgroup"
|
||||||
"github.com/prometheus/prometheus/util/strutil"
|
"github.com/prometheus/prometheus/util/strutil"
|
||||||
|
@ -36,6 +37,7 @@ type Pod struct {
|
||||||
informer cache.SharedInformer
|
informer cache.SharedInformer
|
||||||
store cache.Store
|
store cache.Store
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
|
queue *workqueue.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPod creates a new pod discovery.
|
// NewPod creates a new pod discovery.
|
||||||
|
@ -43,27 +45,45 @@ func NewPod(l log.Logger, pods cache.SharedInformer) *Pod {
|
||||||
if l == nil {
|
if l == nil {
|
||||||
l = log.NewNopLogger()
|
l = log.NewNopLogger()
|
||||||
}
|
}
|
||||||
return &Pod{
|
p := &Pod{
|
||||||
informer: pods,
|
informer: pods,
|
||||||
store: pods.GetStore(),
|
store: pods.GetStore(),
|
||||||
logger: l,
|
logger: l,
|
||||||
|
queue: workqueue.NewNamed("pod"),
|
||||||
}
|
}
|
||||||
|
p.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(o interface{}) {
|
||||||
|
eventCount.WithLabelValues("pod", "add").Inc()
|
||||||
|
p.enqueue(o)
|
||||||
|
},
|
||||||
|
DeleteFunc: func(o interface{}) {
|
||||||
|
eventCount.WithLabelValues("pod", "delete").Inc()
|
||||||
|
p.enqueue(o)
|
||||||
|
},
|
||||||
|
UpdateFunc: func(_, o interface{}) {
|
||||||
|
eventCount.WithLabelValues("pod", "update").Inc()
|
||||||
|
p.enqueue(o)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Pod) enqueue(obj interface{}) {
|
||||||
|
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.queue.Add(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run implements the Discoverer interface.
|
// Run implements the Discoverer interface.
|
||||||
func (p *Pod) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
func (p *Pod) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
// Send full initial set of pod targets.
|
defer p.queue.ShutDown()
|
||||||
var initial []*targetgroup.Group
|
|
||||||
for _, o := range p.store.List() {
|
|
||||||
tg := p.buildPod(o.(*apiv1.Pod))
|
|
||||||
initial = append(initial, tg)
|
|
||||||
|
|
||||||
level.Debug(p.logger).Log("msg", "initial pod", "tg", fmt.Sprintf("%#v", tg))
|
if !cache.WaitForCacheSync(ctx.Done(), p.informer.HasSynced) {
|
||||||
}
|
level.Error(p.logger).Log("msg", "pod informer unable to sync cache")
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
return
|
||||||
case ch <- initial:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send target groups for pod updates.
|
// Send target groups for pod updates.
|
||||||
|
@ -77,59 +97,54 @@ func (p *Pod) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
case ch <- []*targetgroup.Group{tg}:
|
case ch <- []*targetgroup.Group{tg}:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
||||||
AddFunc: func(o interface{}) {
|
|
||||||
eventCount.WithLabelValues("pod", "add").Inc()
|
|
||||||
|
|
||||||
pod, err := convertToPod(o)
|
go func() {
|
||||||
if err != nil {
|
for p.process(send) {
|
||||||
level.Error(p.logger).Log("msg", "converting to Pod object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
send(p.buildPod(pod))
|
}()
|
||||||
},
|
|
||||||
DeleteFunc: func(o interface{}) {
|
|
||||||
eventCount.WithLabelValues("pod", "delete").Inc()
|
|
||||||
|
|
||||||
pod, err := convertToPod(o)
|
|
||||||
if err != nil {
|
|
||||||
level.Error(p.logger).Log("msg", "converting to Pod object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
send(&targetgroup.Group{Source: podSource(pod)})
|
|
||||||
},
|
|
||||||
UpdateFunc: func(_, o interface{}) {
|
|
||||||
eventCount.WithLabelValues("pod", "update").Inc()
|
|
||||||
|
|
||||||
pod, err := convertToPod(o)
|
|
||||||
if err != nil {
|
|
||||||
level.Error(p.logger).Log("msg", "converting to Pod object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
send(p.buildPod(pod))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Block until the target provider is explicitly canceled.
|
// Block until the target provider is explicitly canceled.
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Pod) process(send func(tg *targetgroup.Group)) bool {
|
||||||
|
keyObj, quit := p.queue.Get()
|
||||||
|
if quit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer p.queue.Done(keyObj)
|
||||||
|
key := keyObj.(string)
|
||||||
|
|
||||||
|
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
o, exists, err := p.store.GetByKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
send(&targetgroup.Group{Source: podSourceFromNamespaceAndName(namespace, name)})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
eps, err := convertToPod(o)
|
||||||
|
if err != nil {
|
||||||
|
level.Error(p.logger).Log("msg", "converting to Pod object failed", "err", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
send(p.buildPod(eps))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func convertToPod(o interface{}) (*apiv1.Pod, error) {
|
func convertToPod(o interface{}) (*apiv1.Pod, error) {
|
||||||
pod, ok := o.(*apiv1.Pod)
|
pod, ok := o.(*apiv1.Pod)
|
||||||
if ok {
|
if ok {
|
||||||
return pod, nil
|
return pod, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
deletedState, ok := o.(cache.DeletedFinalStateUnknown)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("Received unexpected object: %v", o)
|
return nil, fmt.Errorf("Received unexpected object: %v", o)
|
||||||
}
|
}
|
||||||
pod, ok = deletedState.Obj.(*apiv1.Pod)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("DeletedFinalStateUnknown contained non-Pod object: %v", deletedState.Obj)
|
|
||||||
}
|
|
||||||
return pod, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
podNameLabel = metaLabelPrefix + "pod_name"
|
podNameLabel = metaLabelPrefix + "pod_name"
|
||||||
|
@ -215,6 +230,10 @@ func podSource(pod *apiv1.Pod) string {
|
||||||
return "pod/" + pod.Namespace + "/" + pod.Name
|
return "pod/" + pod.Namespace + "/" + pod.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func podSourceFromNamespaceAndName(namespace, name string) string {
|
||||||
|
return "pod/" + namespace + "/" + name
|
||||||
|
}
|
||||||
|
|
||||||
func podReady(pod *apiv1.Pod) model.LabelValue {
|
func podReady(pod *apiv1.Pod) model.LabelValue {
|
||||||
for _, cond := range pod.Status.Conditions {
|
for _, cond := range pod.Status.Conditions {
|
||||||
if cond.Type == apiv1.PodReady {
|
if cond.Type == apiv1.PodReady {
|
||||||
|
|
|
@ -21,23 +21,9 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/client-go/pkg/api/v1"
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func podStoreKeyFunc(obj interface{}) (string, error) {
|
func makeMultiPortPods() *v1.Pod {
|
||||||
return obj.(*v1.Pod).ObjectMeta.Name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakePodInformer() *fakeInformer {
|
|
||||||
return newFakeInformer(podStoreKeyFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTestPodDiscovery() (*Pod, *fakeInformer) {
|
|
||||||
i := newFakePodInformer()
|
|
||||||
return NewPod(nil, i), i
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeMultiPortPod() *v1.Pod {
|
|
||||||
return &v1.Pod{
|
return &v1.Pod{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "testpod",
|
Name: "testpod",
|
||||||
|
@ -82,7 +68,7 @@ func makeMultiPortPod() *v1.Pod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makePod() *v1.Pod {
|
func makePods() *v1.Pod {
|
||||||
return &v1.Pod{
|
return &v1.Pod{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "testpod",
|
Name: "testpod",
|
||||||
|
@ -117,14 +103,19 @@ func makePod() *v1.Pod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPodDiscoveryInitial(t *testing.T) {
|
func TestPodDiscoveryBeforeRun(t *testing.T) {
|
||||||
n, i := makeTestPodDiscovery()
|
n, c, w := makeDiscovery(RolePod, NamespaceDiscovery{})
|
||||||
i.GetStore().Add(makeMultiPortPod())
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
expectedInitial: []*targetgroup.Group{
|
beforeRun: func() {
|
||||||
{
|
obj := makeMultiPortPods()
|
||||||
|
c.CoreV1().Pods(obj.Namespace).Create(obj)
|
||||||
|
w.Pods().Add(obj)
|
||||||
|
},
|
||||||
|
expectedMaxItems: 1,
|
||||||
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
|
"pod/default/testpod": {
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__address__": "1.2.3.4:9000",
|
"__address__": "1.2.3.4:9000",
|
||||||
|
@ -163,13 +154,17 @@ func TestPodDiscoveryInitial(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPodDiscoveryAdd(t *testing.T) {
|
func TestPodDiscoveryAdd(t *testing.T) {
|
||||||
n, i := makeTestPodDiscovery()
|
n, c, w := makeDiscovery(RolePod, NamespaceDiscovery{})
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() { go func() { i.Add(makePod()) }() },
|
afterStart: func() {
|
||||||
expectedRes: []*targetgroup.Group{
|
obj := makePods()
|
||||||
{
|
c.CoreV1().Pods(obj.Namespace).Create(obj)
|
||||||
|
w.Pods().Add(obj)
|
||||||
|
},
|
||||||
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
|
"pod/default/testpod": {
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__address__": "1.2.3.4:9000",
|
"__address__": "1.2.3.4:9000",
|
||||||
|
@ -195,75 +190,18 @@ func TestPodDiscoveryAdd(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPodDiscoveryDelete(t *testing.T) {
|
func TestPodDiscoveryDelete(t *testing.T) {
|
||||||
n, i := makeTestPodDiscovery()
|
obj := makePods()
|
||||||
i.GetStore().Add(makePod())
|
n, c, w := makeDiscovery(RolePod, NamespaceDiscovery{}, obj)
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() { go func() { i.Delete(makePod()) }() },
|
afterStart: func() {
|
||||||
expectedInitial: []*targetgroup.Group{
|
obj := makePods()
|
||||||
{
|
c.CoreV1().Pods(obj.Namespace).Delete(obj.Name, &metav1.DeleteOptions{})
|
||||||
Targets: []model.LabelSet{
|
w.Pods().Delete(obj)
|
||||||
{
|
|
||||||
"__address__": "1.2.3.4:9000",
|
|
||||||
"__meta_kubernetes_pod_container_name": "testcontainer",
|
|
||||||
"__meta_kubernetes_pod_container_port_name": "testport",
|
|
||||||
"__meta_kubernetes_pod_container_port_number": "9000",
|
|
||||||
"__meta_kubernetes_pod_container_port_protocol": "TCP",
|
|
||||||
},
|
},
|
||||||
},
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
Labels: model.LabelSet{
|
"pod/default/testpod": {
|
||||||
"__meta_kubernetes_pod_name": "testpod",
|
|
||||||
"__meta_kubernetes_namespace": "default",
|
|
||||||
"__meta_kubernetes_pod_node_name": "testnode",
|
|
||||||
"__meta_kubernetes_pod_ip": "1.2.3.4",
|
|
||||||
"__meta_kubernetes_pod_host_ip": "2.3.4.5",
|
|
||||||
"__meta_kubernetes_pod_ready": "true",
|
|
||||||
"__meta_kubernetes_pod_uid": "abc123",
|
|
||||||
},
|
|
||||||
Source: "pod/default/testpod",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Source: "pod/default/testpod",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPodDiscoveryDeleteUnknownCacheState(t *testing.T) {
|
|
||||||
n, i := makeTestPodDiscovery()
|
|
||||||
i.GetStore().Add(makePod())
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
|
||||||
discovery: n,
|
|
||||||
afterStart: func() { go func() { i.Delete(cache.DeletedFinalStateUnknown{Obj: makePod()}) }() },
|
|
||||||
expectedInitial: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Targets: []model.LabelSet{
|
|
||||||
{
|
|
||||||
"__address__": "1.2.3.4:9000",
|
|
||||||
"__meta_kubernetes_pod_container_name": "testcontainer",
|
|
||||||
"__meta_kubernetes_pod_container_port_name": "testport",
|
|
||||||
"__meta_kubernetes_pod_container_port_number": "9000",
|
|
||||||
"__meta_kubernetes_pod_container_port_protocol": "TCP",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Labels: model.LabelSet{
|
|
||||||
"__meta_kubernetes_pod_name": "testpod",
|
|
||||||
"__meta_kubernetes_namespace": "default",
|
|
||||||
"__meta_kubernetes_pod_node_name": "testnode",
|
|
||||||
"__meta_kubernetes_pod_ip": "1.2.3.4",
|
|
||||||
"__meta_kubernetes_pod_host_ip": "2.3.4.5",
|
|
||||||
"__meta_kubernetes_pod_ready": "true",
|
|
||||||
"__meta_kubernetes_pod_uid": "abc123",
|
|
||||||
},
|
|
||||||
Source: "pod/default/testpod",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Source: "pod/default/testpod",
|
Source: "pod/default/testpod",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -271,8 +209,7 @@ func TestPodDiscoveryDeleteUnknownCacheState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPodDiscoveryUpdate(t *testing.T) {
|
func TestPodDiscoveryUpdate(t *testing.T) {
|
||||||
n, i := makeTestPodDiscovery()
|
obj := &v1.Pod{
|
||||||
i.GetStore().Add(&v1.Pod{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "testpod",
|
Name: "testpod",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
|
@ -297,36 +234,18 @@ func TestPodDiscoveryUpdate(t *testing.T) {
|
||||||
PodIP: "1.2.3.4",
|
PodIP: "1.2.3.4",
|
||||||
HostIP: "2.3.4.5",
|
HostIP: "2.3.4.5",
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
n, c, w := makeDiscovery(RolePod, NamespaceDiscovery{}, obj)
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() { go func() { i.Update(makePod()) }() },
|
afterStart: func() {
|
||||||
expectedInitial: []*targetgroup.Group{
|
obj := makePods()
|
||||||
{
|
c.CoreV1().Pods(obj.Namespace).Create(obj)
|
||||||
Targets: []model.LabelSet{
|
w.Pods().Modify(obj)
|
||||||
{
|
|
||||||
"__address__": "1.2.3.4:9000",
|
|
||||||
"__meta_kubernetes_pod_container_name": "testcontainer",
|
|
||||||
"__meta_kubernetes_pod_container_port_name": "testport",
|
|
||||||
"__meta_kubernetes_pod_container_port_number": "9000",
|
|
||||||
"__meta_kubernetes_pod_container_port_protocol": "TCP",
|
|
||||||
},
|
},
|
||||||
},
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
Labels: model.LabelSet{
|
"pod/default/testpod": {
|
||||||
"__meta_kubernetes_pod_name": "testpod",
|
|
||||||
"__meta_kubernetes_namespace": "default",
|
|
||||||
"__meta_kubernetes_pod_node_name": "testnode",
|
|
||||||
"__meta_kubernetes_pod_ip": "1.2.3.4",
|
|
||||||
"__meta_kubernetes_pod_host_ip": "2.3.4.5",
|
|
||||||
"__meta_kubernetes_pod_ready": "unknown",
|
|
||||||
"__meta_kubernetes_pod_uid": "xyz321",
|
|
||||||
},
|
|
||||||
Source: "pod/default/testpod",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__address__": "1.2.3.4:9000",
|
"__address__": "1.2.3.4:9000",
|
||||||
|
@ -352,42 +271,25 @@ func TestPodDiscoveryUpdate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPodDiscoveryUpdateEmptyPodIP(t *testing.T) {
|
func TestPodDiscoveryUpdateEmptyPodIP(t *testing.T) {
|
||||||
n, i := makeTestPodDiscovery()
|
n, c, w := makeDiscovery(RolePod, NamespaceDiscovery{})
|
||||||
initialPod := makePod()
|
initialPod := makePods()
|
||||||
|
|
||||||
updatedPod := makePod()
|
updatedPod := makePods()
|
||||||
updatedPod.Status.PodIP = ""
|
updatedPod.Status.PodIP = ""
|
||||||
|
|
||||||
i.GetStore().Add(initialPod)
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() { go func() { i.Update(updatedPod) }() },
|
beforeRun: func() {
|
||||||
expectedInitial: []*targetgroup.Group{
|
c.CoreV1().Pods(initialPod.Namespace).Create(initialPod)
|
||||||
{
|
w.Pods().Add(initialPod)
|
||||||
Targets: []model.LabelSet{
|
|
||||||
{
|
|
||||||
"__address__": "1.2.3.4:9000",
|
|
||||||
"__meta_kubernetes_pod_container_name": "testcontainer",
|
|
||||||
"__meta_kubernetes_pod_container_port_name": "testport",
|
|
||||||
"__meta_kubernetes_pod_container_port_number": "9000",
|
|
||||||
"__meta_kubernetes_pod_container_port_protocol": "TCP",
|
|
||||||
},
|
},
|
||||||
|
afterStart: func() {
|
||||||
|
c.CoreV1().Pods(updatedPod.Namespace).Create(updatedPod)
|
||||||
|
w.Pods().Modify(updatedPod)
|
||||||
},
|
},
|
||||||
Labels: model.LabelSet{
|
expectedMaxItems: 2,
|
||||||
"__meta_kubernetes_pod_name": "testpod",
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
"__meta_kubernetes_namespace": "default",
|
"pod/default/testpod": {
|
||||||
"__meta_kubernetes_pod_node_name": "testnode",
|
|
||||||
"__meta_kubernetes_pod_ip": "1.2.3.4",
|
|
||||||
"__meta_kubernetes_pod_host_ip": "2.3.4.5",
|
|
||||||
"__meta_kubernetes_pod_ready": "true",
|
|
||||||
"__meta_kubernetes_pod_uid": "abc123",
|
|
||||||
},
|
|
||||||
Source: "pod/default/testpod",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Source: "pod/default/testpod",
|
Source: "pod/default/testpod",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
apiv1 "k8s.io/client-go/pkg/api/v1"
|
apiv1 "k8s.io/client-go/pkg/api/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
|
|
||||||
"github.com/prometheus/prometheus/discovery/targetgroup"
|
"github.com/prometheus/prometheus/discovery/targetgroup"
|
||||||
"github.com/prometheus/prometheus/util/strutil"
|
"github.com/prometheus/prometheus/util/strutil"
|
||||||
|
@ -34,6 +35,7 @@ type Service struct {
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
informer cache.SharedInformer
|
informer cache.SharedInformer
|
||||||
store cache.Store
|
store cache.Store
|
||||||
|
queue *workqueue.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService returns a new service discovery.
|
// NewService returns a new service discovery.
|
||||||
|
@ -41,21 +43,40 @@ func NewService(l log.Logger, inf cache.SharedInformer) *Service {
|
||||||
if l == nil {
|
if l == nil {
|
||||||
l = log.NewNopLogger()
|
l = log.NewNopLogger()
|
||||||
}
|
}
|
||||||
return &Service{logger: l, informer: inf, store: inf.GetStore()}
|
s := &Service{logger: l, informer: inf, store: inf.GetStore(), queue: workqueue.NewNamed("ingress")}
|
||||||
|
s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(o interface{}) {
|
||||||
|
eventCount.WithLabelValues("service", "add").Inc()
|
||||||
|
s.enqueue(o)
|
||||||
|
},
|
||||||
|
DeleteFunc: func(o interface{}) {
|
||||||
|
eventCount.WithLabelValues("service", "delete").Inc()
|
||||||
|
s.enqueue(o)
|
||||||
|
},
|
||||||
|
UpdateFunc: func(_, o interface{}) {
|
||||||
|
eventCount.WithLabelValues("service", "update").Inc()
|
||||||
|
s.enqueue(o)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Service) enqueue(obj interface{}) {
|
||||||
|
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.queue.Add(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run implements the Discoverer interface.
|
// Run implements the Discoverer interface.
|
||||||
func (s *Service) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
func (s *Service) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
// Send full initial set of pod targets.
|
defer s.queue.ShutDown()
|
||||||
var initial []*targetgroup.Group
|
|
||||||
for _, o := range s.store.List() {
|
if !cache.WaitForCacheSync(ctx.Done(), s.informer.HasSynced) {
|
||||||
tg := s.buildService(o.(*apiv1.Service))
|
level.Error(s.logger).Log("msg", "service informer unable to sync cache")
|
||||||
initial = append(initial, tg)
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
return
|
||||||
case ch <- initial:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send target groups for service updates.
|
// Send target groups for service updates.
|
||||||
|
@ -65,63 +86,62 @@ func (s *Service) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
|
||||||
case ch <- []*targetgroup.Group{tg}:
|
case ch <- []*targetgroup.Group{tg}:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
||||||
AddFunc: func(o interface{}) {
|
|
||||||
eventCount.WithLabelValues("service", "add").Inc()
|
|
||||||
|
|
||||||
svc, err := convertToService(o)
|
go func() {
|
||||||
if err != nil {
|
for s.process(send) {
|
||||||
level.Error(s.logger).Log("msg", "converting to Service object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
send(s.buildService(svc))
|
}()
|
||||||
},
|
|
||||||
DeleteFunc: func(o interface{}) {
|
|
||||||
eventCount.WithLabelValues("service", "delete").Inc()
|
|
||||||
|
|
||||||
svc, err := convertToService(o)
|
|
||||||
if err != nil {
|
|
||||||
level.Error(s.logger).Log("msg", "converting to Service object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
send(&targetgroup.Group{Source: serviceSource(svc)})
|
|
||||||
},
|
|
||||||
UpdateFunc: func(_, o interface{}) {
|
|
||||||
eventCount.WithLabelValues("service", "update").Inc()
|
|
||||||
|
|
||||||
svc, err := convertToService(o)
|
|
||||||
if err != nil {
|
|
||||||
level.Error(s.logger).Log("msg", "converting to Service object failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
send(s.buildService(svc))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Block until the target provider is explicitly canceled.
|
// Block until the target provider is explicitly canceled.
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) process(send func(tg *targetgroup.Group)) bool {
|
||||||
|
keyObj, quit := s.queue.Get()
|
||||||
|
if quit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer s.queue.Done(keyObj)
|
||||||
|
key := keyObj.(string)
|
||||||
|
|
||||||
|
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
o, exists, err := s.store.GetByKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
send(&targetgroup.Group{Source: serviceSourceFromNamespaceAndName(namespace, name)})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
eps, err := convertToService(o)
|
||||||
|
if err != nil {
|
||||||
|
level.Error(s.logger).Log("msg", "converting to Service object failed", "err", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
send(s.buildService(eps))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func convertToService(o interface{}) (*apiv1.Service, error) {
|
func convertToService(o interface{}) (*apiv1.Service, error) {
|
||||||
service, ok := o.(*apiv1.Service)
|
service, ok := o.(*apiv1.Service)
|
||||||
if ok {
|
if ok {
|
||||||
return service, nil
|
return service, nil
|
||||||
}
|
}
|
||||||
deletedState, ok := o.(cache.DeletedFinalStateUnknown)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("Received unexpected object: %v", o)
|
return nil, fmt.Errorf("Received unexpected object: %v", o)
|
||||||
}
|
}
|
||||||
service, ok = deletedState.Obj.(*apiv1.Service)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("DeletedFinalStateUnknown contained non-Service object: %v", deletedState.Obj)
|
|
||||||
}
|
|
||||||
return service, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func serviceSource(s *apiv1.Service) string {
|
func serviceSource(s *apiv1.Service) string {
|
||||||
return "svc/" + s.Namespace + "/" + s.Name
|
return "svc/" + s.Namespace + "/" + s.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serviceSourceFromNamespaceAndName(namespace, name string) string {
|
||||||
|
return "svc/" + namespace + "/" + name
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
serviceNameLabel = metaLabelPrefix + "service_name"
|
serviceNameLabel = metaLabelPrefix + "service_name"
|
||||||
serviceLabelPrefix = metaLabelPrefix + "service_label_"
|
serviceLabelPrefix = metaLabelPrefix + "service_label_"
|
||||||
|
|
|
@ -21,22 +21,8 @@ import (
|
||||||
"github.com/prometheus/prometheus/discovery/targetgroup"
|
"github.com/prometheus/prometheus/discovery/targetgroup"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/pkg/api/v1"
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
"k8s.io/client-go/tools/cache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func serviceStoreKeyFunc(obj interface{}) (string, error) {
|
|
||||||
return obj.(*v1.Service).ObjectMeta.Name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakeServiceInformer() *fakeInformer {
|
|
||||||
return newFakeInformer(serviceStoreKeyFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTestServiceDiscovery() (*Service, *fakeInformer) {
|
|
||||||
i := newFakeServiceInformer()
|
|
||||||
return NewService(nil, i), i
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeMultiPortService() *v1.Service {
|
func makeMultiPortService() *v1.Service {
|
||||||
return &v1.Service{
|
return &v1.Service{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
@ -84,46 +70,19 @@ func makeService() *v1.Service {
|
||||||
return makeSuffixedService("")
|
return makeSuffixedService("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceDiscoveryInitial(t *testing.T) {
|
|
||||||
n, i := makeTestServiceDiscovery()
|
|
||||||
i.GetStore().Add(makeMultiPortService())
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
|
||||||
discovery: n,
|
|
||||||
expectedInitial: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Targets: []model.LabelSet{
|
|
||||||
{
|
|
||||||
"__meta_kubernetes_service_port_protocol": "TCP",
|
|
||||||
"__address__": "testservice.default.svc:30900",
|
|
||||||
"__meta_kubernetes_service_port_name": "testport0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__meta_kubernetes_service_port_protocol": "UDP",
|
|
||||||
"__address__": "testservice.default.svc:30901",
|
|
||||||
"__meta_kubernetes_service_port_name": "testport1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Labels: model.LabelSet{
|
|
||||||
"__meta_kubernetes_service_name": "testservice",
|
|
||||||
"__meta_kubernetes_namespace": "default",
|
|
||||||
"__meta_kubernetes_service_label_testlabel": "testvalue",
|
|
||||||
"__meta_kubernetes_service_annotation_testannotation": "testannotationvalue",
|
|
||||||
},
|
|
||||||
Source: "svc/default/testservice",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServiceDiscoveryAdd(t *testing.T) {
|
func TestServiceDiscoveryAdd(t *testing.T) {
|
||||||
n, i := makeTestServiceDiscovery()
|
n, c, w := makeDiscovery(RoleService, NamespaceDiscovery{})
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() { go func() { i.Add(makeService()) }() },
|
afterStart: func() {
|
||||||
expectedRes: []*targetgroup.Group{
|
obj := makeService()
|
||||||
{
|
c.CoreV1().Services(obj.Namespace).Create(obj)
|
||||||
|
w.Services().Add(obj)
|
||||||
|
},
|
||||||
|
expectedMaxItems: 1,
|
||||||
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
|
"svc/default/testservice": {
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__meta_kubernetes_service_port_protocol": "TCP",
|
"__meta_kubernetes_service_port_protocol": "TCP",
|
||||||
|
@ -142,61 +101,18 @@ func TestServiceDiscoveryAdd(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceDiscoveryDelete(t *testing.T) {
|
func TestServiceDiscoveryDelete(t *testing.T) {
|
||||||
n, i := makeTestServiceDiscovery()
|
n, c, w := makeDiscovery(RoleService, NamespaceDiscovery{}, makeService())
|
||||||
i.GetStore().Add(makeService())
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() { go func() { i.Delete(makeService()) }() },
|
afterStart: func() {
|
||||||
expectedInitial: []*targetgroup.Group{
|
obj := makeService()
|
||||||
{
|
c.CoreV1().Services(obj.Namespace).Delete(obj.Name, &metav1.DeleteOptions{})
|
||||||
Targets: []model.LabelSet{
|
w.Services().Delete(obj)
|
||||||
{
|
|
||||||
"__meta_kubernetes_service_port_protocol": "TCP",
|
|
||||||
"__address__": "testservice.default.svc:30900",
|
|
||||||
"__meta_kubernetes_service_port_name": "testport",
|
|
||||||
},
|
},
|
||||||
},
|
expectedMaxItems: 2,
|
||||||
Labels: model.LabelSet{
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
"__meta_kubernetes_service_name": "testservice",
|
"svc/default/testservice": {
|
||||||
"__meta_kubernetes_namespace": "default",
|
|
||||||
},
|
|
||||||
Source: "svc/default/testservice",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Source: "svc/default/testservice",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServiceDiscoveryDeleteUnknownCacheState(t *testing.T) {
|
|
||||||
n, i := makeTestServiceDiscovery()
|
|
||||||
i.GetStore().Add(makeService())
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
|
||||||
discovery: n,
|
|
||||||
afterStart: func() { go func() { i.Delete(cache.DeletedFinalStateUnknown{Obj: makeService()}) }() },
|
|
||||||
expectedInitial: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Targets: []model.LabelSet{
|
|
||||||
{
|
|
||||||
"__meta_kubernetes_service_port_protocol": "TCP",
|
|
||||||
"__address__": "testservice.default.svc:30900",
|
|
||||||
"__meta_kubernetes_service_port_name": "testport",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Labels: model.LabelSet{
|
|
||||||
"__meta_kubernetes_service_name": "testservice",
|
|
||||||
"__meta_kubernetes_namespace": "default",
|
|
||||||
},
|
|
||||||
Source: "svc/default/testservice",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Source: "svc/default/testservice",
|
Source: "svc/default/testservice",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -204,30 +120,18 @@ func TestServiceDiscoveryDeleteUnknownCacheState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceDiscoveryUpdate(t *testing.T) {
|
func TestServiceDiscoveryUpdate(t *testing.T) {
|
||||||
n, i := makeTestServiceDiscovery()
|
n, c, w := makeDiscovery(RoleService, NamespaceDiscovery{}, makeService())
|
||||||
i.GetStore().Add(makeService())
|
|
||||||
|
|
||||||
k8sDiscoveryTest{
|
k8sDiscoveryTest{
|
||||||
discovery: n,
|
discovery: n,
|
||||||
afterStart: func() { go func() { i.Update(makeMultiPortService()) }() },
|
afterStart: func() {
|
||||||
expectedInitial: []*targetgroup.Group{
|
obj := makeMultiPortService()
|
||||||
{
|
c.CoreV1().Services(obj.Namespace).Update(obj)
|
||||||
Targets: []model.LabelSet{
|
w.Services().Modify(obj)
|
||||||
{
|
|
||||||
"__meta_kubernetes_service_port_protocol": "TCP",
|
|
||||||
"__address__": "testservice.default.svc:30900",
|
|
||||||
"__meta_kubernetes_service_port_name": "testport",
|
|
||||||
},
|
},
|
||||||
},
|
expectedMaxItems: 2,
|
||||||
Labels: model.LabelSet{
|
expectedRes: map[string]*targetgroup.Group{
|
||||||
"__meta_kubernetes_service_name": "testservice",
|
"svc/default/testservice": {
|
||||||
"__meta_kubernetes_namespace": "default",
|
|
||||||
},
|
|
||||||
Source: "svc/default/testservice",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedRes: []*targetgroup.Group{
|
|
||||||
{
|
|
||||||
Targets: []model.LabelSet{
|
Targets: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__meta_kubernetes_service_port_protocol": "TCP",
|
"__meta_kubernetes_service_port_protocol": "TCP",
|
||||||
|
|
Loading…
Reference in a new issue