logindiscovery: add tests for conditional update and error cases

- Also add some log lines for better observability of behavior.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
This commit is contained in:
Andrew Keesler
2020-07-30 10:39:15 -04:00
parent e0cac97084
commit 9a859875a7
4 changed files with 213 additions and 21 deletions

View File

@@ -6,11 +6,15 @@ SPDX-License-Identifier: Apache-2.0
package logindiscovery
import (
"context"
"encoding/base64"
"fmt"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2"
"github.com/suzerain-io/controller-go"
placeholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder/v1alpha1"
@@ -45,13 +49,27 @@ func NewPublisherController(namespace string, kubeClient kubernetes.Interface, p
}
func (c *publisherController) Sync(ctx controller.Context) error {
configMap, err := c.kubeClient.CoreV1().ConfigMaps(clusterInfoNamespace).Get(ctx.Context, clusterInfoName, metav1.GetOptions{})
if err != nil {
return nil // TODO should this return an error? and should it log?
configMap, err := c.kubeClient.
CoreV1().
ConfigMaps(clusterInfoNamespace).
Get(ctx.Context, clusterInfoName, metav1.GetOptions{})
notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound {
return fmt.Errorf("failed to get %s configmap: %w", clusterInfoName, err)
}
if notFound {
klog.InfoS(
"could not find config map",
"configmap",
klog.KRef(clusterInfoNamespace, clusterInfoName),
)
return nil
}
kubeConfig, kubeConfigPresent := configMap.Data[clusterInfoConfigMapKey]
if !kubeConfigPresent {
return nil // TODO should this return an error? and should it log?
klog.InfoS("could not find kubeconfig configmap key")
return nil
}
config, _ := clientcmd.Load([]byte(kubeConfig))
@@ -74,10 +92,57 @@ func (c *publisherController) Sync(ctx controller.Context) error {
CertificateAuthorityData: certificateAuthorityData,
},
}
_, _ = c.placeholderClient.
PlaceholderV1alpha1().
LoginDiscoveryConfigs(c.namespace).
Create(ctx.Context, &discoveryConfig, metav1.CreateOptions{})
if err := c.createOrUpdateLoginDiscoveryConfig(ctx.Context, &discoveryConfig); err != nil {
return err
}
return nil
}
func (c *publisherController) createOrUpdateLoginDiscoveryConfig(
ctx context.Context,
discoveryConfig *placeholderv1alpha1.LoginDiscoveryConfig,
) error {
loginDiscoveryConfigs := c.placeholderClient.
PlaceholderV1alpha1().
LoginDiscoveryConfigs(c.namespace)
existingDiscoveryConfig, err := loginDiscoveryConfigs.Get(
ctx,
discoveryConfig.Name,
metav1.GetOptions{},
)
notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound {
return fmt.Errorf("could not get logindiscoveryconfig: %w", err)
}
if notFound {
if _, err := loginDiscoveryConfigs.Create(
ctx,
discoveryConfig,
metav1.CreateOptions{},
); err != nil {
return fmt.Errorf("could not create logindiscoveryconfig: %w", err)
}
} else if !equal(existingDiscoveryConfig, discoveryConfig) {
// Update just the fields we care about.
existingDiscoveryConfig.Spec.Server = discoveryConfig.Spec.Server
existingDiscoveryConfig.Spec.CertificateAuthorityData = discoveryConfig.Spec.CertificateAuthorityData
if _, err := loginDiscoveryConfigs.Update(
ctx,
existingDiscoveryConfig,
metav1.UpdateOptions{},
); err != nil {
return fmt.Errorf("could not update logindiscoveryconfig: %w", err)
}
}
return nil
}
func equal(a, b *placeholderv1alpha1.LoginDiscoveryConfig) bool {
return a.Spec.Server == b.Spec.Server &&
a.Spec.CertificateAuthorityData == b.Spec.CertificateAuthorityData
}

View File

@@ -7,6 +7,7 @@ package logindiscovery
import (
"context"
"errors"
"strings"
"testing"
"time"
@@ -16,6 +17,7 @@ import (
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
kubernetesfake "k8s.io/client-go/kubernetes/fake"
coretesting "k8s.io/client-go/testing"
@@ -106,38 +108,144 @@ func TestRun(t *testing.T) {
err := controller.TestSync(t, subject, *controllerContext)
r.NoError(err)
expectedLoginDiscoveryConfigGVR, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(installedInNamespace, kubeServerURL, caData)
expectedLoginDiscoveryConfigGVR, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(
installedInNamespace,
kubeServerURL,
caData,
)
expectedActions := []coretesting.Action{
coretesting.NewCreateAction(expectedLoginDiscoveryConfigGVR, installedInNamespace, expectedLoginDiscoveryConfig),
coretesting.NewGetAction(
expectedLoginDiscoveryConfigGVR,
installedInNamespace,
expectedLoginDiscoveryConfig.Name,
),
coretesting.NewCreateAction(
expectedLoginDiscoveryConfigGVR,
installedInNamespace,
expectedLoginDiscoveryConfig,
),
}
// Expect a LoginDiscoveryConfig to be created with the fields from the cluster-info ConfigMap
actualCreatedObject := placeholderClient.Actions()[0].(coretesting.CreateActionImpl).Object
r.Equal(expectedLoginDiscoveryConfig, actualCreatedObject)
r.Equal(expectedActions, placeholderClient.Actions())
})
when("creating the LoginDiscoveryConfig fails", func() {
it.Before(func() {
placeholderClient.PrependReactor(
"create",
"logindiscoveryconfigs",
func(_ coretesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("create failed")
},
)
})
it("returns the create error", func() {
err := controller.TestSync(t, subject, *controllerContext)
r.EqualError(err, "could not create logindiscoveryconfig: create failed")
})
})
})
when("the LoginDiscoveryConfig already exists", func() {
when("the LoginDiscoveryConfig is already up to date according to the data in the ConfigMap", func() {
it.Before(func() {
// TODO add a fake LoginDiscoveryConfig to the placeholderClient whose data matches the ConfigMap's data
_, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(
installedInNamespace,
kubeServerURL,
caData,
)
err := placeholderClient.Tracker().Add(expectedLoginDiscoveryConfig)
r.NoError(err)
})
it.Pend("does not update the LoginDiscoveryConfig to avoid unnecessary etcd writes", func() {
it("does not update the LoginDiscoveryConfig to avoid unnecessary etcd writes/api calls", func() {
err := controller.TestSync(t, subject, *controllerContext)
r.NoError(err)
r.Empty(placeholderClient.Actions())
expectedLoginDiscoveryConfigGVR, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(
installedInNamespace,
kubeServerURL,
caData,
)
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
expectedLoginDiscoveryConfigGVR,
installedInNamespace,
expectedLoginDiscoveryConfig.Name,
),
}
r.Equal(expectedActions, placeholderClient.Actions())
})
when("getting the LoginDiscoveryConfig fails", func() {
it.Before(func() {
placeholderClient.PrependReactor(
"get",
"logindiscoveryconfigs",
func(_ coretesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("get failed")
},
)
})
it("returns the get error", func() {
err := controller.TestSync(t, subject, *controllerContext)
r.EqualError(err, "could not get logindiscoveryconfig: get failed")
})
})
})
when("the LoginDiscoveryConfig is stale compared to the data in the ConfigMap", func() {
it.Before(func() {
// TODO add a fake LoginDiscoveryConfig to the placeholderClient whose data does not match the ConfigMap's data
_, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(
installedInNamespace,
kubeServerURL,
caData,
)
expectedLoginDiscoveryConfig.Spec.Server = "https://some-other-server"
err := placeholderClient.Tracker().Add(expectedLoginDiscoveryConfig)
r.NoError(err)
})
it.Pend("updates the existing LoginDiscoveryConfig", func() {
// TODO
it("updates the existing LoginDiscoveryConfig", func() {
err := controller.TestSync(t, subject, *controllerContext)
r.NoError(err)
expectedLoginDiscoveryConfigGVR, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(
installedInNamespace,
kubeServerURL,
caData,
)
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
expectedLoginDiscoveryConfigGVR,
installedInNamespace,
expectedLoginDiscoveryConfig.Name,
),
coretesting.NewUpdateAction(
expectedLoginDiscoveryConfigGVR,
installedInNamespace,
expectedLoginDiscoveryConfig,
),
}
r.Equal(expectedActions, placeholderClient.Actions())
})
when("updating the LoginDiscoveryConfig fails", func() {
it.Before(func() {
placeholderClient.PrependReactor(
"update",
"logindiscoveryconfigs",
func(_ coretesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("update failed")
},
)
})
it("returns the update error", func() {
err := controller.TestSync(t, subject, *controllerContext)
r.EqualError(err, "could not update logindiscoveryconfig: update failed")
})
})
})
})
@@ -181,5 +289,22 @@ func TestRun(t *testing.T) {
r.Empty(placeholderClient.Actions())
})
})
when("getting the cluster-info ConfigMap in the kube-public namespace fails", func() {
it.Before(func() {
kubeClient.PrependReactor(
"get",
"configmaps",
func(_ coretesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("get failed")
},
)
})
it("returns an error", func() {
err := controller.TestSync(t, subject, *controllerContext)
r.EqualError(err, "failed to get cluster-info configmap: get failed")
})
})
}, spec.Parallel(), spec.Report(report.Terminal{}))
}