Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cmd/contour: add service status informer (#2386)
Updates #403 Add informer to watch envoy's service document and pass the status.loadbalancer stanza to the ingress status updater. cmd/contour: add IngressStatusWriter Wire up envoy service name and namespace to flags, defaults match the example deployment. examples/contour: add ingress status subresource to Contour's role Signed-off-by: Dave Cheney <dave@cheney.net>
- Loading branch information
1 parent
d4fb68d
commit fc79b07
Showing
8 changed files
with
405 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// Copyright © 2019 VMware | ||
// 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 main | ||
|
||
import ( | ||
"sync" | ||
|
||
"github.com/projectcontour/contour/internal/k8s" | ||
"github.com/sirupsen/logrus" | ||
v1 "k8s.io/api/core/v1" | ||
) | ||
|
||
// ingressStatusWriter manages the lifetime of StatusLoadBalancerUpdaters. | ||
// | ||
// The theory of operation of the ingressStatusWriter is as follows: | ||
// 1. On startup the ingressStatusWriter waits to be elected leader. | ||
// 2. Once elected leader, the ingressStatusWriter waits to receive a | ||
// v1.LoadBalancerStatus value. | ||
// 3. Once a v1.LoadBalancerStatus value has been received, any existing informer | ||
// is stopped and a new informer started in its place. | ||
// 4. Each informer is connected to a k8s.StatusLoadBalancerUpdater which reacts to | ||
// OnAdd events for networking.k8s.io/ingress.v1beta1 objects. For each OnAdd | ||
// the object is patched with the v1.LoadBalancerStatus value obtained on creation. | ||
// OnUpdate and OnDelete events are ignored.If a new v1.LoadBalancerStatus value | ||
// is been received, operation restarts at step 3. | ||
// 5. If the worker is stopped, any existing informer is stopped before the worker stops. | ||
type ingressStatusWriter struct { | ||
log logrus.FieldLogger | ||
clients *k8s.Clients | ||
isLeader chan struct{} | ||
lbStatus chan v1.LoadBalancerStatus | ||
} | ||
|
||
func (isw *ingressStatusWriter) Start(stop <-chan struct{}) error { | ||
|
||
// await leadership election | ||
isw.log.Info("awaiting leadership election") | ||
select { | ||
case <-stop: | ||
// asked to stop before elected leader | ||
return nil | ||
case <-isw.isLeader: | ||
isw.log.Info("elected leader") | ||
} | ||
|
||
var shutdown chan struct{} | ||
var stopping sync.WaitGroup | ||
for { | ||
select { | ||
case <-stop: | ||
// stop existing informer and shut down | ||
if shutdown != nil { | ||
close(shutdown) | ||
} | ||
stopping.Wait() | ||
return nil | ||
case lbs := <-isw.lbStatus: | ||
// stop existing informer | ||
if shutdown != nil { | ||
close(shutdown) | ||
} | ||
stopping.Wait() | ||
|
||
// create informer for the new LoadBalancerStatus | ||
factory := isw.clients.NewInformerFactory() | ||
inf := factory.Networking().V1beta1().Ingresses().Informer() | ||
log := isw.log.WithField("context", "IngressStatusLoadBalancerUpdater") | ||
inf.AddEventHandler(&k8s.StatusLoadBalancerUpdater{ | ||
Client: isw.clients.ClientSet(), | ||
Logger: log, | ||
Status: lbs, | ||
}) | ||
|
||
shutdown = make(chan struct{}) | ||
stopping.Add(1) | ||
fn := startInformer(factory, log) | ||
go func() { | ||
defer stopping.Done() | ||
fn(shutdown) | ||
}() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
// +build none | ||
|
||
package main | ||
|
||
import ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// Copyright © 2020 VMware | ||
// 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 k8s | ||
|
||
import ( | ||
"github.com/sirupsen/logrus" | ||
v1 "k8s.io/api/core/v1" | ||
"k8s.io/api/networking/v1beta1" | ||
clientset "k8s.io/client-go/kubernetes" | ||
) | ||
|
||
// StatusLoadbalancerUpdater observes informer OnAdd events and | ||
// updates the ingress.status.loadBalancer field on all Ingress | ||
// objects that match the ingress class (if used). | ||
type StatusLoadBalancerUpdater struct { | ||
Client clientset.Interface | ||
Logger logrus.FieldLogger | ||
Status v1.LoadBalancerStatus | ||
} | ||
|
||
func (s *StatusLoadBalancerUpdater) OnAdd(obj interface{}) { | ||
ing := obj.(*v1beta1.Ingress).DeepCopy() | ||
|
||
// TODO(dfc) check ingress class | ||
|
||
ing.Status.LoadBalancer = s.Status | ||
_, err := s.Client.NetworkingV1beta1().Ingresses(ing.GetNamespace()).UpdateStatus(ing) | ||
if err != nil { | ||
s.Logger. | ||
WithField("name", ing.GetName()). | ||
WithField("namespace", ing.GetNamespace()). | ||
WithError(err).Error("unable to update status") | ||
} | ||
} | ||
|
||
func (s *StatusLoadBalancerUpdater) OnUpdate(oldObj, newObj interface{}) { | ||
// Ignoring OnUpdate allows us to avoid the message generated | ||
// from the status update. | ||
|
||
// TODO(dfc) handle these cases: | ||
// - OnUpdate transitions from an ingress class which is out of scope | ||
// to one in scope. | ||
// - OnUpdate transitions from an ingress class in scope to one out | ||
// of scope. | ||
} | ||
|
||
func (s *StatusLoadBalancerUpdater) OnDelete(obj interface{}) { | ||
// we don't need to update the status on resources that | ||
// have been deleted. | ||
} | ||
|
||
// ServiceStatusLoadBalancerWatcher implements ResourceEventHandler and | ||
// watches for changes to the status.loadbalancer field | ||
type ServiceStatusLoadBalancerWatcher struct { | ||
ServiceName string | ||
LBStatus chan v1.LoadBalancerStatus | ||
} | ||
|
||
func (s *ServiceStatusLoadBalancerWatcher) OnAdd(obj interface{}) { | ||
svc, ok := obj.(*v1.Service) | ||
if !ok { | ||
// not a service | ||
return | ||
} | ||
if svc.Name != s.ServiceName { | ||
return | ||
} | ||
s.notify(svc.Status.LoadBalancer) | ||
} | ||
|
||
func (s *ServiceStatusLoadBalancerWatcher) OnUpdate(oldObj, newObj interface{}) { | ||
svc, ok := newObj.(*v1.Service) | ||
if !ok { | ||
// not a service | ||
return | ||
} | ||
if svc.Name != s.ServiceName { | ||
return | ||
} | ||
s.notify(svc.Status.LoadBalancer) | ||
} | ||
|
||
func (s *ServiceStatusLoadBalancerWatcher) OnDelete(obj interface{}) { | ||
svc, ok := obj.(*v1.Service) | ||
if !ok { | ||
// not a service | ||
return | ||
} | ||
if svc.Name != s.ServiceName { | ||
return | ||
} | ||
s.notify(v1.LoadBalancerStatus{ | ||
Ingress: nil, | ||
}) | ||
} | ||
|
||
func (s *ServiceStatusLoadBalancerWatcher) notify(lbstatus v1.LoadBalancerStatus) { | ||
s.LBStatus <- lbstatus | ||
} |
Oops, something went wrong.