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
No known key found for this signature in database
GPG Key ID: 27CE0444346F9413
4 changed files with 213 additions and 21 deletions

2
go.mod
View File

@ -11,7 +11,7 @@ require (
github.com/spf13/cobra v1.0.0 github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.6.1 github.com/stretchr/testify v1.6.1
github.com/suzerain-io/controller-go v0.0.0-20200728175738-b49edda60499 github.com/suzerain-io/controller-go v0.0.0-20200728175738-b49edda60499
github.com/suzerain-io/placeholder-name-api v0.0.0-20200729202220-f1696913d7c9 github.com/suzerain-io/placeholder-name-api v0.0.0-20200730131400-4a1da8d7e70b
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200729202601-9b4b6d38494c github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200729202601-9b4b6d38494c
k8s.io/api v0.19.0-rc.0 k8s.io/api v0.19.0-rc.0
k8s.io/apimachinery v0.19.0-rc.0 k8s.io/apimachinery v0.19.0-rc.0

2
go.sum
View File

@ -534,6 +534,8 @@ github.com/suzerain-io/controller-go v0.0.0-20200728175738-b49edda60499 h1:2NQMY
github.com/suzerain-io/controller-go v0.0.0-20200728175738-b49edda60499/go.mod h1:+v9upryFWBJac6KXKlheGHr7e3kqpk1ldH1iIMFopMs= github.com/suzerain-io/controller-go v0.0.0-20200728175738-b49edda60499/go.mod h1:+v9upryFWBJac6KXKlheGHr7e3kqpk1ldH1iIMFopMs=
github.com/suzerain-io/placeholder-name-api v0.0.0-20200729202220-f1696913d7c9 h1:xnco3XJMrvlwyQJfKoyVPciATvCJ3Y6SY2D8gI2DT2E= github.com/suzerain-io/placeholder-name-api v0.0.0-20200729202220-f1696913d7c9 h1:xnco3XJMrvlwyQJfKoyVPciATvCJ3Y6SY2D8gI2DT2E=
github.com/suzerain-io/placeholder-name-api v0.0.0-20200729202220-f1696913d7c9/go.mod h1:OuYBJDpMMnvMUoBn+XeMWtHghuYk0cq9bNkNa3T8j/g= github.com/suzerain-io/placeholder-name-api v0.0.0-20200729202220-f1696913d7c9/go.mod h1:OuYBJDpMMnvMUoBn+XeMWtHghuYk0cq9bNkNa3T8j/g=
github.com/suzerain-io/placeholder-name-api v0.0.0-20200730131400-4a1da8d7e70b h1:7Fuizf0c3ffqyHj7X4AvXnYNFJHbSgHKjuDxDsxeQ8A=
github.com/suzerain-io/placeholder-name-api v0.0.0-20200730131400-4a1da8d7e70b/go.mod h1:OuYBJDpMMnvMUoBn+XeMWtHghuYk0cq9bNkNa3T8j/g=
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200729202601-9b4b6d38494c h1:UQVAfjF71g+v2L0ZT8MedKxrdIpx4KrHqvefnmW1AoU= github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200729202601-9b4b6d38494c h1:UQVAfjF71g+v2L0ZT8MedKxrdIpx4KrHqvefnmW1AoU=
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200729202601-9b4b6d38494c/go.mod h1:J/D+4tWlKpLpTd8Jotb7FnLl1OeT2KrUOP3Wc3ooqis= github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200729202601-9b4b6d38494c/go.mod h1:J/D+4tWlKpLpTd8Jotb7FnLl1OeT2KrUOP3Wc3ooqis=
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ= github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ=

View File

@ -6,11 +6,15 @@ SPDX-License-Identifier: Apache-2.0
package logindiscovery package logindiscovery
import ( import (
"context"
"encoding/base64" "encoding/base64"
"fmt"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2"
"github.com/suzerain-io/controller-go" "github.com/suzerain-io/controller-go"
placeholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder/v1alpha1" 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 { func (c *publisherController) Sync(ctx controller.Context) error {
configMap, err := c.kubeClient.CoreV1().ConfigMaps(clusterInfoNamespace).Get(ctx.Context, clusterInfoName, metav1.GetOptions{}) configMap, err := c.kubeClient.
if err != nil { CoreV1().
return nil // TODO should this return an error? and should it log? 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] kubeConfig, kubeConfigPresent := configMap.Data[clusterInfoConfigMapKey]
if !kubeConfigPresent { 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)) config, _ := clientcmd.Load([]byte(kubeConfig))
@ -74,10 +92,57 @@ func (c *publisherController) Sync(ctx controller.Context) error {
CertificateAuthorityData: certificateAuthorityData, CertificateAuthorityData: certificateAuthorityData,
}, },
} }
_, _ = c.placeholderClient. if err := c.createOrUpdateLoginDiscoveryConfig(ctx.Context, &discoveryConfig); err != nil {
PlaceholderV1alpha1(). return err
LoginDiscoveryConfigs(c.namespace). }
Create(ctx.Context, &discoveryConfig, metav1.CreateOptions{})
return nil 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 ( import (
"context" "context"
"errors"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -16,6 +17,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
kubernetesfake "k8s.io/client-go/kubernetes/fake" kubernetesfake "k8s.io/client-go/kubernetes/fake"
coretesting "k8s.io/client-go/testing" coretesting "k8s.io/client-go/testing"
@ -106,38 +108,144 @@ func TestRun(t *testing.T) {
err := controller.TestSync(t, subject, *controllerContext) err := controller.TestSync(t, subject, *controllerContext)
r.NoError(err) r.NoError(err)
expectedLoginDiscoveryConfigGVR, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(installedInNamespace, kubeServerURL, caData) expectedLoginDiscoveryConfigGVR, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(
installedInNamespace,
kubeServerURL,
caData,
)
expectedActions := []coretesting.Action{ 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()) 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 already exists", func() {
when("the LoginDiscoveryConfig is already up to date according to the data in the ConfigMap", func() { when("the LoginDiscoveryConfig is already up to date according to the data in the ConfigMap", func() {
it.Before(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) err := controller.TestSync(t, subject, *controllerContext)
r.NoError(err) 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() { when("the LoginDiscoveryConfig is stale compared to the data in the ConfigMap", func() {
it.Before(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() { it("updates the existing LoginDiscoveryConfig", func() {
// TODO 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()) 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{})) }, spec.Parallel(), spec.Report(report.Terminal{}))
} }