1/*
2Copyright 2018 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package options
18
19import (
20	"strings"
21	"testing"
22	"time"
23
24	"k8s.io/apimachinery/pkg/runtime/schema"
25	utilerrors "k8s.io/apimachinery/pkg/util/errors"
26	"k8s.io/apiserver/pkg/server"
27	"k8s.io/apiserver/pkg/storage/storagebackend"
28)
29
30func TestEtcdOptionsValidate(t *testing.T) {
31	testCases := []struct {
32		name        string
33		testOptions *EtcdOptions
34		expectErr   string
35	}{
36		{
37			name: "test when ServerList is not specified",
38			testOptions: &EtcdOptions{
39				StorageConfig: storagebackend.Config{
40					Type:   "etcd3",
41					Prefix: "/registry",
42					Transport: storagebackend.TransportConfig{
43						ServerList:    nil,
44						KeyFile:       "/var/run/kubernetes/etcd.key",
45						TrustedCAFile: "/var/run/kubernetes/etcdca.crt",
46						CertFile:      "/var/run/kubernetes/etcdce.crt",
47					},
48					CompactionInterval:    storagebackend.DefaultCompactInterval,
49					CountMetricPollPeriod: time.Minute,
50				},
51				DefaultStorageMediaType: "application/vnd.kubernetes.protobuf",
52				DeleteCollectionWorkers: 1,
53				EnableGarbageCollection: true,
54				EnableWatchCache:        true,
55				DefaultWatchCacheSize:   100,
56				EtcdServersOverrides:    []string{"/events#http://127.0.0.1:4002"},
57			},
58			expectErr: "--etcd-servers must be specified",
59		},
60		{
61			name: "test when storage-backend is invalid",
62			testOptions: &EtcdOptions{
63				StorageConfig: storagebackend.Config{
64					Type:   "etcd4",
65					Prefix: "/registry",
66					Transport: storagebackend.TransportConfig{
67						ServerList:    []string{"http://127.0.0.1"},
68						KeyFile:       "/var/run/kubernetes/etcd.key",
69						TrustedCAFile: "/var/run/kubernetes/etcdca.crt",
70						CertFile:      "/var/run/kubernetes/etcdce.crt",
71					},
72					CompactionInterval:    storagebackend.DefaultCompactInterval,
73					CountMetricPollPeriod: time.Minute,
74				},
75				DefaultStorageMediaType: "application/vnd.kubernetes.protobuf",
76				DeleteCollectionWorkers: 1,
77				EnableGarbageCollection: true,
78				EnableWatchCache:        true,
79				DefaultWatchCacheSize:   100,
80				EtcdServersOverrides:    []string{"/events#http://127.0.0.1:4002"},
81			},
82			expectErr: "--storage-backend invalid, allowed values: etcd3. If not specified, it will default to 'etcd3'",
83		},
84		{
85			name: "test when etcd-servers-overrides is invalid",
86			testOptions: &EtcdOptions{
87				StorageConfig: storagebackend.Config{
88					Type: "etcd3",
89					Transport: storagebackend.TransportConfig{
90						ServerList:    []string{"http://127.0.0.1"},
91						KeyFile:       "/var/run/kubernetes/etcd.key",
92						TrustedCAFile: "/var/run/kubernetes/etcdca.crt",
93						CertFile:      "/var/run/kubernetes/etcdce.crt",
94					},
95					Prefix:                "/registry",
96					CompactionInterval:    storagebackend.DefaultCompactInterval,
97					CountMetricPollPeriod: time.Minute,
98				},
99				DefaultStorageMediaType: "application/vnd.kubernetes.protobuf",
100				DeleteCollectionWorkers: 1,
101				EnableGarbageCollection: true,
102				EnableWatchCache:        true,
103				DefaultWatchCacheSize:   100,
104				EtcdServersOverrides:    []string{"/events/http://127.0.0.1:4002"},
105			},
106			expectErr: "--etcd-servers-overrides invalid, must be of format: group/resource#servers, where servers are URLs, semicolon separated",
107		},
108		{
109			name: "test when EtcdOptions is valid",
110			testOptions: &EtcdOptions{
111				StorageConfig: storagebackend.Config{
112					Type:   "etcd3",
113					Prefix: "/registry",
114					Transport: storagebackend.TransportConfig{
115						ServerList:    []string{"http://127.0.0.1"},
116						KeyFile:       "/var/run/kubernetes/etcd.key",
117						TrustedCAFile: "/var/run/kubernetes/etcdca.crt",
118						CertFile:      "/var/run/kubernetes/etcdce.crt",
119					},
120					CompactionInterval:    storagebackend.DefaultCompactInterval,
121					CountMetricPollPeriod: time.Minute,
122				},
123				DefaultStorageMediaType: "application/vnd.kubernetes.protobuf",
124				DeleteCollectionWorkers: 1,
125				EnableGarbageCollection: true,
126				EnableWatchCache:        true,
127				DefaultWatchCacheSize:   100,
128				EtcdServersOverrides:    []string{"/events#http://127.0.0.1:4002"},
129			},
130		},
131	}
132
133	for _, testcase := range testCases {
134		t.Run(testcase.name, func(t *testing.T) {
135			errs := testcase.testOptions.Validate()
136			if len(testcase.expectErr) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), testcase.expectErr) {
137				t.Errorf("got err: %v, expected err: %s", errs, testcase.expectErr)
138			}
139			if len(testcase.expectErr) == 0 && len(errs) != 0 {
140				t.Errorf("got err: %s, expected err nil", errs)
141			}
142		})
143	}
144}
145
146func TestParseWatchCacheSizes(t *testing.T) {
147	testCases := []struct {
148		name                  string
149		cacheSizes            []string
150		expectWatchCacheSizes map[schema.GroupResource]int
151		expectErr             string
152	}{
153		{
154			name:       "test when invalid value of watch cache size",
155			cacheSizes: []string{"deployments.apps#65536", "replicasets.extensions"},
156			expectErr:  "invalid value of watch cache size",
157		},
158		{
159			name:       "test when invalid size of watch cache size",
160			cacheSizes: []string{"deployments.apps#65536", "replicasets.extensions#655d1"},
161			expectErr:  "invalid size of watch cache size",
162		},
163		{
164			name:       "test when watch cache size is negative",
165			cacheSizes: []string{"deployments.apps#65536", "replicasets.extensions#-65536"},
166			expectErr:  "watch cache size cannot be negative",
167		},
168		{
169			name:       "test when parse watch cache size success",
170			cacheSizes: []string{"deployments.apps#65536", "replicasets.extensions#65536"},
171			expectWatchCacheSizes: map[schema.GroupResource]int{
172				{Group: "apps", Resource: "deployments"}:       65536,
173				{Group: "extensions", Resource: "replicasets"}: 65536,
174			},
175		},
176	}
177
178	for _, testcase := range testCases {
179		t.Run(testcase.name, func(t *testing.T) {
180			result, err := ParseWatchCacheSizes(testcase.cacheSizes)
181			if len(testcase.expectErr) != 0 && !strings.Contains(err.Error(), testcase.expectErr) {
182				t.Errorf("got err: %v, expected err: %s", err, testcase.expectErr)
183			}
184			if len(testcase.expectErr) == 0 {
185				if err != nil {
186					t.Errorf("got err: %v, expected err nil", err)
187				} else {
188					for key, expectValue := range testcase.expectWatchCacheSizes {
189						if resultValue, exist := result[key]; !exist || resultValue != expectValue {
190							t.Errorf("got watch cache size: %v, expected watch cache size %v", result, testcase.expectWatchCacheSizes)
191						}
192					}
193				}
194			}
195		})
196	}
197}
198
199func TestKMSHealthzEndpoint(t *testing.T) {
200	testCases := []struct {
201		name                 string
202		encryptionConfigPath string
203		wantChecks           []string
204	}{
205		{
206			name:                 "single kms-provider, expect single kms healthz check",
207			encryptionConfigPath: "testdata/encryption-configs/single-kms-provider.yaml",
208			wantChecks:           []string{"etcd", "kms-provider-0"},
209		},
210		{
211			name:                 "two kms-providers, expect two kms healthz checks",
212			encryptionConfigPath: "testdata/encryption-configs/multiple-kms-providers.yaml",
213			wantChecks:           []string{"etcd", "kms-provider-0", "kms-provider-1"},
214		},
215	}
216
217	for _, tc := range testCases {
218		t.Run(tc.name, func(t *testing.T) {
219			serverConfig := &server.Config{}
220			etcdOptions := &EtcdOptions{
221				EncryptionProviderConfigFilepath: tc.encryptionConfigPath,
222			}
223			if err := etcdOptions.addEtcdHealthEndpoint(serverConfig); err != nil {
224				t.Fatalf("Failed to add healthz error: %v", err)
225			}
226
227			for _, n := range tc.wantChecks {
228				found := false
229				for _, h := range serverConfig.HealthzChecks {
230					if n == h.Name() {
231						found = true
232						break
233					}
234				}
235				if !found {
236					t.Errorf("Missing HealthzChecker %s", n)
237				}
238				found = false
239			}
240		})
241	}
242}
243