validation_test.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package validation
  14. import (
  15. "fmt"
  16. "runtime"
  17. "strings"
  18. "testing"
  19. "time"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/util/validation/field"
  22. componentbaseconfig "k8s.io/component-base/config"
  23. kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config"
  24. "k8s.io/utils/pointer"
  25. )
  26. func TestValidateKubeProxyConfiguration(t *testing.T) {
  27. var proxyMode kubeproxyconfig.ProxyMode
  28. if runtime.GOOS == "windows" {
  29. proxyMode = kubeproxyconfig.ProxyModeKernelspace
  30. } else {
  31. proxyMode = kubeproxyconfig.ProxyModeIPVS
  32. }
  33. successCases := []kubeproxyconfig.KubeProxyConfiguration{
  34. {
  35. BindAddress: "192.168.59.103",
  36. HealthzBindAddress: "0.0.0.0:10256",
  37. MetricsBindAddress: "127.0.0.1:10249",
  38. ClusterCIDR: "192.168.59.0/24",
  39. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  40. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  41. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  42. MasqueradeAll: true,
  43. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  44. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  45. },
  46. Mode: proxyMode,
  47. IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{
  48. SyncPeriod: metav1.Duration{Duration: 10 * time.Second},
  49. MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  50. },
  51. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  52. MaxPerCore: pointer.Int32Ptr(1),
  53. Min: pointer.Int32Ptr(1),
  54. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  55. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  56. },
  57. },
  58. {
  59. BindAddress: "192.168.59.103",
  60. HealthzBindAddress: "0.0.0.0:10256",
  61. MetricsBindAddress: "127.0.0.1:10249",
  62. ClusterCIDR: "192.168.59.0/24",
  63. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  64. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  65. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  66. MasqueradeAll: true,
  67. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  68. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  69. },
  70. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  71. MaxPerCore: pointer.Int32Ptr(1),
  72. Min: pointer.Int32Ptr(1),
  73. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  74. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  75. },
  76. },
  77. {
  78. BindAddress: "192.168.59.103",
  79. HealthzBindAddress: "",
  80. MetricsBindAddress: "127.0.0.1:10249",
  81. ClusterCIDR: "192.168.59.0/24",
  82. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  83. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  84. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  85. MasqueradeAll: true,
  86. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  87. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  88. },
  89. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  90. MaxPerCore: pointer.Int32Ptr(1),
  91. Min: pointer.Int32Ptr(1),
  92. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  93. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  94. },
  95. },
  96. {
  97. BindAddress: "fd00:192:168:59::103",
  98. HealthzBindAddress: "",
  99. MetricsBindAddress: "[::1]:10249",
  100. ClusterCIDR: "fd00:192:168:59::/64",
  101. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  102. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  103. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  104. MasqueradeAll: true,
  105. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  106. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  107. },
  108. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  109. MaxPerCore: pointer.Int32Ptr(1),
  110. Min: pointer.Int32Ptr(1),
  111. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  112. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  113. },
  114. },
  115. {
  116. BindAddress: "10.10.12.11",
  117. HealthzBindAddress: "0.0.0.0:12345",
  118. MetricsBindAddress: "127.0.0.1:10249",
  119. FeatureGates: map[string]bool{"IPv6DualStack": true},
  120. ClusterCIDR: "192.168.59.0/24",
  121. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  122. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  123. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  124. MasqueradeAll: true,
  125. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  126. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  127. },
  128. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  129. MaxPerCore: pointer.Int32Ptr(1),
  130. Min: pointer.Int32Ptr(1),
  131. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  132. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  133. },
  134. },
  135. {
  136. BindAddress: "10.10.12.11",
  137. HealthzBindAddress: "0.0.0.0:12345",
  138. MetricsBindAddress: "127.0.0.1:10249",
  139. FeatureGates: map[string]bool{"IPv6DualStack": true},
  140. ClusterCIDR: "fd00:192:168::/64",
  141. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  142. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  143. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  144. MasqueradeAll: true,
  145. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  146. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  147. },
  148. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  149. MaxPerCore: pointer.Int32Ptr(1),
  150. Min: pointer.Int32Ptr(1),
  151. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  152. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  153. },
  154. },
  155. {
  156. BindAddress: "10.10.12.11",
  157. HealthzBindAddress: "0.0.0.0:12345",
  158. MetricsBindAddress: "127.0.0.1:10249",
  159. FeatureGates: map[string]bool{"IPv6DualStack": true},
  160. ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64",
  161. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  162. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  163. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  164. MasqueradeAll: true,
  165. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  166. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  167. },
  168. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  169. MaxPerCore: pointer.Int32Ptr(1),
  170. Min: pointer.Int32Ptr(1),
  171. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  172. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  173. },
  174. },
  175. }
  176. for _, successCase := range successCases {
  177. if errs := Validate(&successCase); len(errs) != 0 {
  178. t.Errorf("expected success: %v", errs)
  179. }
  180. }
  181. errorCases := []struct {
  182. config kubeproxyconfig.KubeProxyConfiguration
  183. msg string
  184. }{
  185. {
  186. config: kubeproxyconfig.KubeProxyConfiguration{
  187. // only BindAddress is invalid
  188. BindAddress: "10.10.12.11:2000",
  189. HealthzBindAddress: "0.0.0.0:10256",
  190. MetricsBindAddress: "127.0.0.1:10249",
  191. ClusterCIDR: "192.168.59.0/24",
  192. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  193. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  194. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  195. MasqueradeAll: true,
  196. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  197. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  198. },
  199. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  200. MaxPerCore: pointer.Int32Ptr(1),
  201. Min: pointer.Int32Ptr(1),
  202. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  203. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  204. },
  205. },
  206. msg: "not a valid textual representation of an IP address",
  207. },
  208. {
  209. config: kubeproxyconfig.KubeProxyConfiguration{
  210. BindAddress: "10.10.12.11",
  211. // only HealthzBindAddress is invalid
  212. HealthzBindAddress: "0.0.0.0",
  213. MetricsBindAddress: "127.0.0.1:10249",
  214. ClusterCIDR: "192.168.59.0/24",
  215. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  216. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  217. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  218. MasqueradeAll: true,
  219. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  220. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  221. },
  222. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  223. MaxPerCore: pointer.Int32Ptr(1),
  224. Min: pointer.Int32Ptr(1),
  225. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  226. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  227. },
  228. },
  229. msg: "must be IP:port",
  230. },
  231. {
  232. config: kubeproxyconfig.KubeProxyConfiguration{
  233. BindAddress: "10.10.12.11",
  234. HealthzBindAddress: "0.0.0.0:12345",
  235. // only MetricsBindAddress is invalid
  236. MetricsBindAddress: "127.0.0.1",
  237. ClusterCIDR: "192.168.59.0/24",
  238. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  239. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  240. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  241. MasqueradeAll: true,
  242. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  243. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  244. },
  245. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  246. MaxPerCore: pointer.Int32Ptr(1),
  247. Min: pointer.Int32Ptr(1),
  248. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  249. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  250. },
  251. },
  252. msg: "must be IP:port",
  253. },
  254. {
  255. config: kubeproxyconfig.KubeProxyConfiguration{
  256. BindAddress: "10.10.12.11",
  257. HealthzBindAddress: "0.0.0.0:12345",
  258. MetricsBindAddress: "127.0.0.1:10249",
  259. // only ClusterCIDR is invalid
  260. ClusterCIDR: "192.168.59.0",
  261. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  262. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  263. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  264. MasqueradeAll: true,
  265. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  266. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  267. },
  268. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  269. MaxPerCore: pointer.Int32Ptr(1),
  270. Min: pointer.Int32Ptr(1),
  271. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  272. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  273. },
  274. },
  275. msg: "must be a valid CIDR block (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)",
  276. },
  277. {
  278. config: kubeproxyconfig.KubeProxyConfiguration{
  279. BindAddress: "10.10.12.11",
  280. HealthzBindAddress: "0.0.0.0:12345",
  281. MetricsBindAddress: "127.0.0.1:10249",
  282. // DualStack ClusterCIDR without feature flag enabled
  283. FeatureGates: map[string]bool{"IPv6DualStack": false},
  284. ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64",
  285. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  286. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  287. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  288. MasqueradeAll: true,
  289. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  290. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  291. },
  292. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  293. MaxPerCore: pointer.Int32Ptr(1),
  294. Min: pointer.Int32Ptr(1),
  295. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  296. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  297. },
  298. },
  299. msg: "only one CIDR allowed (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)",
  300. },
  301. {
  302. config: kubeproxyconfig.KubeProxyConfiguration{
  303. BindAddress: "10.10.12.11",
  304. HealthzBindAddress: "0.0.0.0:12345",
  305. MetricsBindAddress: "127.0.0.1:10249",
  306. // DualStack with multiple CIDRs but only one IP family
  307. FeatureGates: map[string]bool{"IPv6DualStack": true},
  308. ClusterCIDR: "192.168.59.0/24,10.0.0.0/16",
  309. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  310. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  311. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  312. MasqueradeAll: true,
  313. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  314. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  315. },
  316. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  317. MaxPerCore: pointer.Int32Ptr(1),
  318. Min: pointer.Int32Ptr(1),
  319. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  320. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  321. },
  322. },
  323. msg: "must be a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)",
  324. },
  325. {
  326. config: kubeproxyconfig.KubeProxyConfiguration{
  327. BindAddress: "10.10.12.11",
  328. HealthzBindAddress: "0.0.0.0:12345",
  329. MetricsBindAddress: "127.0.0.1:10249",
  330. // DualStack with an invalid subnet
  331. FeatureGates: map[string]bool{"IPv6DualStack": true},
  332. ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64,a.b.c.d/f",
  333. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  334. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  335. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  336. MasqueradeAll: true,
  337. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  338. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  339. },
  340. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  341. MaxPerCore: pointer.Int32Ptr(1),
  342. Min: pointer.Int32Ptr(1),
  343. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  344. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  345. },
  346. },
  347. msg: "only one CIDR allowed or a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)",
  348. },
  349. {
  350. config: kubeproxyconfig.KubeProxyConfiguration{
  351. BindAddress: "10.10.12.11",
  352. HealthzBindAddress: "0.0.0.0:12345",
  353. MetricsBindAddress: "127.0.0.1:10249",
  354. FeatureGates: map[string]bool{"IPv6DualStack": true},
  355. ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64,10.0.0.0/16",
  356. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  357. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  358. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  359. MasqueradeAll: true,
  360. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  361. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  362. },
  363. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  364. MaxPerCore: pointer.Int32Ptr(1),
  365. Min: pointer.Int32Ptr(1),
  366. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  367. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  368. },
  369. },
  370. msg: "only one CIDR allowed or a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)",
  371. },
  372. {
  373. config: kubeproxyconfig.KubeProxyConfiguration{
  374. BindAddress: "10.10.12.11",
  375. HealthzBindAddress: "0.0.0.0:12345",
  376. MetricsBindAddress: "127.0.0.1:10249",
  377. ClusterCIDR: "192.168.59.0/24",
  378. // only UDPIdleTimeout is invalid
  379. UDPIdleTimeout: metav1.Duration{Duration: -1 * time.Second},
  380. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  381. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  382. MasqueradeAll: true,
  383. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  384. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  385. },
  386. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  387. MaxPerCore: pointer.Int32Ptr(1),
  388. Min: pointer.Int32Ptr(1),
  389. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  390. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  391. },
  392. },
  393. msg: "must be greater than 0",
  394. },
  395. {
  396. config: kubeproxyconfig.KubeProxyConfiguration{
  397. BindAddress: "10.10.12.11",
  398. HealthzBindAddress: "0.0.0.0:12345",
  399. MetricsBindAddress: "127.0.0.1:10249",
  400. ClusterCIDR: "192.168.59.0/24",
  401. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  402. // only ConfigSyncPeriod is invalid
  403. ConfigSyncPeriod: metav1.Duration{Duration: -1 * time.Second},
  404. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  405. MasqueradeAll: true,
  406. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  407. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  408. },
  409. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  410. MaxPerCore: pointer.Int32Ptr(1),
  411. Min: pointer.Int32Ptr(1),
  412. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  413. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  414. },
  415. },
  416. msg: "must be greater than 0",
  417. },
  418. {
  419. config: kubeproxyconfig.KubeProxyConfiguration{
  420. BindAddress: "192.168.59.103",
  421. HealthzBindAddress: "0.0.0.0:10256",
  422. MetricsBindAddress: "127.0.0.1:10249",
  423. ClusterCIDR: "192.168.59.0/24",
  424. UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
  425. ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  426. IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  427. MasqueradeAll: true,
  428. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  429. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  430. },
  431. // not specifying valid period in IPVS mode.
  432. Mode: kubeproxyconfig.ProxyModeIPVS,
  433. Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
  434. MaxPerCore: pointer.Int32Ptr(1),
  435. Min: pointer.Int32Ptr(1),
  436. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  437. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  438. },
  439. },
  440. msg: "must be greater than 0",
  441. },
  442. }
  443. for _, errorCase := range errorCases {
  444. if errs := Validate(&errorCase.config); len(errs) == 0 {
  445. t.Errorf("expected failure for %s", errorCase.msg)
  446. } else if !strings.Contains(errs[0].Error(), errorCase.msg) {
  447. t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg)
  448. }
  449. }
  450. }
  451. func TestValidateKubeProxyIPTablesConfiguration(t *testing.T) {
  452. valid := int32(5)
  453. successCases := []kubeproxyconfig.KubeProxyIPTablesConfiguration{
  454. {
  455. MasqueradeAll: true,
  456. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  457. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  458. },
  459. {
  460. MasqueradeBit: &valid,
  461. MasqueradeAll: true,
  462. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  463. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  464. },
  465. }
  466. newPath := field.NewPath("KubeProxyConfiguration")
  467. for _, successCase := range successCases {
  468. if errs := validateKubeProxyIPTablesConfiguration(successCase, newPath.Child("KubeProxyIPTablesConfiguration")); len(errs) != 0 {
  469. t.Errorf("expected success: %v", errs)
  470. }
  471. }
  472. invalid := int32(-10)
  473. errorCases := []struct {
  474. config kubeproxyconfig.KubeProxyIPTablesConfiguration
  475. msg string
  476. }{
  477. {
  478. config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  479. MasqueradeAll: true,
  480. SyncPeriod: metav1.Duration{Duration: -5 * time.Second},
  481. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  482. },
  483. msg: "must be greater than 0",
  484. },
  485. {
  486. config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  487. MasqueradeBit: &valid,
  488. MasqueradeAll: true,
  489. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  490. MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second},
  491. },
  492. msg: "must be greater than or equal to 0",
  493. },
  494. {
  495. config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  496. MasqueradeBit: &invalid,
  497. MasqueradeAll: true,
  498. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  499. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  500. },
  501. msg: "must be within the range [0, 31]",
  502. },
  503. // SyncPeriod must be >= MinSyncPeriod
  504. {
  505. config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
  506. MasqueradeBit: &valid,
  507. MasqueradeAll: true,
  508. SyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  509. MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  510. },
  511. msg: fmt.Sprintf("must be greater than or equal to %s", newPath.Child("KubeProxyIPTablesConfiguration").Child("MinSyncPeriod").String()),
  512. },
  513. }
  514. for _, errorCase := range errorCases {
  515. if errs := validateKubeProxyIPTablesConfiguration(errorCase.config, newPath.Child("KubeProxyIPTablesConfiguration")); len(errs) == 0 {
  516. t.Errorf("expected failure for %s", errorCase.msg)
  517. } else if !strings.Contains(errs[0].Error(), errorCase.msg) {
  518. t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg)
  519. }
  520. }
  521. }
  522. func TestValidateKubeProxyIPVSConfiguration(t *testing.T) {
  523. newPath := field.NewPath("KubeProxyConfiguration")
  524. testCases := []struct {
  525. config kubeproxyconfig.KubeProxyIPVSConfiguration
  526. expectErr bool
  527. reason string
  528. }{
  529. {
  530. config: kubeproxyconfig.KubeProxyIPVSConfiguration{
  531. SyncPeriod: metav1.Duration{Duration: -5 * time.Second},
  532. MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
  533. },
  534. expectErr: true,
  535. reason: "SyncPeriod must be greater than 0",
  536. },
  537. {
  538. config: kubeproxyconfig.KubeProxyIPVSConfiguration{
  539. SyncPeriod: metav1.Duration{Duration: 0 * time.Second},
  540. MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
  541. },
  542. expectErr: true,
  543. reason: "SyncPeriod must be greater than 0",
  544. },
  545. {
  546. config: kubeproxyconfig.KubeProxyIPVSConfiguration{
  547. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  548. MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second},
  549. },
  550. expectErr: true,
  551. reason: "MinSyncPeriod must be greater than or equal to 0",
  552. },
  553. {
  554. config: kubeproxyconfig.KubeProxyIPVSConfiguration{
  555. SyncPeriod: metav1.Duration{Duration: 1 * time.Second},
  556. MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  557. },
  558. expectErr: true,
  559. reason: "SyncPeriod must be greater than or equal to MinSyncPeriod",
  560. },
  561. // SyncPeriod == MinSyncPeriod
  562. {
  563. config: kubeproxyconfig.KubeProxyIPVSConfiguration{
  564. SyncPeriod: metav1.Duration{Duration: 10 * time.Second},
  565. MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
  566. },
  567. expectErr: false,
  568. },
  569. // SyncPeriod > MinSyncPeriod
  570. {
  571. config: kubeproxyconfig.KubeProxyIPVSConfiguration{
  572. SyncPeriod: metav1.Duration{Duration: 10 * time.Second},
  573. MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  574. },
  575. expectErr: false,
  576. },
  577. // SyncPeriod can be 0
  578. {
  579. config: kubeproxyconfig.KubeProxyIPVSConfiguration{
  580. SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
  581. MinSyncPeriod: metav1.Duration{Duration: 0 * time.Second},
  582. },
  583. expectErr: false,
  584. },
  585. }
  586. for _, test := range testCases {
  587. errs := validateKubeProxyIPVSConfiguration(test.config, newPath.Child("KubeProxyIPVSConfiguration"))
  588. if len(errs) == 0 && test.expectErr {
  589. t.Errorf("Expect error, got nil, reason: %s", test.reason)
  590. }
  591. if len(errs) > 0 && !test.expectErr {
  592. t.Errorf("Unexpected error: %v", errs)
  593. }
  594. }
  595. }
  596. func TestValidateKubeProxyConntrackConfiguration(t *testing.T) {
  597. successCases := []kubeproxyconfig.KubeProxyConntrackConfiguration{
  598. {
  599. MaxPerCore: pointer.Int32Ptr(1),
  600. Min: pointer.Int32Ptr(1),
  601. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  602. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  603. },
  604. {
  605. MaxPerCore: pointer.Int32Ptr(0),
  606. Min: pointer.Int32Ptr(0),
  607. TCPEstablishedTimeout: &metav1.Duration{Duration: 0 * time.Second},
  608. TCPCloseWaitTimeout: &metav1.Duration{Duration: 0 * time.Second},
  609. },
  610. }
  611. newPath := field.NewPath("KubeProxyConfiguration")
  612. for _, successCase := range successCases {
  613. if errs := validateKubeProxyConntrackConfiguration(successCase, newPath.Child("KubeProxyConntrackConfiguration")); len(errs) != 0 {
  614. t.Errorf("expected success: %v", errs)
  615. }
  616. }
  617. errorCases := []struct {
  618. config kubeproxyconfig.KubeProxyConntrackConfiguration
  619. msg string
  620. }{
  621. {
  622. config: kubeproxyconfig.KubeProxyConntrackConfiguration{
  623. MaxPerCore: pointer.Int32Ptr(-1),
  624. Min: pointer.Int32Ptr(1),
  625. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  626. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  627. },
  628. msg: "must be greater than or equal to 0",
  629. },
  630. {
  631. config: kubeproxyconfig.KubeProxyConntrackConfiguration{
  632. MaxPerCore: pointer.Int32Ptr(1),
  633. Min: pointer.Int32Ptr(-1),
  634. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  635. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  636. },
  637. msg: "must be greater than or equal to 0",
  638. },
  639. {
  640. config: kubeproxyconfig.KubeProxyConntrackConfiguration{
  641. MaxPerCore: pointer.Int32Ptr(1),
  642. Min: pointer.Int32Ptr(3),
  643. TCPEstablishedTimeout: &metav1.Duration{Duration: -5 * time.Second},
  644. TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
  645. },
  646. msg: "must be greater than or equal to 0",
  647. },
  648. {
  649. config: kubeproxyconfig.KubeProxyConntrackConfiguration{
  650. MaxPerCore: pointer.Int32Ptr(1),
  651. Min: pointer.Int32Ptr(3),
  652. TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
  653. TCPCloseWaitTimeout: &metav1.Duration{Duration: -5 * time.Second},
  654. },
  655. msg: "must be greater than or equal to 0",
  656. },
  657. }
  658. for _, errorCase := range errorCases {
  659. if errs := validateKubeProxyConntrackConfiguration(errorCase.config, newPath.Child("KubeProxyConntrackConfiguration")); len(errs) == 0 {
  660. t.Errorf("expected failure for %s", errorCase.msg)
  661. } else if !strings.Contains(errs[0].Error(), errorCase.msg) {
  662. t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg)
  663. }
  664. }
  665. }
  666. func TestValidateProxyMode(t *testing.T) {
  667. newPath := field.NewPath("KubeProxyConfiguration")
  668. successCases := []kubeproxyconfig.ProxyMode{
  669. kubeproxyconfig.ProxyModeUserspace,
  670. kubeproxyconfig.ProxyMode(""),
  671. }
  672. if runtime.GOOS == "windows" {
  673. successCases = append(successCases, kubeproxyconfig.ProxyModeKernelspace)
  674. } else {
  675. successCases = append(successCases, kubeproxyconfig.ProxyModeIPTables, kubeproxyconfig.ProxyModeIPVS)
  676. }
  677. for _, successCase := range successCases {
  678. if errs := validateProxyMode(successCase, newPath.Child("ProxyMode")); len(errs) != 0 {
  679. t.Errorf("expected success: %v", errs)
  680. }
  681. }
  682. errorCases := []struct {
  683. mode kubeproxyconfig.ProxyMode
  684. msg string
  685. }{
  686. {
  687. mode: kubeproxyconfig.ProxyMode("non-existing"),
  688. msg: "or blank (blank means the",
  689. },
  690. }
  691. for _, errorCase := range errorCases {
  692. if errs := validateProxyMode(errorCase.mode, newPath.Child("ProxyMode")); len(errs) == 0 {
  693. t.Errorf("expected failure %s for %v", errorCase.msg, errorCase.mode)
  694. } else if !strings.Contains(errs[0].Error(), errorCase.msg) {
  695. t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg)
  696. }
  697. }
  698. }
  699. func TestValidateClientConnectionConfiguration(t *testing.T) {
  700. newPath := field.NewPath("KubeProxyConfiguration")
  701. successCases := []componentbaseconfig.ClientConnectionConfiguration{
  702. {
  703. Burst: 0,
  704. },
  705. {
  706. Burst: 5,
  707. },
  708. }
  709. for _, successCase := range successCases {
  710. if errs := validateClientConnectionConfiguration(successCase, newPath.Child("Burst")); len(errs) != 0 {
  711. t.Errorf("expected success: %v", errs)
  712. }
  713. }
  714. errorCases := []struct {
  715. ccc componentbaseconfig.ClientConnectionConfiguration
  716. msg string
  717. }{
  718. {
  719. ccc: componentbaseconfig.ClientConnectionConfiguration{Burst: -5},
  720. msg: "must be greater than or equal to 0",
  721. },
  722. }
  723. for _, errorCase := range errorCases {
  724. if errs := validateClientConnectionConfiguration(errorCase.ccc, newPath.Child("Burst")); len(errs) == 0 {
  725. t.Errorf("expected failure for %s", errorCase.msg)
  726. } else if !strings.Contains(errs[0].Error(), errorCase.msg) {
  727. t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg)
  728. }
  729. }
  730. }
  731. func TestValidateHostPort(t *testing.T) {
  732. newPath := field.NewPath("KubeProxyConfiguration")
  733. successCases := []string{
  734. "0.0.0.0:10256",
  735. "127.0.0.1:10256",
  736. "10.10.10.10:10256",
  737. }
  738. for _, successCase := range successCases {
  739. if errs := validateHostPort(successCase, newPath.Child("HealthzBindAddress")); len(errs) != 0 {
  740. t.Errorf("expected success: %v", errs)
  741. }
  742. }
  743. errorCases := []struct {
  744. ccc string
  745. msg string
  746. }{
  747. {
  748. ccc: "10.10.10.10",
  749. msg: "must be IP:port",
  750. },
  751. {
  752. ccc: "123.456.789.10:12345",
  753. msg: "must be a valid IP",
  754. },
  755. {
  756. ccc: "10.10.10.10:foo",
  757. msg: "must be a valid port",
  758. },
  759. {
  760. ccc: "10.10.10.10:0",
  761. msg: "must be a valid port",
  762. },
  763. {
  764. ccc: "10.10.10.10:65536",
  765. msg: "must be a valid port",
  766. },
  767. }
  768. for _, errorCase := range errorCases {
  769. if errs := validateHostPort(errorCase.ccc, newPath.Child("HealthzBindAddress")); len(errs) == 0 {
  770. t.Errorf("expected failure for %s", errorCase.msg)
  771. } else if !strings.Contains(errs[0].Error(), errorCase.msg) {
  772. t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg)
  773. }
  774. }
  775. }
  776. func TestValidateIPVSSchedulerMethod(t *testing.T) {
  777. newPath := field.NewPath("KubeProxyConfiguration")
  778. successCases := []kubeproxyconfig.IPVSSchedulerMethod{
  779. kubeproxyconfig.RoundRobin,
  780. kubeproxyconfig.WeightedRoundRobin,
  781. kubeproxyconfig.LeastConnection,
  782. kubeproxyconfig.WeightedLeastConnection,
  783. kubeproxyconfig.LocalityBasedLeastConnection,
  784. kubeproxyconfig.LocalityBasedLeastConnectionWithReplication,
  785. kubeproxyconfig.SourceHashing,
  786. kubeproxyconfig.DestinationHashing,
  787. kubeproxyconfig.ShortestExpectedDelay,
  788. kubeproxyconfig.NeverQueue,
  789. "",
  790. }
  791. for _, successCase := range successCases {
  792. if errs := validateIPVSSchedulerMethod(successCase, newPath.Child("Scheduler")); len(errs) != 0 {
  793. t.Errorf("expected success: %v", errs)
  794. }
  795. }
  796. errorCases := []struct {
  797. mode kubeproxyconfig.IPVSSchedulerMethod
  798. msg string
  799. }{
  800. {
  801. mode: kubeproxyconfig.IPVSSchedulerMethod("non-existing"),
  802. msg: "blank means the default algorithm method (currently rr)",
  803. },
  804. }
  805. for _, errorCase := range errorCases {
  806. if errs := validateIPVSSchedulerMethod(errorCase.mode, newPath.Child("ProxyMode")); len(errs) == 0 {
  807. t.Errorf("expected failure for %s", errorCase.msg)
  808. } else if !strings.Contains(errs[0].Error(), errorCase.msg) {
  809. t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg)
  810. }
  811. }
  812. }
  813. func TestValidateKubeProxyNodePortAddress(t *testing.T) {
  814. newPath := field.NewPath("KubeProxyConfiguration")
  815. successCases := []struct {
  816. addresses []string
  817. }{
  818. {[]string{}},
  819. {[]string{"127.0.0.0/8"}},
  820. {[]string{"0.0.0.0/0"}},
  821. {[]string{"::/0"}},
  822. {[]string{"127.0.0.1/32", "1.2.3.0/24"}},
  823. {[]string{"127.0.0.0/8"}},
  824. {[]string{"127.0.0.1/32"}},
  825. {[]string{"::1/128"}},
  826. {[]string{"1.2.3.4/32"}},
  827. {[]string{"10.20.30.0/24"}},
  828. {[]string{"10.20.0.0/16", "100.200.0.0/16"}},
  829. {[]string{"10.0.0.0/8"}},
  830. {[]string{"2001:db8::/32"}},
  831. }
  832. for _, successCase := range successCases {
  833. if errs := validateKubeProxyNodePortAddress(successCase.addresses, newPath.Child("NodePortAddresses")); len(errs) != 0 {
  834. t.Errorf("expected success: %v", errs)
  835. }
  836. }
  837. errorCases := []struct {
  838. addresses []string
  839. msg string
  840. }{
  841. {
  842. addresses: []string{"foo"},
  843. msg: "must be a valid IP block",
  844. },
  845. {
  846. addresses: []string{"1.2.3"},
  847. msg: "must be a valid IP block",
  848. },
  849. {
  850. addresses: []string{""},
  851. msg: "must be a valid IP block",
  852. },
  853. {
  854. addresses: []string{"10.20.30.40"},
  855. msg: "must be a valid IP block",
  856. },
  857. {
  858. addresses: []string{"::1"},
  859. msg: "must be a valid IP block",
  860. },
  861. {
  862. addresses: []string{"2001:db8:1"},
  863. msg: "must be a valid IP block",
  864. },
  865. {
  866. addresses: []string{"2001:db8:xyz/64"},
  867. msg: "must be a valid IP block",
  868. },
  869. }
  870. for _, errorCase := range errorCases {
  871. if errs := validateKubeProxyNodePortAddress(errorCase.addresses, newPath.Child("NodePortAddresses")); len(errs) == 0 {
  872. t.Errorf("expected failure for %s", errorCase.msg)
  873. } else if !strings.Contains(errs[0].Error(), errorCase.msg) {
  874. t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg)
  875. }
  876. }
  877. }
  878. func TestValidateKubeProxyExcludeCIDRs(t *testing.T) {
  879. // TODO(rramkumar): This test is a copy of TestValidateKubeProxyNodePortAddress.
  880. // Maybe some code can be shared?
  881. newPath := field.NewPath("KubeProxyConfiguration")
  882. successCases := []struct {
  883. addresses []string
  884. }{
  885. {[]string{}},
  886. {[]string{"127.0.0.0/8"}},
  887. {[]string{"0.0.0.0/0"}},
  888. {[]string{"::/0"}},
  889. {[]string{"127.0.0.1/32", "1.2.3.0/24"}},
  890. {[]string{"127.0.0.0/8"}},
  891. {[]string{"127.0.0.1/32"}},
  892. {[]string{"::1/128"}},
  893. {[]string{"1.2.3.4/32"}},
  894. {[]string{"10.20.30.0/24"}},
  895. {[]string{"10.20.0.0/16", "100.200.0.0/16"}},
  896. {[]string{"10.0.0.0/8"}},
  897. {[]string{"2001:db8::/32"}},
  898. }
  899. for _, successCase := range successCases {
  900. if errs := validateIPVSExcludeCIDRs(successCase.addresses, newPath.Child("ExcludeCIDRs")); len(errs) != 0 {
  901. t.Errorf("expected success: %v", errs)
  902. }
  903. }
  904. errorCases := []struct {
  905. addresses []string
  906. msg string
  907. }{
  908. {
  909. addresses: []string{"foo"},
  910. msg: "must be a valid IP block",
  911. },
  912. {
  913. addresses: []string{"1.2.3"},
  914. msg: "must be a valid IP block",
  915. },
  916. {
  917. addresses: []string{""},
  918. msg: "must be a valid IP block",
  919. },
  920. {
  921. addresses: []string{"10.20.30.40"},
  922. msg: "must be a valid IP block",
  923. },
  924. {
  925. addresses: []string{"::1"},
  926. msg: "must be a valid IP block",
  927. },
  928. {
  929. addresses: []string{"2001:db8:1"},
  930. msg: "must be a valid IP block",
  931. },
  932. {
  933. addresses: []string{"2001:db8:xyz/64"},
  934. msg: "must be a valid IP block",
  935. },
  936. }
  937. for _, errorCase := range errorCases {
  938. if errs := validateIPVSExcludeCIDRs(errorCase.addresses, newPath.Child("ExcludeCIDRs")); len(errs) == 0 {
  939. t.Errorf("expected failure for %s", errorCase.msg)
  940. } else if !strings.Contains(errs[0].Error(), errorCase.msg) {
  941. t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg)
  942. }
  943. }
  944. }