Skip to content

Commit

Permalink
[NET-7158] CRUD hooks for api gateway v2 (#3519)
Browse files Browse the repository at this point in the history
* Add hooks for CRUD side effects for apigateway controller

* Added tests for controller
  • Loading branch information
jm96441n authored Feb 6, 2024
1 parent a03b9d5 commit 55ce734
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 1 deletion.
36 changes: 35 additions & 1 deletion control-plane/controllers/resources/api-gateway-controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"

"github.com/go-logr/logr"
k8serr "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
Expand All @@ -27,7 +28,30 @@ type APIGatewayController struct {
// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute/status,verbs=get;update;patch

func (r *APIGatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
r.Logger(req.NamespacedName).Info("Reconciling APIGateway")
logger := r.Logger(req.NamespacedName)
logger.Info("Reconciling APIGateway")

resource := &meshv2beta1.APIGateway{}
if err := r.Get(ctx, req.NamespacedName, resource); k8serr.IsNotFound(err) {
return ctrl.Result{}, client.IgnoreNotFound(err)
} else if err != nil {
logger.Error(err, "retrieving resource")
return ctrl.Result{}, err
}

// Call hooks
if !resource.DeletionTimestamp.IsZero() {
logger.Info("deletion event")

if err := r.onDelete(ctx, req, resource); err != nil {
return ctrl.Result{}, err
}
} else {
if err := r.onCreateUpdate(ctx, req, resource); err != nil {
return ctrl.Result{}, err
}
}

return r.Controller.ReconcileResource(ctx, r, req, &meshv2beta1.APIGateway{})
}

Expand All @@ -42,3 +66,13 @@ func (r *APIGatewayController) UpdateStatus(ctx context.Context, obj client.Obje
func (r *APIGatewayController) SetupWithManager(mgr ctrl.Manager) error {
return setupWithManager(mgr, &meshv2beta1.APIGateway{}, r)
}

func (r *APIGatewayController) onCreateUpdate(ctx context.Context, req ctrl.Request, resource *meshv2beta1.APIGateway) error {
// TODO: NET-7449, NET-7450, and NET-7451
return nil
}

func (r *APIGatewayController) onDelete(ctx context.Context, req ctrl.Request, resource *meshv2beta1.APIGateway) error {
// TODO: NET-7449, NET-7450, and NET-7451
return nil
}
179 changes: 179 additions & 0 deletions control-plane/controllers/resources/api-gateway-controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package resources

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1"
"github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants"
"github.com/hashicorp/consul-k8s/control-plane/helper/test"
pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1"
"github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"

logrtest "github.com/go-logr/logr/testr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestAPIGatewayController_ReconcileResourceExists(t *testing.T) {
t.Parallel()
ctx := context.Background()

s := runtime.NewScheme()
s.AddKnownTypes(schema.GroupVersion{
Group: "mesh.consul.hashicorp.com",
Version: pbmesh.Version,
}, &v2beta1.APIGateway{}, &v2beta1.APIGatewayList{})

apiGW := &v2beta1.APIGateway{
ObjectMeta: metav1.ObjectMeta{
Name: "api-gateway",
Namespace: metav1.NamespaceDefault,
},
Spec: pbmesh.APIGateway{
GatewayClassName: "consul",
Listeners: []*pbmesh.APIGatewayListener{
{
Name: "http-listener",
Port: 9090,
Protocol: "http",
},
},
},
}

fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(apiGW).Build()

testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) {
c.Experiments = []string{"resource-apis"}
})

gwCtrl := APIGatewayController{
Client: fakeClient,
Log: logrtest.New(t),
Scheme: s,
Controller: &ConsulResourceController{
ConsulClientConfig: testClient.Cfg,
ConsulServerConnMgr: testClient.Watcher,
},
}

// ensure the resource is not in consul yet
{
req := &pbresource.ReadRequest{Id: apiGW.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)}
_, err := testClient.ResourceClient.Read(ctx, req)
require.Error(t, err)
}

// now reconcile the resource
{
namespacedName := types.NamespacedName{
Namespace: metav1.NamespaceDefault,
Name: apiGW.KubernetesName(),
}

// First get it, so we have the latest revision number.
err := fakeClient.Get(ctx, namespacedName, apiGW)
require.NoError(t, err)

resp, err := gwCtrl.Reconcile(ctx, ctrl.Request{
NamespacedName: namespacedName,
})

require.NoError(t, err)
require.False(t, resp.Requeue)
}

// now check that the object in Consul is as expected.
{
expectedResource := &pbmesh.APIGateway{
GatewayClassName: "consul",
Listeners: []*pbmesh.APIGatewayListener{
{
Name: "http-listener",
Port: 9090,
Protocol: "http",
},
},
}
req := &pbresource.ReadRequest{Id: apiGW.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)}
res, err := testClient.ResourceClient.Read(ctx, req)
require.NoError(t, err)
require.NotNil(t, res)
require.Equal(t, apiGW.GetName(), res.GetResource().GetId().GetName())

data := res.GetResource().Data
actual := &pbmesh.APIGateway{}
require.NoError(t, data.UnmarshalTo(actual))

opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...)
diff := cmp.Diff(expectedResource, actual, opts...)
require.Equal(t, "", diff, "APIGateway does not match")
}
}

func TestAPIGatewayController_ReconcileAPIGWDoesNotExistInK8s(t *testing.T) {
t.Parallel()
ctx := context.Background()

s := runtime.NewScheme()
s.AddKnownTypes(schema.GroupVersion{
Group: "mesh.consul.hashicorp.com",
Version: pbmesh.Version,
}, &v2beta1.APIGateway{}, &v2beta1.APIGatewayList{})

fakeClient := fake.NewClientBuilder().WithScheme(s).Build()

testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) {
c.Experiments = []string{"resource-apis"}
})

gwCtrl := APIGatewayController{
Client: fakeClient,
Log: logrtest.New(t),
Scheme: s,
Controller: &ConsulResourceController{
ConsulClientConfig: testClient.Cfg,
ConsulServerConnMgr: testClient.Watcher,
},
}

// now reconcile the resource
{
namespacedName := types.NamespacedName{
Namespace: metav1.NamespaceDefault,
Name: "api-gateway",
}

resp, err := gwCtrl.Reconcile(ctx, ctrl.Request{
NamespacedName: namespacedName,
})

require.NoError(t, err)
require.False(t, resp.Requeue)
require.Equal(t, ctrl.Result{}, resp)
}

// ensure the resource is not in consul
{
req := &pbresource.ReadRequest{Id: &pbresource.ID{
Name: "api-gateway",
Type: pbmesh.APIGatewayType,
Tenancy: &pbresource.Tenancy{
Namespace: constants.DefaultConsulNS,
Partition: constants.DefaultConsulPartition,
},
}}

_, err := testClient.ResourceClient.Read(ctx, req)
require.Error(t, err)
}
}

0 comments on commit 55ce734

Please sign in to comment.