mirror of https://github.com/linkerd/linkerd2.git
223 lines
5.9 KiB
Go
223 lines
5.9 KiB
Go
package watcher
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/linkerd/linkerd2/controller/k8s"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
ts "github.com/servicemeshinterface/smi-sdk-go/pkg/apis/split/v1alpha1"
|
|
tslisters "github.com/servicemeshinterface/smi-sdk-go/pkg/gen/client/split/listers/split/v1alpha1"
|
|
logging "github.com/sirupsen/logrus"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/client-go/tools/cache"
|
|
)
|
|
|
|
type (
|
|
// TrafficSplitWatcher watches all TrafficSplits in the Kubernetes cluster.
|
|
// Listeners can subscribe to a particular apex service and
|
|
// TrafficSplitWatcher will publish all TrafficSplits for that apex service.
|
|
TrafficSplitWatcher struct {
|
|
tsLister tslisters.TrafficSplitLister
|
|
publishers map[ServiceID]*trafficSplitPublisher
|
|
|
|
log *logging.Entry
|
|
sync.RWMutex // This mutex protects modification of the map itself.
|
|
}
|
|
|
|
trafficSplitPublisher struct {
|
|
split *ts.TrafficSplit
|
|
listeners []TrafficSplitUpdateListener
|
|
|
|
log *logging.Entry
|
|
splitMetrics metrics
|
|
// All access to the trafficSplitPublisher is explicitly synchronized by this mutex.
|
|
sync.Mutex
|
|
}
|
|
|
|
// TrafficSplitUpdateListener is the interface that subscribers must implement.
|
|
TrafficSplitUpdateListener interface {
|
|
UpdateTrafficSplit(split *ts.TrafficSplit)
|
|
}
|
|
)
|
|
|
|
var splitVecs = newMetricsVecs("trafficsplit", []string{"namespace", "service"})
|
|
|
|
// NewTrafficSplitWatcher creates a TrafficSplitWatcher and begins watching the k8sAPI for
|
|
// TrafficSplit changes.
|
|
func NewTrafficSplitWatcher(k8sAPI *k8s.API, log *logging.Entry) *TrafficSplitWatcher {
|
|
watcher := &TrafficSplitWatcher{
|
|
tsLister: k8sAPI.TS().Lister(),
|
|
publishers: make(map[ServiceID]*trafficSplitPublisher),
|
|
log: log.WithField("component", "traffic-split-watcher"),
|
|
}
|
|
|
|
k8sAPI.TS().Informer().AddEventHandler(
|
|
cache.ResourceEventHandlerFuncs{
|
|
AddFunc: watcher.addTrafficSplit,
|
|
UpdateFunc: watcher.updateTrafficSplit,
|
|
DeleteFunc: watcher.deleteTrafficSplit,
|
|
},
|
|
)
|
|
|
|
return watcher
|
|
}
|
|
|
|
///
|
|
/// TrafficSplitWatcher
|
|
///
|
|
|
|
// Subscribe to a service.
|
|
// Each time a traffic split is updated with the given apex service, the
|
|
// listener will be updated.
|
|
func (tsw *TrafficSplitWatcher) Subscribe(id ServiceID, listener TrafficSplitUpdateListener) error {
|
|
tsw.log.Infof("Establishing watch on service %s", id)
|
|
|
|
publisher := tsw.getOrNewTrafficSplitPublisher(id, nil)
|
|
|
|
publisher.subscribe(listener)
|
|
return nil
|
|
}
|
|
|
|
// Unsubscribe removes a listener from the subscribers list for this service.
|
|
func (tsw *TrafficSplitWatcher) Unsubscribe(id ServiceID, listener TrafficSplitUpdateListener) error {
|
|
tsw.log.Infof("Stopping watch on profile %s", id)
|
|
|
|
publisher, ok := tsw.getTrafficSplitPublisher(id)
|
|
if !ok {
|
|
return fmt.Errorf("cannot unsubscribe from unknown service [%s] ", id)
|
|
}
|
|
publisher.unsubscribe(listener)
|
|
return nil
|
|
}
|
|
|
|
func (tsw *TrafficSplitWatcher) addTrafficSplit(obj interface{}) {
|
|
split := obj.(*ts.TrafficSplit)
|
|
id := ServiceID{
|
|
Name: split.Spec.Service,
|
|
Namespace: split.Namespace,
|
|
}
|
|
|
|
publisher := tsw.getOrNewTrafficSplitPublisher(id, split)
|
|
|
|
publisher.update(split)
|
|
}
|
|
|
|
func (tsw *TrafficSplitWatcher) updateTrafficSplit(old interface{}, new interface{}) {
|
|
tsw.addTrafficSplit(new)
|
|
}
|
|
|
|
func (tsw *TrafficSplitWatcher) deleteTrafficSplit(obj interface{}) {
|
|
split, ok := obj.(*ts.TrafficSplit)
|
|
if !ok {
|
|
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
|
if !ok {
|
|
tsw.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
|
|
return
|
|
}
|
|
split, ok = tombstone.Obj.(*ts.TrafficSplit)
|
|
if !ok {
|
|
tsw.log.Errorf("DeletedFinalStateUnknown contained object that is not a TrafficSplit %#v", obj)
|
|
return
|
|
}
|
|
}
|
|
|
|
id := ServiceID{
|
|
Name: split.Spec.Service,
|
|
Namespace: split.Namespace,
|
|
}
|
|
|
|
publisher, ok := tsw.getTrafficSplitPublisher(id)
|
|
if ok {
|
|
publisher.update(nil)
|
|
}
|
|
}
|
|
|
|
func (tsw *TrafficSplitWatcher) getOrNewTrafficSplitPublisher(id ServiceID, split *ts.TrafficSplit) *trafficSplitPublisher {
|
|
tsw.Lock()
|
|
defer tsw.Unlock()
|
|
|
|
publisher, ok := tsw.publishers[id]
|
|
if !ok {
|
|
if split == nil {
|
|
var err error
|
|
split, err = tsw.tsLister.TrafficSplits(id.Namespace).Get(id.Name)
|
|
if err != nil && !apierrors.IsNotFound(err) {
|
|
tsw.log.Errorf("error getting TrafficSplit: %s", err)
|
|
}
|
|
if err != nil {
|
|
split = nil
|
|
}
|
|
}
|
|
|
|
publisher = &trafficSplitPublisher{
|
|
split: split,
|
|
listeners: make([]TrafficSplitUpdateListener, 0),
|
|
log: tsw.log.WithFields(logging.Fields{
|
|
"component": "traffic-split-publisher",
|
|
"ns": id.Namespace,
|
|
"service": id.Name,
|
|
}),
|
|
splitMetrics: splitVecs.newMetrics(prometheus.Labels{
|
|
"namespace": id.Namespace,
|
|
"service": id.Name,
|
|
}),
|
|
}
|
|
tsw.publishers[id] = publisher
|
|
}
|
|
|
|
return publisher
|
|
}
|
|
|
|
func (tsw *TrafficSplitWatcher) getTrafficSplitPublisher(id ServiceID) (publisher *trafficSplitPublisher, ok bool) {
|
|
tsw.RLock()
|
|
defer tsw.RUnlock()
|
|
publisher, ok = tsw.publishers[id]
|
|
return
|
|
}
|
|
|
|
///
|
|
/// trafficSplitPublisher
|
|
///
|
|
|
|
func (tsp *trafficSplitPublisher) subscribe(listener TrafficSplitUpdateListener) {
|
|
tsp.Lock()
|
|
defer tsp.Unlock()
|
|
|
|
tsp.listeners = append(tsp.listeners, listener)
|
|
listener.UpdateTrafficSplit(tsp.split)
|
|
|
|
tsp.splitMetrics.setSubscribers(len(tsp.listeners))
|
|
}
|
|
|
|
func (tsp *trafficSplitPublisher) unsubscribe(listener TrafficSplitUpdateListener) {
|
|
tsp.Lock()
|
|
defer tsp.Unlock()
|
|
|
|
for i, item := range tsp.listeners {
|
|
if item == listener {
|
|
// delete the item from the slice
|
|
n := len(tsp.listeners)
|
|
tsp.listeners[i] = tsp.listeners[n-1]
|
|
tsp.listeners[n-1] = nil
|
|
tsp.listeners = tsp.listeners[:n-1]
|
|
break
|
|
}
|
|
}
|
|
|
|
tsp.splitMetrics.setSubscribers(len(tsp.listeners))
|
|
}
|
|
|
|
func (tsp *trafficSplitPublisher) update(split *ts.TrafficSplit) {
|
|
tsp.Lock()
|
|
defer tsp.Unlock()
|
|
tsp.log.Debug("Updating TrafficSplit")
|
|
|
|
tsp.split = split
|
|
for _, listener := range tsp.listeners {
|
|
listener.UpdateTrafficSplit(split)
|
|
}
|
|
|
|
tsp.splitMetrics.incUpdates()
|
|
}
|