Skip to content

Commit 0845967

Browse files
[release-0.18] 🐛 Suppress API server warnings in the client (#2890)
* Allow client tests to inspect controller and client log messages * Verify that client respects option to suppress warnings * Correctly suppress API server warnings * fixup! Verify that client respects option to suppress warnings Always delete test namespace. --------- Co-authored-by: Daniel Lipovetsky <[email protected]>
1 parent 12cc8d5 commit 0845967

File tree

3 files changed

+114
-17
lines changed

3 files changed

+114
-17
lines changed

pkg/client/client.go

+9-13
Original file line numberDiff line numberDiff line change
@@ -124,19 +124,15 @@ func newClient(config *rest.Config, options Options) (*client, error) {
124124
config.UserAgent = rest.DefaultKubernetesUserAgent()
125125
}
126126

127-
if !options.WarningHandler.SuppressWarnings {
128-
// surface warnings
129-
logger := log.Log.WithName("KubeAPIWarningLogger")
130-
// Set a WarningHandler, the default WarningHandler
131-
// is log.KubeAPIWarningLogger with deduplication enabled.
132-
// See log.KubeAPIWarningLoggerOptions for considerations
133-
// regarding deduplication.
134-
config.WarningHandler = log.NewKubeAPIWarningLogger(
135-
logger,
136-
log.KubeAPIWarningLoggerOptions{
137-
Deduplicate: !options.WarningHandler.AllowDuplicateLogs,
138-
},
139-
)
127+
// By default, we de-duplicate and surface warnings.
128+
config.WarningHandler = log.NewKubeAPIWarningLogger(
129+
log.Log.WithName("KubeAPIWarningLogger"),
130+
log.KubeAPIWarningLoggerOptions{
131+
Deduplicate: !options.WarningHandler.AllowDuplicateLogs,
132+
},
133+
)
134+
if options.WarningHandler.SuppressWarnings {
135+
config.WarningHandler = rest.NoWarnings{}
140136
}
141137

142138
// Use the rest HTTP client for the provided config if unset

pkg/client/client_suite_test.go

+19-4
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@ limitations under the License.
1717
package client_test
1818

1919
import (
20+
"bytes"
21+
"io"
2022
"testing"
2123

2224
. "github.com/onsi/ginkgo/v2"
2325
. "github.com/onsi/gomega"
2426
"k8s.io/client-go/kubernetes"
2527
"k8s.io/client-go/kubernetes/scheme"
2628
"k8s.io/client-go/rest"
29+
"k8s.io/klog/v2"
2730
"sigs.k8s.io/controller-runtime/examples/crd/pkg"
2831
"sigs.k8s.io/controller-runtime/pkg/envtest"
2932

@@ -36,12 +39,24 @@ func TestClient(t *testing.T) {
3639
RunSpecs(t, "Client Suite")
3740
}
3841

39-
var testenv *envtest.Environment
40-
var cfg *rest.Config
41-
var clientset *kubernetes.Clientset
42+
var (
43+
testenv *envtest.Environment
44+
cfg *rest.Config
45+
clientset *kubernetes.Clientset
46+
47+
// Used by tests to inspect controller and client log messages.
48+
log bytes.Buffer
49+
)
4250

4351
var _ = BeforeSuite(func() {
44-
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
52+
// Forwards logs to ginkgo output, and allows tests to inspect logs.
53+
mw := io.MultiWriter(&log, GinkgoWriter)
54+
55+
// Use prefixes to help us tell the source of the log message.
56+
// controller-runtime uses logf
57+
logf.SetLogger(zap.New(zap.WriteTo(mw), zap.UseDevMode(true)).WithName("logf"))
58+
// client-go logs uses klog
59+
klog.SetLogger(zap.New(zap.WriteTo(mw), zap.UseDevMode(true)).WithName("klog"))
4560

4661
testenv = &envtest.Environment{CRDDirectoryPaths: []string{"./testdata"}}
4762

pkg/client/client_test.go

+86
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ limitations under the License.
1717
package client_test
1818

1919
import (
20+
"bufio"
2021
"context"
2122
"encoding/json"
2223
"errors"
2324
"fmt"
2425
"reflect"
26+
"strings"
2527
"sync/atomic"
2628
"time"
2729

@@ -226,6 +228,90 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC
226228
Expect(client.IgnoreNotFound(err)).NotTo(HaveOccurred())
227229
})
228230

231+
Describe("WarningHandler", func() {
232+
It("should log warnings when warning suppression is disabled", func() {
233+
cache := &fakeReader{}
234+
cl, err := client.New(cfg, client.Options{
235+
WarningHandler: client.WarningHandlerOptions{SuppressWarnings: false}, Cache: &client.CacheOptions{Reader: cache, DisableFor: []client.Object{&corev1.Namespace{}}},
236+
})
237+
Expect(err).NotTo(HaveOccurred())
238+
Expect(cl).NotTo(BeNil())
239+
240+
tns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "ws-disabled"}}
241+
tns, err = clientset.CoreV1().Namespaces().Create(ctx, tns, metav1.CreateOptions{})
242+
Expect(err).NotTo(HaveOccurred())
243+
Expect(tns).NotTo(BeNil())
244+
defer deleteNamespace(ctx, tns)
245+
246+
toCreate := &pkg.ChaosPod{
247+
ObjectMeta: metav1.ObjectMeta{
248+
Name: "example",
249+
Namespace: tns.Name,
250+
},
251+
// The ChaosPod CRD does not define Status, so the field is unknown to the API server,
252+
// but field validation is not strict by default, so the API server returns a warning,
253+
// and we need a warning to check whether suppression works.
254+
Status: pkg.ChaosPodStatus{},
255+
}
256+
err = cl.Create(ctx, toCreate)
257+
Expect(err).NotTo(HaveOccurred())
258+
Expect(cl).NotTo(BeNil())
259+
260+
scanner := bufio.NewScanner(&log)
261+
for scanner.Scan() {
262+
line := scanner.Text()
263+
if strings.Contains(
264+
line,
265+
"unknown field \"status\"",
266+
) {
267+
return
268+
}
269+
}
270+
defer Fail("expected to find one API server warning in the client log")
271+
})
272+
273+
It("should not log warnings when warning suppression is enabled", func() {
274+
cache := &fakeReader{}
275+
cl, err := client.New(cfg, client.Options{
276+
WarningHandler: client.WarningHandlerOptions{SuppressWarnings: true}, Cache: &client.CacheOptions{Reader: cache, DisableFor: []client.Object{&corev1.Namespace{}}},
277+
})
278+
Expect(err).NotTo(HaveOccurred())
279+
Expect(cl).NotTo(BeNil())
280+
281+
tns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "ws-enabled"}}
282+
tns, err = clientset.CoreV1().Namespaces().Create(ctx, tns, metav1.CreateOptions{})
283+
Expect(err).NotTo(HaveOccurred())
284+
Expect(tns).NotTo(BeNil())
285+
286+
toCreate := &pkg.ChaosPod{
287+
ObjectMeta: metav1.ObjectMeta{
288+
Name: "example",
289+
Namespace: tns.Name,
290+
},
291+
// The ChaosPod CRD does not define Status, so the field is unknown to the API server,
292+
// but field validation is not strict by default, so the API server returns a warning,
293+
// and we need a warning to check whether suppression works.
294+
Status: pkg.ChaosPodStatus{},
295+
}
296+
err = cl.Create(ctx, toCreate)
297+
Expect(err).NotTo(HaveOccurred())
298+
Expect(cl).NotTo(BeNil())
299+
300+
scanner := bufio.NewScanner(&log)
301+
for scanner.Scan() {
302+
line := scanner.Text()
303+
if strings.Contains(
304+
line,
305+
"unknown field \"status\"",
306+
) {
307+
defer Fail("expected to find zero API server warnings in the client log")
308+
break
309+
}
310+
}
311+
deleteNamespace(ctx, tns)
312+
})
313+
})
314+
229315
Describe("New", func() {
230316
It("should return a new Client", func() {
231317
cl, err := client.New(cfg, client.Options{})

0 commit comments

Comments
 (0)