Skip to content

Commit 293a3da

Browse files
committed
Add a new load-balancer-status flag for setting ingress details
Signed-off-by: Haitao Li <[email protected]>
1 parent 70e9be9 commit 293a3da

File tree

17 files changed

+333
-57
lines changed

17 files changed

+333
-57
lines changed

apis/projectcontour/v1alpha1/contourconfig.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,11 @@ type EnvoyConfig struct {
253253
// +optional
254254
Service *NamespacedName `json:"service,omitempty"`
255255

256-
// Ingress holds Envoy service parameters for setting Ingress status.
256+
// LoadBalancer holds load balancer parameters for Ingress status.
257257
//
258-
// Contour's default is { namespace: "projectcontour", name: "envoy" }.
258+
// Contour's default is "service:projectcontour/envoy"
259259
// +optional
260-
Ingress *NamespacedName `json:"ingress,omitempty"`
260+
LoadBalancer string `json:"loadBalancer,omitempty"`
261261

262262
// Defines the HTTP Listener for Envoy.
263263
//

cmd/contour/serve.go

Lines changed: 122 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"net/http"
2121
"os"
2222
"strconv"
23+
"strings"
2324
"time"
2425

2526
"github.com/alecthomas/kingpin/v2"
@@ -145,8 +146,6 @@ func registerServe(app *kingpin.Application) (*kingpin.CmdClause, *serveContext)
145146
serve.Flag("envoy-service-https-port", "Kubernetes Service port for HTTPS requests.").PlaceHolder("<port>").IntVar(&ctx.httpsPort)
146147
serve.Flag("envoy-service-name", "Name of the Envoy service to inspect for Ingress status details.").PlaceHolder("<name>").StringVar(&ctx.Config.EnvoyServiceName)
147148
serve.Flag("envoy-service-namespace", "Envoy Service Namespace.").PlaceHolder("<namespace>").StringVar(&ctx.Config.EnvoyServiceNamespace)
148-
serve.Flag("envoy-ingress-name", "Name of the Envoy ingress to inspect for Ingress status details.").PlaceHolder("<name>").StringVar(&ctx.Config.EnvoyIngressName)
149-
serve.Flag("envoy-ingress-namespace", "Envoy Ingress Namespace.").PlaceHolder("<namespace>").StringVar(&ctx.Config.EnvoyIngressNamespace)
150149

151150
serve.Flag("health-address", "Address the health HTTP endpoint will bind to.").PlaceHolder("<ipaddr>").StringVar(&ctx.healthAddr)
152151
serve.Flag("health-port", "Port the health HTTP endpoint will bind to.").PlaceHolder("<port>").IntVar(&ctx.healthPort)
@@ -169,6 +168,8 @@ func registerServe(app *kingpin.Application) (*kingpin.CmdClause, *serveContext)
169168
serve.Flag("leader-election-resource-namespace", "The namespace of the resource (Lease) leader election will lease.").Default(config.GetenvOr("CONTOUR_NAMESPACE", "projectcontour")).StringVar(&ctx.LeaderElection.Namespace)
170169
serve.Flag("leader-election-retry-period", "The interval which Contour will attempt to acquire leadership lease.").Default("2s").DurationVar(&ctx.LeaderElection.RetryPeriod)
171170

171+
serve.Flag("load-balancer-status", "The source to inspect for load balancer status details.").PlaceHolder("<kind:ns/name|address>").StringVar(&ctx.Config.LoadBalancerStatus)
172+
172173
serve.Flag("root-namespaces", "Restrict contour to searching these namespaces for root ingress routes.").PlaceHolder("<ns,ns>").StringVar(&ctx.rootNamespaces)
173174

174175
serve.Flag("stats-address", "Envoy /stats interface address.").PlaceHolder("<ipaddr>").StringVar(&ctx.statsAddr)
@@ -686,48 +687,86 @@ func (s *Server) doServe() error {
686687
return err
687688
}
688689

689-
// Register an informer to watch envoy's service if we haven't been given static details.
690-
if lbAddress := contourConfiguration.Ingress.StatusAddress; len(lbAddress) > 0 {
691-
s.log.WithField("loadbalancer-address", lbAddress).Info("Using supplied information for Ingress status")
692-
lbsw.lbStatus <- parseStatusFlag(lbAddress)
693-
} else {
694-
serviceHandler := &k8s.ServiceStatusLoadBalancerWatcher{
695-
ServiceName: contourConfiguration.Envoy.Service.Name,
696-
LBStatus: lbsw.lbStatus,
697-
Log: s.log.WithField("context", "serviceStatusLoadBalancerWatcher"),
690+
if contourConfiguration.Envoy.LoadBalancer != "" {
691+
if contourConfiguration.Ingress.StatusAddress != "" {
692+
s.log.Warnf("ingress-status-address is ignored when load-balancer-status is specified")
698693
}
699-
700-
var handler cache.ResourceEventHandler = serviceHandler
701-
if contourConfiguration.Envoy.Service.Namespace != "" {
702-
handler = k8s.NewNamespaceFilter([]string{contourConfiguration.Envoy.Service.Namespace}, handler)
694+
if contourConfiguration.Envoy.Service.Name != "" || contourConfiguration.Envoy.Service.Namespace != "" {
695+
s.log.Warnf("envoy-service-name/envoy-service-namespace are ignored when load-balancer-status is specified")
703696
}
704697

705-
if err := s.informOnResource(&corev1.Service{}, handler); err != nil {
706-
s.log.WithError(err).WithField("resource", "services").Fatal("failed to create informer")
698+
elbs, err := parseEnvoyLoadBalancerStatus(contourConfiguration.Envoy.LoadBalancer)
699+
if err != nil {
700+
return err
707701
}
708702

709-
ingressHandler := &k8s.IngressStatusLoadBalancerWatcher{
710-
ServiceName: contourConfiguration.Envoy.Service.Name,
711-
LBStatus: lbsw.lbStatus,
712-
Log: s.log.WithField("context", "ingressStatusLoadBalancerWatcher"),
713-
}
703+
// Register an informer to watch envoy's service/ingress if we haven't been given static details.
704+
if lbAddress := elbs.FQDNs; len(lbAddress) > 0 {
705+
s.log.WithField("loadbalancer-fqdns", lbAddress).Info("Using supplied information for Ingress status")
706+
lbsw.lbStatus <- parseStatusFlag(lbAddress)
707+
} else {
708+
switch strings.ToLower(elbs.Kind) {
709+
case "service":
710+
serviceHandler := &k8s.ServiceStatusLoadBalancerWatcher{
711+
ServiceName: elbs.Name,
712+
LBStatus: lbsw.lbStatus,
713+
Log: s.log.WithField("context", "serviceStatusLoadBalancerWatcher"),
714+
}
714715

715-
var ingressEventHandler cache.ResourceEventHandler = ingressHandler
716-
if contourConfiguration.Envoy.Ingress.Namespace != "" {
717-
handler = k8s.NewNamespaceFilter([]string{contourConfiguration.Envoy.Ingress.Namespace}, handler)
718-
}
716+
var handler cache.ResourceEventHandler = serviceHandler
717+
if elbs.Namespace != "" {
718+
handler = k8s.NewNamespaceFilter([]string{elbs.Namespace}, handler)
719+
}
719720

720-
if err := informOnResource(&networking_v1.Ingress{}, ingressEventHandler, s.mgr.GetCache()); err != nil {
721-
s.log.WithError(err).WithField("resource", "ingresses").Fatal("failed to create ingresses informer")
721+
if err := s.informOnResource(&corev1.Service{}, handler); err != nil {
722+
s.log.WithError(err).WithField("resource", "services").Fatal("failed to create services informer")
723+
}
724+
case "ingress":
725+
ingressHandler := &k8s.IngressStatusLoadBalancerWatcher{
726+
IngressName: elbs.Name,
727+
LBStatus: lbsw.lbStatus,
728+
Log: s.log.WithField("context", "ingressStatusLoadBalancerWatcher"),
729+
}
730+
731+
var handler cache.ResourceEventHandler = ingressHandler
732+
if elbs.Namespace != "" {
733+
handler = k8s.NewNamespaceFilter([]string{elbs.Namespace}, handler)
734+
}
735+
736+
if err := s.informOnResource(&networking_v1.Ingress{}, handler); err != nil {
737+
s.log.WithError(err).WithField("resource", "ingresses").Fatal("failed to create ingresses informer")
738+
}
739+
default:
740+
return fmt.Errorf("unsupported ingress kind: %s", elbs.Kind)
741+
}
742+
743+
s.log.Infof("Watching %s for Ingress status", elbs)
722744
}
745+
} else {
746+
// Register an informer to watch envoy's service/ingress if we haven't been given static details.
747+
if lbAddress := contourConfiguration.Ingress.StatusAddress; len(lbAddress) > 0 {
748+
s.log.WithField("loadbalancer-address", lbAddress).Info("Using supplied information for Ingress status")
749+
lbsw.lbStatus <- parseStatusFlag(lbAddress)
750+
} else {
751+
serviceHandler := &k8s.ServiceStatusLoadBalancerWatcher{
752+
ServiceName: contourConfiguration.Envoy.Service.Name,
753+
LBStatus: lbsw.lbStatus,
754+
Log: s.log.WithField("context", "serviceStatusLoadBalancerWatcher"),
755+
}
723756

724-
s.log.WithField("envoy-service-name", contourConfiguration.Envoy.Service.Name).
725-
WithField("envoy-service-namespace", contourConfiguration.Envoy.Service.Namespace).
726-
Info("Watching Service for Ingress status")
757+
var handler cache.ResourceEventHandler = serviceHandler
758+
if contourConfiguration.Envoy.Service.Namespace != "" {
759+
handler = k8s.NewNamespaceFilter([]string{contourConfiguration.Envoy.Service.Namespace}, handler)
760+
}
727761

728-
s.log.WithField("envoy-ingress-name", contourConfiguration.Envoy.Ingress.Name).
729-
WithField("envoy-ingress-namespace", contourConfiguration.Envoy.Ingress.Namespace).
730-
Info("Watching Ingress for Ingress status")
762+
if err := s.informOnResource(&corev1.Service{}, handler); err != nil {
763+
s.log.WithError(err).WithField("resource", "services").Fatal("failed to create services informer")
764+
}
765+
766+
s.log.WithField("envoy-service-name", contourConfiguration.Envoy.Service.Name).
767+
WithField("envoy-service-namespace", contourConfiguration.Envoy.Service.Namespace).
768+
Info("Watching Service for Ingress status")
769+
}
731770
}
732771

733772
xdsServer := &xdsServer{
@@ -756,6 +795,55 @@ func (s *Server) doServe() error {
756795
return s.mgr.Start(signals.SetupSignalHandler())
757796
}
758797

798+
type envoyLoadBalancerStatus struct {
799+
Kind string
800+
FQDNs string
801+
config.NamespacedName
802+
}
803+
804+
func (elbs *envoyLoadBalancerStatus) String() string {
805+
if elbs.Kind == "hostname" {
806+
return fmt.Sprintf("%s:%s", elbs.Kind, elbs.FQDNs)
807+
}
808+
return fmt.Sprintf("%s:%s/%s", elbs.Kind, elbs.Namespace, elbs.Name)
809+
}
810+
811+
func parseEnvoyLoadBalancerStatus(s string) (*envoyLoadBalancerStatus, error) {
812+
parts := strings.SplitN(s, ":", 2)
813+
if len(parts) != 2 {
814+
return nil, fmt.Errorf("invalid load-balancer-status: %s", s)
815+
}
816+
817+
if parts[1] == "" {
818+
return nil, fmt.Errorf("invalid load-balancer-status: empty object reference")
819+
}
820+
821+
elbs := envoyLoadBalancerStatus{}
822+
823+
elbs.Kind = strings.ToLower(parts[0])
824+
switch elbs.Kind {
825+
case "ingress", "service":
826+
parts = strings.Split(parts[1], "/")
827+
if len(parts) != 2 {
828+
return nil, fmt.Errorf("invalid load-balancer-status: %s is not in the format of <namespace>/<name>", s)
829+
}
830+
831+
if parts[0] == "" || parts[1] == "" {
832+
return nil, fmt.Errorf("invalid load-balancer-status: <namespace> or <name> is empty")
833+
}
834+
elbs.Namespace = parts[0]
835+
elbs.Name = parts[1]
836+
case "hostname":
837+
elbs.FQDNs = parts[1]
838+
case "":
839+
return nil, fmt.Errorf("invalid load-balancer-status: kind is empty")
840+
default:
841+
return nil, fmt.Errorf("invalid load-balancer-status: unsupported kind: %s", elbs.Kind)
842+
}
843+
844+
return &elbs, nil
845+
}
846+
759847
func (s *Server) getExtensionSvcConfig(name string, namespace string) (xdscache_v3.ExtensionServiceConfig, error) {
760848
extensionSvc := &contour_api_v1alpha1.ExtensionService{}
761849
key := client.ObjectKey{

cmd/contour/serve_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1"
2020
"github.com/projectcontour/contour/internal/dag"
2121
"github.com/projectcontour/contour/internal/ref"
22+
"github.com/projectcontour/contour/pkg/config"
2223
"github.com/sirupsen/logrus"
2324
"github.com/stretchr/testify/assert"
2425
"github.com/stretchr/testify/require"
@@ -217,3 +218,109 @@ func mustGetIngressProcessor(t *testing.T, builder *dag.Builder) *dag.IngressPro
217218
require.FailNow(t, "IngressProcessor not found in list of DAG builder's processors")
218219
return nil
219220
}
221+
222+
func TestParseEnvoyLoadBalancerStatus(t *testing.T) {
223+
224+
tests := []struct {
225+
name string
226+
status string
227+
want envoyLoadBalancerStatus
228+
}{
229+
{
230+
name: "Service",
231+
status: "service:namespace-1/name-1",
232+
want: envoyLoadBalancerStatus{
233+
Kind: "service",
234+
NamespacedName: config.NamespacedName{
235+
Name: "name-1",
236+
Namespace: "namespace-1",
237+
},
238+
},
239+
},
240+
{
241+
name: "Ingress",
242+
status: "ingress:namespace-1/name-1",
243+
want: envoyLoadBalancerStatus{
244+
Kind: "ingress",
245+
NamespacedName: config.NamespacedName{
246+
Name: "name-1",
247+
Namespace: "namespace-1",
248+
},
249+
},
250+
},
251+
{
252+
name: "hostname",
253+
status: "hostname:example.com",
254+
want: envoyLoadBalancerStatus{
255+
Kind: "hostname",
256+
FQDNs: "example.com",
257+
},
258+
},
259+
}
260+
for _, tt := range tests {
261+
t.Run(tt.name, func(t *testing.T) {
262+
r, err := parseEnvoyLoadBalancerStatus(tt.status)
263+
assert.NoError(t, err)
264+
assert.Equal(t, tt.want, *r)
265+
})
266+
}
267+
268+
tests2 := []struct {
269+
name string
270+
status string
271+
error string
272+
}{
273+
{
274+
name: "Empty",
275+
status: "",
276+
error: "invalid",
277+
},
278+
{
279+
name: "No kind",
280+
status: ":n",
281+
error: "kind is empty",
282+
},
283+
{
284+
name: "Invalid kind",
285+
status: "test:n",
286+
error: "unsupported kind",
287+
},
288+
{
289+
name: "No reference",
290+
status: "service:",
291+
error: "empty object reference",
292+
},
293+
{
294+
name: "No colon",
295+
status: "service",
296+
error: "invalid",
297+
},
298+
{
299+
name: "No slash",
300+
status: "service:name-1",
301+
error: "not in the format",
302+
},
303+
{
304+
name: "starts with slash",
305+
status: "service:/name-1",
306+
error: "is empty",
307+
},
308+
{
309+
name: "ends with slash",
310+
status: "service:name-1/",
311+
error: "is empty",
312+
},
313+
{
314+
name: "two many slashes",
315+
status: "service:name/x/y",
316+
error: "not in the format",
317+
},
318+
}
319+
for _, tt := range tests2 {
320+
t.Run(tt.name, func(t *testing.T) {
321+
_, err := parseEnvoyLoadBalancerStatus(tt.status)
322+
assert.Error(t, err)
323+
assert.Contains(t, err.Error(), tt.error)
324+
})
325+
}
326+
}

cmd/contour/servecontext.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -544,10 +544,6 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_api_v1alpha
544544
Name: ctx.Config.EnvoyServiceName,
545545
Namespace: ctx.Config.EnvoyServiceNamespace,
546546
},
547-
Ingress: &contour_api_v1alpha1.NamespacedName{
548-
Name: ctx.Config.EnvoyIngressName,
549-
Namespace: ctx.Config.EnvoyIngressNamespace,
550-
},
551547
HTTPListener: &contour_api_v1alpha1.EnvoyListener{
552548
Address: ctx.httpAddr,
553549
Port: ctx.httpPort,
@@ -581,6 +577,7 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_api_v1alpha
581577
XffNumTrustedHops: &ctx.Config.Network.XffNumTrustedHops,
582578
EnvoyAdminPort: &ctx.Config.Network.EnvoyAdminPort,
583579
},
580+
LoadBalancer: ctx.Config.LoadBalancerStatus,
584581
},
585582
Gateway: gatewayConfig,
586583
HTTPProxy: &contour_api_v1alpha1.HTTPProxyConfig{

cmd/contour/servecontext_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ func TestConvertServeContext(t *testing.T) {
418418
TrafficClass: 0,
419419
},
420420
},
421+
LoadBalancer: "service:projectcontour/envoy",
421422
HTTPListener: &contour_api_v1alpha1.EnvoyListener{
422423
Address: "0.0.0.0",
423424
Port: 8080,

examples/contour/01-crds.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,10 @@ spec:
320320
default is false."
321321
type: boolean
322322
type: object
323+
loadBalancer:
324+
description: "LoadBalancer holds load balancer parameters for
325+
Ingress status. \n Contour's default is \"service:projectcontour/envoy\""
326+
type: string
323327
logging:
324328
description: Logging defines how Envoy's logs can be configured.
325329
properties:
@@ -3797,6 +3801,10 @@ spec:
37973801
Contour's default is false."
37983802
type: boolean
37993803
type: object
3804+
loadBalancer:
3805+
description: "LoadBalancer holds load balancer parameters
3806+
for Ingress status. \n Contour's default is \"service:projectcontour/envoy\""
3807+
type: string
38003808
logging:
38013809
description: Logging defines how Envoy's logs can be configured.
38023810
properties:

0 commit comments

Comments
 (0)