Skip to content

Commit

Permalink
k8s gateway extensions: allow customizing translator
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenctl committed Jul 18, 2024
1 parent 8079fff commit 3af543c
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 105 deletions.
6 changes: 6 additions & 0 deletions changelog/v1.17.1/custom-gwclass-translation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
changelog:
- type: NON_USER_FACING
description: >-
Allow configuring additional gatewayclasses for controller
and customizing Proxy translation based on the gateway class.
Used by GME.
9 changes: 4 additions & 5 deletions projects/gateway2/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -41,7 +42,7 @@ const (

type GatewayConfig struct {
Mgr manager.Manager
GWClassName apiv1.ObjectName
GWClasses sets.Set[string]
Dev bool
ControllerName string
AutoProvision bool
Expand All @@ -55,7 +56,7 @@ type GatewayConfig struct {

func NewBaseGatewayController(ctx context.Context, cfg GatewayConfig) error {
log := log.FromContext(ctx)
log.V(5).Info("starting controller", "controllerName", cfg.ControllerName, "gwClassName", cfg.GWClassName)
log.V(5).Info("starting controller", "controllerName", cfg.ControllerName, "GatewayClasses", sets.List(cfg.GWClasses))

controllerBuilder := &controllerBuilder{
cfg: cfg,
Expand Down Expand Up @@ -85,7 +86,6 @@ func NewBaseGatewayController(ctx context.Context, cfg GatewayConfig) error {
controllerBuilder.addVhOptIndexes,
controllerBuilder.addGwParamsIndexes,
)

}

func run(ctx context.Context, funcs ...func(ctx context.Context) error) error {
Expand Down Expand Up @@ -179,7 +179,7 @@ func (c *controllerBuilder) watchGw(ctx context.Context) error {
For(&apiv1.Gateway{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
// we only care about Gateways that use our GatewayClass
if gw, ok := object.(*apiv1.Gateway); ok {
return gw.Spec.GatewayClassName == c.cfg.GWClassName
return c.cfg.GWClasses.Has(string(gw.Spec.GatewayClassName))
}
return false
}), predicate.GenerationChangedPredicate{}))
Expand Down Expand Up @@ -414,7 +414,6 @@ func (r *controllerReconciler) ReconcileHttpRoutes(ctx context.Context, req ctrl
}

func (r *controllerReconciler) ReconcileReferenceGrants(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {

// reconcile all things?!
r.kick(ctx)
return ctrl.Result{}, nil
Expand Down
20 changes: 10 additions & 10 deletions projects/gateway2/controller/controller_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/solo-io/gloo/projects/gateway2/wellknown"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
Expand All @@ -38,13 +39,16 @@ var (
ctx context.Context
cancel context.CancelFunc

gatewayClassName string
gatewayControllerName string
kubeconfig string
kubeconfig string
)

func getAssetsDir() string {
const (
gatewayClassName = "clsname"
altGatewayClassName = "clsname-alt"
gatewayControllerName = "controller/name"
)

func getAssetsDir() string {
assets := ""
if os.Getenv("KUBEBUILDER_ASSETS") == "" {
// set default if not user provided
Expand All @@ -61,9 +65,6 @@ var _ = BeforeSuite(func() {

ctx, cancel = context.WithCancel(context.TODO())

gatewayClassName = "clsname"
gatewayControllerName = "controller/name"

By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{
Expand Down Expand Up @@ -100,16 +101,14 @@ var _ = BeforeSuite(func() {
kubeconfig = generateKubeConfiguration(cfg)
mgr.GetLogger().Info("starting manager", "kubeconfig", kubeconfig)

var gatewayClassObjName api.ObjectName = api.ObjectName(gatewayClassName)

exts, err := extensions.NewK8sGatewayExtensions(ctx, extensions.K8sGatewayExtensionsFactoryParameters{
Mgr: mgr,
})
Expect(err).ToNot(HaveOccurred())
cfg := controller.GatewayConfig{
Mgr: mgr,
ControllerName: gatewayControllerName,
GWClassName: gatewayClassObjName,
GWClasses: sets.New(gatewayClassName, altGatewayClassName),
AutoProvision: true,
Kick: func(ctx context.Context) { return },
Extensions: exts,
Expand Down Expand Up @@ -170,6 +169,7 @@ func TestController(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Controller Suite")
}

func generateKubeConfiguration(restconfig *rest.Config) string {
clusters := make(map[string]*clientcmdapi.Cluster)
authinfos := make(map[string]*clientcmdapi.AuthInfo)
Expand Down
123 changes: 64 additions & 59 deletions projects/gateway2/controller/gw_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,70 +17,75 @@ var _ = Describe("GwController", func() {
interval = time.Millisecond * 250
)

It("should add status to gateway", func() {
same := api.NamespacesFromSame
gw := api.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "gw",
Namespace: "default",
},
Spec: api.GatewaySpec{
GatewayClassName: api.ObjectName(gatewayClassName),
Listeners: []api.Listener{{
Protocol: "HTTP",
Port: 80,
AllowedRoutes: &api.AllowedRoutes{
Namespaces: &api.RouteNamespaces{
From: &same,
DescribeTable(
"should add status to gateway",
func(gwClass string) {
print(gwClass)
same := api.NamespacesFromSame
gwName := "gw-" + gwClass
gw := api.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: gwName,
Namespace: "default",
},
Spec: api.GatewaySpec{
GatewayClassName: api.ObjectName(gwClass),
Listeners: []api.Listener{{
Protocol: "HTTP",
Port: 80,
AllowedRoutes: &api.AllowedRoutes{
Namespaces: &api.RouteNamespaces{
From: &same,
},
},
},
Name: "listener",
}},
},
}
err := k8sClient.Create(ctx, &gw)
Expect(err).NotTo(HaveOccurred())

// Wait for service to be created
var svc corev1.Service
Eventually(func() bool {
var createdServices corev1.ServiceList
err := k8sClient.List(ctx, &createdServices)
if err != nil {
return false
}
for _, svc = range createdServices.Items {
if len(svc.ObjectMeta.OwnerReferences) == 1 && svc.ObjectMeta.OwnerReferences[0].UID == gw.UID {
return true
}
Name: "listener",
}},
},
}
return false
}, timeout, interval).Should(BeTrue(), "service not created")
Expect(svc.Spec.ClusterIP).NotTo(BeEmpty())

// Need to update the status of the service
svc.Status.LoadBalancer = corev1.LoadBalancerStatus{
Ingress: []corev1.LoadBalancerIngress{{
IP: "127.0.0.1",
}},
}
Expect(k8sClient.Status().Update(ctx, &svc)).NotTo(HaveOccurred())
err := k8sClient.Create(ctx, &gw)
Expect(err).NotTo(HaveOccurred())

Eventually(func() bool {
err := k8sClient.Get(ctx, client.ObjectKey{Name: "gw", Namespace: "default"}, &gw)
if err != nil {
return false
}
if len(gw.Status.Addresses) == 0 {
// Wait for service to be created
var svc corev1.Service
Eventually(func() bool {
var createdServices corev1.ServiceList
err := k8sClient.List(ctx, &createdServices)
if err != nil {
return false
}
for _, svc = range createdServices.Items {
if len(svc.ObjectMeta.OwnerReferences) == 1 && svc.ObjectMeta.OwnerReferences[0].UID == gw.UID {
return true
}
}
return false
}
return true
}, timeout, interval).Should(BeTrue())
}, timeout, interval).Should(BeTrue(), "service not created")
Expect(svc.Spec.ClusterIP).NotTo(BeEmpty())

Expect(gw.Status.Addresses).To(HaveLen(1))
Expect(*gw.Status.Addresses[0].Type).To(Equal(api.IPAddressType))
Expect(gw.Status.Addresses[0].Value).To(Equal("127.0.0.1"))
// Need to update the status of the service
svc.Status.LoadBalancer = corev1.LoadBalancerStatus{
Ingress: []corev1.LoadBalancerIngress{{
IP: "127.0.0.1",
}},
}
Expect(k8sClient.Status().Update(ctx, &svc)).NotTo(HaveOccurred())

})
Eventually(func() bool {
err := k8sClient.Get(ctx, client.ObjectKey{Name: gwName, Namespace: "default"}, &gw)
if err != nil {
return false
}
if len(gw.Status.Addresses) == 0 {
return false
}
return true
}, timeout, interval).Should(BeTrue())

Expect(gw.Status.Addresses).To(HaveLen(1))
Expect(*gw.Status.Addresses[0].Type).To(Equal(api.IPAddressType))
Expect(gw.Status.Addresses[0].Value).To(Equal("127.0.0.1"))
},
Entry("default gateway class", gatewayClassName),
Entry("alternative gateway class", altGatewayClassName),
)
})
11 changes: 3 additions & 8 deletions projects/gateway2/controller/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import (

"github.com/solo-io/gloo/projects/gloo/pkg/servers/iosnapshot"

"k8s.io/apimachinery/pkg/util/sets"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
apiv1 "sigs.k8s.io/gateway-api/apis/v1"

gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1"
"github.com/solo-io/gloo/projects/gateway2/controller/scheme"
Expand All @@ -30,11 +30,7 @@ const (
AutoProvision = true
)

var (
gatewayClassName = apiv1.ObjectName(wellknown.GatewayClassName)

setupLog = ctrl.Log.WithName("setup")
)
var setupLog = ctrl.Log.WithName("setup")

type StartConfig struct {
Dev bool
Expand Down Expand Up @@ -137,8 +133,7 @@ func Start(ctx context.Context, cfg StartConfig) error {

gwCfg := GatewayConfig{
Mgr: mgr,
GWClassName: gatewayClassName,
ControllerName: wellknown.GatewayControllerName,
GWClasses: sets.New(append(cfg.Opts.ExtraGatewayClasses, wellknown.GatewayClassName)...),
AutoProvision: AutoProvision,
ControlPlane: cfg.Opts.ControlPlane,
IstioValues: cfg.Opts.GlooGateway.IstioValues,
Expand Down
47 changes: 31 additions & 16 deletions projects/gateway2/extensions/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ import (

gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1"
"github.com/solo-io/gloo/projects/gateway2/query"
"github.com/solo-io/gloo/projects/gateway2/translator"
"github.com/solo-io/gloo/projects/gateway2/translator/plugins/registry"
"github.com/solo-io/solo-kit/pkg/api/v2/reporter"
controllerruntime "sigs.k8s.io/controller-runtime"
apiv1 "sigs.k8s.io/gateway-api/apis/v1"
)

// K8sGatewayExtensions is responsible for providing implementations for translation utilities
// which have Enterprise variants.
type K8sGatewayExtensions interface {
// CreatePluginRegistry returns the PluginRegistry
CreatePluginRegistry(ctx context.Context) registry.PluginRegistry
// GetPluginRegistry exposes the plugins supported by this implementation.
GetPluginRegistry() registry.PluginRegistry

// GetTranslator allows an extnsion to provide custom translation for
// different gateway classes.
GetTranslator(*apiv1.Gateway) translator.K8sGwTranslator
}

// K8sGatewayExtensionsFactoryParameters contains the parameters required to start Gloo K8s Gateway Extensions (including Translator Plugins)
Expand All @@ -40,11 +46,26 @@ func NewK8sGatewayExtensions(
_ context.Context,
params K8sGatewayExtensionsFactoryParameters,
) (K8sGatewayExtensions, error) {
queries := query.NewData(
params.Mgr.GetClient(),
params.Mgr.GetScheme(),
)
plugins := registry.BuildPlugins(
queries,
params.Mgr.GetClient(),
params.RouteOptionClient,
params.VirtualHostOptionClient,
params.StatusReporter,
)
pluginRegistry := registry.NewPluginRegistry(plugins)

return &k8sGatewayExtensions{
mgr: params.Mgr,
routeOptionClient: params.RouteOptionClient,
virtualHostOptionClient: params.VirtualHostOptionClient,
statusReporter: params.StatusReporter,
translator: translator.NewTranslator(queries, pluginRegistry),
pluginRegistry: pluginRegistry,
}, nil
}

Expand All @@ -53,20 +74,14 @@ type k8sGatewayExtensions struct {
routeOptionClient gatewayv1.RouteOptionClient
virtualHostOptionClient gatewayv1.VirtualHostOptionClient
statusReporter reporter.StatusReporter
translator translator.K8sGwTranslator
pluginRegistry registry.PluginRegistry
}

// CreatePluginRegistry returns the PluginRegistry
func (e *k8sGatewayExtensions) CreatePluginRegistry(_ context.Context) registry.PluginRegistry {
queries := query.NewData(
e.mgr.GetClient(),
e.mgr.GetScheme(),
)
plugins := registry.BuildPlugins(
queries,
e.mgr.GetClient(),
e.routeOptionClient,
e.virtualHostOptionClient,
e.statusReporter,
)
return registry.NewPluginRegistry(plugins)
func (e *k8sGatewayExtensions) GetTranslator(_ *apiv1.Gateway) translator.K8sGwTranslator {
return e.translator
}

func (e *k8sGatewayExtensions) GetPluginRegistry() registry.PluginRegistry {
return e.pluginRegistry
}
Loading

0 comments on commit 3af543c

Please sign in to comment.