checks_test.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. /*
  2. Copyright 2016 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 preflight
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io/ioutil"
  18. "strings"
  19. "testing"
  20. "github.com/lithammer/dedent"
  21. "github.com/pkg/errors"
  22. "net/http"
  23. "os"
  24. "k8s.io/apimachinery/pkg/util/sets"
  25. kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
  26. "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  27. utilruntime "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime"
  28. "k8s.io/utils/exec"
  29. fakeexec "k8s.io/utils/exec/testing"
  30. )
  31. var (
  32. externalEtcdRootCAFileContent = dedent.Dedent(`
  33. -----BEGIN CERTIFICATE-----
  34. MIIFrjCCA5agAwIBAgIUJAM5bQz/Ann8qye8T7Uyl+cAt3wwDQYJKoZIhvcNAQEN
  35. BQAwbzEOMAwGA1UEBhMFQ2hpbmExDzANBgNVBAgTBkhhaW5hbjEOMAwGA1UEBxMF
  36. U2FueWExDTALBgNVBAoTBGV0Y2QxFjAUBgNVBAsTDWV0Y2Qgc2VjdXJpdHkxFTAT
  37. BgNVBAMTDGV0Y2Qtcm9vdC1jYTAeFw0xNzAyMjIwNzEyMDBaFw0yMjAyMjEwNzEy
  38. MDBaMG8xDjAMBgNVBAYTBUNoaW5hMQ8wDQYDVQQIEwZIYWluYW4xDjAMBgNVBAcT
  39. BVNhbnlhMQ0wCwYDVQQKEwRldGNkMRYwFAYDVQQLEw1ldGNkIHNlY3VyaXR5MRUw
  40. EwYDVQQDEwxldGNkLXJvb3QtY2EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
  41. AoICAQDD16VNTwvEvy1yd/vt8Eq2NwTw51mKHGYlZwsDqdqMEnEiWoJ7Iv9HZ+cl
  42. jX0FnahKnaV76j3xPO73L5WOvRYxnZ8MvU/aBdDO+Tct4ht3m7TJaav6s55otjDy
  43. dQNmlpBt4fFEB/nDozQaocfu2mqr5nyKJOjJpe+57Uw4h0LshreDOlzHEs8CkP6W
  44. /B9yGFARVyz84YgVtemUX8WTB3cVU49KEYMCuhqXY8s97xSTGT/4Tq/MruKb2V+w
  45. uUPjvyO5eIUcWetjBhgEGsS37NrsSFhoUNMp/PtIkth0LQoWb9sjnG069KIQqm61
  46. 1PKxH7jgLYLf4q455iAuTFr0lF1OcmICTeJB+GiS+3ubOb1TH3AYICXvQUniNWJx
  47. sDz3qUUu4GLHk9wHtdNmX2FXYB8kHMZAidDM4Zw3IhZZap6n6BlGVVBV5h8sNM3t
  48. SB+pDLuAaZLx3/ah2ds6AwkfaMdYDsE/MWcWQqzBfhOp758Mx3dF16IY+6IQp0RS
  49. 8qGKxgLDnTF9LgyHVOait2N/pT54faf8//ShSqTqzTK1wzHCkYwL6/B259zXWxeX
  50. z4gOpQOk4rO4pgm/65QW9aKzHoQnpQ7lFQL2cdsKJv2tyC7pDfVrFy2uHWaUibbP
  51. 7pDw3OD8MQwR1TuhflK1AIicpMQe/kTAuRwH4fneeaGVdddBQQIDAQABo0IwQDAO
  52. BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUtoqcReNJ
  53. p8z8Hz1/Q7XMK2fgi74wDQYJKoZIhvcNAQENBQADggIBADbh4HB//Gb0TUUEPoSw
  54. VMJSUK1pb6KVTqAITSCKPwGT8KfCvVpUxEjh9J3dm1L8wbdr48yffdjhdl96cx2F
  55. aGWdUIxRBIcpt5xvauBoj0OwfNcD5B9q1aKuh5XPNu4BndNeGw51vdJ8bJbtrZa8
  56. wKWF/PHciCo/wlzE/YgsemHeY5bYeXawXVP/+ocoLH82Fb8Aq0Af3ZABiA6fmawz
  57. FiZlnIrZnHVJYSap4yDhC/AQECXKY5gj7kjSnDebsIYds5OrW0D3LeRzs+q5nQXE
  58. xR35qg834kxUULS8AywqmR3+zjfeymm2FtsjT/PuzEImA80y29qpLZIpPg0meKHF
  59. pCMJkEHaRh4/JAinLaKCGLpnchqBy7CR6yvVnGkx93J0louIbVyUfn63R6mxCvd7
  60. kL16a2xBMKgV4RDFcu+VYjbJTFdWOTGFrxPBmd/rLdwD3XNiwPtI0vXGM7I35DDP
  61. SWwKVvR97F3uEnIQ1u8vHa1pNfQ1qSf/+hUJx2D9ypr7LTQ0LpLh1vUeTeUAVHmT
  62. EEpcqzDg6lsqXw6KHJ55kd3QR/hRXd/Vr6EWUawDEnGjxyFVV2dTBbunsbSobNI4
  63. eKV+60oCk3NMwrZoLw4Fv5qs2saS62dgJNfxbKqBX9ljSQxGzHjRwh+hVByCnG8m
  64. Z9JkQayesM6D7uwbQJXd5rgy
  65. -----END CERTIFICATE-----
  66. `)
  67. externalEtcdCertFileContent = dedent.Dedent(`
  68. -----BEGIN CERTIFICATE-----
  69. MIIGEjCCA/qgAwIBAgIURHJFslbPveA1WwQ4FaPJg1x6B8YwDQYJKoZIhvcNAQEN
  70. BQAwbzEOMAwGA1UEBhMFQ2hpbmExDzANBgNVBAgTBkhhaW5hbjEOMAwGA1UEBxMF
  71. U2FueWExDTALBgNVBAoTBGV0Y2QxFjAUBgNVBAsTDWV0Y2Qgc2VjdXJpdHkxFTAT
  72. BgNVBAMTDGV0Y2Qtcm9vdC1jYTAeFw0xNzAyMjIwNzE0MDBaFw0yNzAyMjAwNzE0
  73. MDBaMGwxDjAMBgNVBAYTBUNoaW5hMQ8wDQYDVQQIEwZIYWluYW4xDjAMBgNVBAcT
  74. BVNhbnlhMQ0wCwYDVQQKEwRldGNkMRYwFAYDVQQLEw1ldGNkIHNlY3VyaXR5MRIw
  75. EAYDVQQDEwlteS1ldGNkLTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
  76. AQCmCR4OSRrUCES90sUbj5tvjF24lPCMj7qP9MBUxcVvWfaJM12o4AxqBr8OThgd
  77. lpNvlbKmRpfvbraXiDnuGty1vPa3z7RmKbwFgENfgKHz4fUw/MQ7CALOQ5PAvgf1
  78. rQ6Ii4cr49nWctpQmBXHtZRjvquBYnw70KrWfQ121DwPYy7cb/StuHLsTgqsgzhl
  79. ECILWCj9GNqcGQr5+ZvwUxa2yam2CS1M+PLbB6HxX/4RBBTWKAt8+kjt6TxxMaSE
  80. bNDHNDLWzQSpxg5qTLOQtrubFD4O3JT2E8DEj+LvXJKH7pJd1Z+r0m3ymQvBAIXr
  81. 6OJs+sHbaaxKWS35k9m88NRojR+r5KPoEcBgxhtBtXUfMS5v5dTtcNsHl/mHmTC+
  82. gWiqpzA+tF55uUEWhRoA+pN7Ie2PviRhG43t99l7bsHVnrxZQqWsWlvCxMN1c2+7
  83. PRwhsYZFITyKcMSvd19Nb5HGc5hT7btZlWc2xKS2YNnDXbD8C5SdxZek5Cb/xRxL
  84. T8taf2c1bHs8sZrzIK2DCGvaN3471WEnmaCuRWr2fqyJeCPwsvvWeNDVmgPP6v7g
  85. ncyy+4QyyfNrdURTZFyw81ZbCiznPc070u7vtIYt3Sa0NXd0oEG1ybAZwBIYhMOY
  86. 5ctepJLf7QxHXR70RdI0ksHEmZGZ1igk7gzhmHEgQM87pQIDAQABo4GoMIGlMA4G
  87. A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYD
  88. VR0TAQH/BAIwADAdBgNVHQ4EFgQU0U/Zn4mc95UXm+LVO67wqJpL9gIwHwYDVR0j
  89. BBgwFoAUtoqcReNJp8z8Hz1/Q7XMK2fgi74wJgYDVR0RBB8wHYIJbG9jYWxob3N0
  90. hwR/AAABhwQKcjPGhwQKcgwwMA0GCSqGSIb3DQEBDQUAA4ICAQCikW5SNpndBxEz
  91. qblER72KkfSEXMFhQry3RZJeAw6rQiOl+PMJqMnylcepOAUrNi20emS270dQDh3z
  92. Hw/JBgKftZ1JrjbF9NF4oFUZcFUKmTgyWYnhLH0BskgwJf2u+DpugFa4U8niQf15
  93. ciZGoUfWCGOJbgVP7esdnyhH/P/DpOEObWf8vOfvfQ49r7MzATyzMESyJjdtAH/F
  94. c5JKACxpJhaYfTZ78F43jSw0vswBdLQ7fJWqg/sJBlTG0GBFJcEJzFVpwzYUxwZ4
  95. rUpAn4A02M2V9XDNlptrWvcQz/5Vs/aCmehz7GOiMJB6SLWcMSpJRLMqoJjaFVfO
  96. OPm7bWMMaVOUPedzvcBKRXmEAg7HQnm3ibkVNjTW8Hr66n34Yk/dO9WXD+6IXnOQ
  97. bMY+Mf9vpIsscSpGTO15sAKqiXCzHR9RWqNd4U3jvo3JtewkNMhIKzPThgYNfsO3
  98. 7HSrlfffeEQKc59rDUaC3Y9YSc5ERJRMC+mdOqXNMy2iedZnNEsmgYlaVDg6xfG8
  99. 65w9UkMOe+DTJtMHnMxP4rT6WE4cKysQeSYxkyo/jh+8rKEy9+AyuEntJAknABUc
  100. N5mizdYu8nrtiSu9jdLKhwO41gC2IlXPUHizylo6g24RFVBjHLlzYAAsVMMMSQW1
  101. XRMVQjawUTknbAgHuE7/rEX8c27WUA==
  102. -----END CERTIFICATE-----
  103. `)
  104. externalEtcdKeyFileContent = dedent.Dedent(`
  105. -----BEGIN RSA PRIVATE KEY-----
  106. MIIJKAIBAAKCAgEApgkeDkka1AhEvdLFG4+bb4xduJTwjI+6j/TAVMXFb1n2iTNd
  107. qOAMaga/Dk4YHZaTb5WypkaX7262l4g57hrctbz2t8+0Zim8BYBDX4Ch8+H1MPzE
  108. OwgCzkOTwL4H9a0OiIuHK+PZ1nLaUJgVx7WUY76rgWJ8O9Cq1n0NdtQ8D2Mu3G/0
  109. rbhy7E4KrIM4ZRAiC1go/RjanBkK+fmb8FMWtsmptgktTPjy2weh8V/+EQQU1igL
  110. fPpI7ek8cTGkhGzQxzQy1s0EqcYOakyzkLa7mxQ+DtyU9hPAxI/i71ySh+6SXdWf
  111. q9Jt8pkLwQCF6+jibPrB22msSlkt+ZPZvPDUaI0fq+Sj6BHAYMYbQbV1HzEub+XU
  112. 7XDbB5f5h5kwvoFoqqcwPrReeblBFoUaAPqTeyHtj74kYRuN7ffZe27B1Z68WUKl
  113. rFpbwsTDdXNvuz0cIbGGRSE8inDEr3dfTW+RxnOYU+27WZVnNsSktmDZw12w/AuU
  114. ncWXpOQm/8UcS0/LWn9nNWx7PLGa8yCtgwhr2jd+O9VhJ5mgrkVq9n6siXgj8LL7
  115. 1njQ1ZoDz+r+4J3MsvuEMsnza3VEU2RcsPNWWwos5z3NO9Lu77SGLd0mtDV3dKBB
  116. tcmwGcASGITDmOXLXqSS3+0MR10e9EXSNJLBxJmRmdYoJO4M4ZhxIEDPO6UCAwEA
  117. AQKCAgEAmr3OlDPP3CLkpiFEcJ5TmA+y3S96TRY7IqVRhvBXRKMMoOwNczF0gHBP
  118. Ka7gzNqkCA/1UwBh49VEOU/N5bqFTp+RNNhQYhKtWFck82H4Dkrd8EzzOa0KqF/U
  119. 2YKB+pbR/7JCRUZypGmgTBKh4eG6LYfrYYd/D2Q3g/VCUigU3aZrayiwWiOYf+Fw
  120. Ez2slowFnpsIgHHkdCzmzPi0O7PEbJDgKXa+EInIFRg09renGwa5wKnLoyvEQm7o
  121. VPqWQJEFt1JPu1+R5ARhNPLNO6cCi9K+z60G65yXQNp0/u5A5o0TPn609DcHH11B
  122. 1ht9tNL0C+tcNvhyiUw6C+uet3egDVu1TqptzAfb2Y3MQK6UV/by7KJxcFxBAzWl
  123. UQ4zDaQzCcU81T92zI+XeRSJuCPuOL61mH7zEiPZZPOLV8MbxBX/7lj+IJTBL+vJ
  124. Idq7Nn/+LRtuSy5PH2MzZ5DzIMmjkjQ/ScpzAr9Zpkm3dpTcGTpFV0uqHseE77Re
  125. 55tz9uB7pxV1n6Gz4uMNnsioEYsFIRfzst4QWDdaQqcYJQuKvW9pXNmgRgSCIlft
  126. 54DxQ98a1PVFmS40TT9mjUg0P66m+8bk5vEb58iAjoYJRcoriZhlT6cOcuPW6hos
  127. 3PfA2gMXuWu61mAjzdP0zbzNBXCn5nRppqLNmWMVZCI0nLjmyZUCggEBAMEpCQu9
  128. cRWc/GjvmnfXHewvqQHu3A3J1HCLR0VqJo8rcIIvhSe7dPRAMtUFxV1R2eOfMvSZ
  129. Y4y69tMHZPVTgnp2t5TSavjpMqSQLvXyBkgL8FnGEl5l6HEQTm8y0C13Cm+CUB5a
  130. uxQnQflkX539SjWX0XdOmYuLORmrKGxgcDOd9652fDJcFSXYa0mx6KN2JZHh9psA
  131. 9ldHhUIq1ngoVnrctlK53MptckPrFwMFdXRCKiMfkvpUkXTeXu4D7Z1VNh2V/3gF
  132. lmRNioXaxp7W8omBSQlwaHY5btPj5jktiC9/so4ORqJjHvbCURrIvdkPPaXi/YJy
  133. HdoOgHYFnn3p6M8CggEBANwNDtdbHWwwVC7Op6TNc8qK+SWAId5RqPOmM70XBVvg
  134. u9nxT7a5vmRTs81fcVoxtE0t+KWIfOXquxqTbk0ONqIsl2CLTiTFaNHoHlvwgFBT
  135. aYukORiGILIzOJr82RPugAw1+j8jmw3OsCOXnf2odGs+oC/V9vEd9NyZpDHPohtK
  136. a8Bk8p326mQam23ArUesIqnw31fG22KRpoLXuk/9nNcAAAZd1Qd9hGWf0HHxunXB
  137. wj6e3VTm0G4NPTli5vmVavYRPMFUUJpU5lwTHhlrHTSmANHTjZGnn0mEOfIrfodF
  138. ODwJjwoyq4rPls0fqOvyAyBCnhop4fC8yOd4cQcLSUsCggEAbv9Br3lhLmZTtYla
  139. XltDWqHYoL+9vD6q0TF39y+UkNkJggYEolxaTLFHhJoYXBPY/bBR+7TZO9mEVKf/
  140. H+qpI+5seByiU/7NlzszgSle6q/RogTsMUqmU7JnIAc3EalCWemsWIUS0/XrN4Cy
  141. YXtX1Yw0VjbYjROn8FQmmoCgeUjhN2Pm4pl/nYvLu0F8ydHurPIIX/IhnO4AaZFs
  142. RQgJCfki3E7pzXkvHFBPnPDaGcCbritKrodCPsI6EtQ3Cx4YRtAXScUMMv9MBrc9
  143. Q7GJFfMxITdzD9zZDvH7Lgg4JfNfi7owZMhI1su7B4UrczwK1PSncPpapR+IOkno
  144. VbrAiQKCAQB2xGV6PqdGuV72VHuPK4SPkSqf3uRoxdJWjyHlsQMnb8hz/RZ1HRNx
  145. uuuUsSrQ73rNHT7SuTQQM/0AfwpNdJpwNXkOlqF6n0HP6WRZYxkeQab5w409e0cy
  146. ZwrqPAY+B7/81zVV1rXdYe0XiMGxIraTG54Bs44w3WZHmnVQnSx1Zll54gJA1//y
  147. P5ocRp4/zNx4tJUXHzFRpiMlA6J/gfag5FMfHI3aGRjYcMVken+VBxr8CWqUZG+i
  148. tmqRCpx3oPm2Dd+oyQUoByK+F2NrfLCqtd5DYddLAhmq6D8OQgNspyOO4+ncKzUD
  149. Gr/dvnTBxEGDq/EBVhGoiXw10n/OuXy5AoIBAAUAoTyt4gQjjC0ddtMLN7+R1Ymp
  150. eNULpq2XTvidj7jaysIW9Q52ncvN6h2Vds/Z3Ujvdne2jMq7Q/C96fKcrhgMH9ca
  151. ADGLWtD+VkP4NgFjj7R2jabF8d9IQdJDXAgvR/kokojF0RsJuvD2hawN6lQkkj6S
  152. fNNGMBk4sGyt7gzAn3iO4Zoy+QjtALNnZcaH6s7oIg3UKf6OwskiBB60Q5P1U3/E
  153. RPtTxhex3jFuySNJ413JgyGkvcP+qjuzi6eyVDxkfiyNohQYGuZ8rieFX7QfQFAY
  154. TIXptchVUTxmGKWzcpLC3AfkwFvV2IPoMk8YnDSp270D30cqWiI9puSEcxQ=
  155. -----END RSA PRIVATE KEY-----
  156. `)
  157. )
  158. type preflightCheckTest struct {
  159. msg string
  160. }
  161. func (pfct preflightCheckTest) Name() string {
  162. return "preflightCheckTest"
  163. }
  164. func (pfct preflightCheckTest) Check() (warning, errorList []error) {
  165. if pfct.msg == "warning" {
  166. return []error{errors.New("warning")}, nil
  167. }
  168. if pfct.msg != "" {
  169. return nil, []error{errors.New("fake error")}
  170. }
  171. return
  172. }
  173. func TestRunInitNodeChecks(t *testing.T) {
  174. var tests = []struct {
  175. name string
  176. cfg *kubeadmapi.InitConfiguration
  177. expected bool
  178. isSecondaryControlPlane bool
  179. downloadCerts bool
  180. }{
  181. {name: "Test valid advertised address",
  182. cfg: &kubeadmapi.InitConfiguration{
  183. LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "foo"},
  184. },
  185. expected: false,
  186. },
  187. {
  188. name: "Test CA file exists if specified",
  189. cfg: &kubeadmapi.InitConfiguration{
  190. ClusterConfiguration: kubeadmapi.ClusterConfiguration{
  191. Etcd: kubeadmapi.Etcd{External: &kubeadmapi.ExternalEtcd{CAFile: "/foo"}},
  192. },
  193. },
  194. expected: false,
  195. },
  196. {
  197. name: "Skip test CA file exists if specified/download certs",
  198. cfg: &kubeadmapi.InitConfiguration{
  199. ClusterConfiguration: kubeadmapi.ClusterConfiguration{
  200. Etcd: kubeadmapi.Etcd{External: &kubeadmapi.ExternalEtcd{CAFile: "/foo"}},
  201. },
  202. },
  203. expected: true,
  204. isSecondaryControlPlane: true,
  205. downloadCerts: true,
  206. },
  207. {
  208. name: "Test Cert file exists if specified",
  209. cfg: &kubeadmapi.InitConfiguration{
  210. ClusterConfiguration: kubeadmapi.ClusterConfiguration{
  211. Etcd: kubeadmapi.Etcd{External: &kubeadmapi.ExternalEtcd{CertFile: "/foo"}},
  212. },
  213. },
  214. expected: false,
  215. },
  216. {
  217. name: "Test Key file exists if specified",
  218. cfg: &kubeadmapi.InitConfiguration{
  219. ClusterConfiguration: kubeadmapi.ClusterConfiguration{
  220. Etcd: kubeadmapi.Etcd{External: &kubeadmapi.ExternalEtcd{CertFile: "/foo"}},
  221. },
  222. },
  223. expected: false,
  224. },
  225. {
  226. cfg: &kubeadmapi.InitConfiguration{
  227. LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "2001:1234::1:15"},
  228. },
  229. expected: false,
  230. },
  231. }
  232. for _, rt := range tests {
  233. // TODO: Make RunInitNodeChecks accept a ClusterConfiguration object instead of InitConfiguration
  234. actual := RunInitNodeChecks(exec.New(), rt.cfg, sets.NewString(), rt.isSecondaryControlPlane, rt.downloadCerts)
  235. if (actual == nil) != rt.expected {
  236. t.Errorf(
  237. "failed RunInitNodeChecks:\n\texpected: %t\n\t actual: %t\n\t error: %v",
  238. rt.expected,
  239. (actual == nil),
  240. actual,
  241. )
  242. }
  243. }
  244. }
  245. func TestRunJoinNodeChecks(t *testing.T) {
  246. var tests = []struct {
  247. cfg *kubeadmapi.JoinConfiguration
  248. expected bool
  249. }{
  250. {
  251. cfg: &kubeadmapi.JoinConfiguration{},
  252. expected: false,
  253. },
  254. {
  255. cfg: &kubeadmapi.JoinConfiguration{
  256. Discovery: kubeadmapi.Discovery{
  257. BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{
  258. APIServerEndpoint: "192.168.1.15",
  259. },
  260. },
  261. },
  262. expected: false,
  263. },
  264. {
  265. cfg: &kubeadmapi.JoinConfiguration{
  266. Discovery: kubeadmapi.Discovery{
  267. BootstrapToken: &kubeadmapi.BootstrapTokenDiscovery{
  268. APIServerEndpoint: "2001:1234::1:15",
  269. },
  270. },
  271. },
  272. expected: false,
  273. },
  274. }
  275. for _, rt := range tests {
  276. actual := RunJoinNodeChecks(exec.New(), rt.cfg, sets.NewString())
  277. if (actual == nil) != rt.expected {
  278. t.Errorf(
  279. "failed RunJoinNodeChecks:\n\texpected: %t\n\t actual: %t",
  280. rt.expected,
  281. (actual != nil),
  282. )
  283. }
  284. }
  285. }
  286. func TestRunChecks(t *testing.T) {
  287. var tokenTest = []struct {
  288. p []Checker
  289. expected bool
  290. output string
  291. }{
  292. {[]Checker{}, true, ""},
  293. {[]Checker{preflightCheckTest{"warning"}}, true, "\t[WARNING preflightCheckTest]: warning\n"}, // should just print warning
  294. {[]Checker{preflightCheckTest{"error"}}, false, ""},
  295. {[]Checker{preflightCheckTest{"test"}}, false, ""},
  296. {[]Checker{DirAvailableCheck{Path: "/does/not/exist"}}, true, ""},
  297. {[]Checker{DirAvailableCheck{Path: "/"}}, false, ""},
  298. {[]Checker{FileAvailableCheck{Path: "/does/not/exist"}}, true, ""},
  299. {[]Checker{FileContentCheck{Path: "/does/not/exist"}}, false, ""},
  300. {[]Checker{FileContentCheck{Path: "/"}}, true, ""},
  301. {[]Checker{FileContentCheck{Path: "/", Content: []byte("does not exist")}}, false, ""},
  302. {[]Checker{InPathCheck{executable: "foobarbaz", exec: exec.New()}}, true, "\t[WARNING FileExisting-foobarbaz]: foobarbaz not found in system path\n"},
  303. {[]Checker{InPathCheck{executable: "foobarbaz", mandatory: true, exec: exec.New()}}, false, ""},
  304. {[]Checker{InPathCheck{executable: "foobar", mandatory: false, exec: exec.New(), suggestion: "install foobar"}}, true, "\t[WARNING FileExisting-foobar]: foobar not found in system path\nSuggestion: install foobar\n"},
  305. }
  306. for _, rt := range tokenTest {
  307. buf := new(bytes.Buffer)
  308. actual := RunChecks(rt.p, buf, sets.NewString())
  309. if (actual == nil) != rt.expected {
  310. t.Errorf(
  311. "failed RunChecks:\n\texpected: %t\n\t actual: %t",
  312. rt.expected,
  313. (actual == nil),
  314. )
  315. }
  316. if buf.String() != rt.output {
  317. t.Errorf(
  318. "failed RunChecks:\n\texpected: %s\n\t actual: %s",
  319. rt.output,
  320. buf.String(),
  321. )
  322. }
  323. }
  324. }
  325. func TestConfigRootCAs(t *testing.T) {
  326. f, err := ioutil.TempFile(os.TempDir(), "kubeadm-external-etcd-test-cafile")
  327. if err != nil {
  328. t.Errorf("failed configRootCAs:\n\texpected: succeed creating temp CA file\n\tactual:%v", err)
  329. }
  330. defer os.Remove(f.Name())
  331. if err := ioutil.WriteFile(f.Name(), []byte(externalEtcdRootCAFileContent), 0644); err != nil {
  332. t.Errorf("failed configRootCAs:\n\texpected: succeed writing contents to temp CA file %s\n\tactual:%v", f.Name(), err)
  333. }
  334. c := ExternalEtcdVersionCheck{Etcd: kubeadmapi.Etcd{External: &kubeadmapi.ExternalEtcd{CAFile: f.Name()}}}
  335. config, err := c.configRootCAs(nil)
  336. if err != nil {
  337. t.Errorf(
  338. "failed configRootCAs:\n\texpected: has no error\n\tactual:%v",
  339. err,
  340. )
  341. }
  342. if config.RootCAs == nil {
  343. t.Errorf(
  344. "failed configRootCAs:\n\texpected: RootCAs not equal to nil\n\tactual:%v",
  345. config.RootCAs,
  346. )
  347. }
  348. }
  349. func TestConfigCertAndKey(t *testing.T) {
  350. certFile, err := ioutil.TempFile(os.TempDir(), "kubeadm-external-etcd-test-certfile")
  351. if err != nil {
  352. t.Errorf(
  353. "failed configCertAndKey:\n\texpected: succeed creating temp CertFile file\n\tactual:%v",
  354. err,
  355. )
  356. }
  357. defer os.Remove(certFile.Name())
  358. if err := ioutil.WriteFile(certFile.Name(), []byte(externalEtcdCertFileContent), 0644); err != nil {
  359. t.Errorf(
  360. "failed configCertAndKey:\n\texpected: succeed writing contents to temp CertFile file %s\n\tactual:%v",
  361. certFile.Name(),
  362. err,
  363. )
  364. }
  365. keyFile, err := ioutil.TempFile(os.TempDir(), "kubeadm-external-etcd-test-keyfile")
  366. if err != nil {
  367. t.Errorf(
  368. "failed configCertAndKey:\n\texpected: succeed creating temp KeyFile file\n\tactual:%v",
  369. err,
  370. )
  371. }
  372. defer os.Remove(keyFile.Name())
  373. if err := ioutil.WriteFile(keyFile.Name(), []byte(externalEtcdKeyFileContent), 0644); err != nil {
  374. t.Errorf(
  375. "failed configCertAndKey:\n\texpected: succeed writing contents to temp KeyFile file %s\n\tactual:%v",
  376. keyFile.Name(),
  377. err,
  378. )
  379. }
  380. c := ExternalEtcdVersionCheck{
  381. Etcd: kubeadmapi.Etcd{
  382. External: &kubeadmapi.ExternalEtcd{
  383. CertFile: certFile.Name(),
  384. KeyFile: keyFile.Name(),
  385. },
  386. },
  387. }
  388. config, err := c.configCertAndKey(nil)
  389. if err != nil {
  390. t.Errorf(
  391. "failed configCertAndKey:\n\texpected: has no error\n\tactual:%v",
  392. err,
  393. )
  394. }
  395. if config.Certificates == nil {
  396. t.Errorf(
  397. "failed configCertAndKey:\n\texpected: Certificates not equal to nil\n\tactual:%v",
  398. config.Certificates,
  399. )
  400. }
  401. }
  402. func TestKubernetesVersionCheck(t *testing.T) {
  403. var tests = []struct {
  404. check KubernetesVersionCheck
  405. expectWarnings bool
  406. }{
  407. {
  408. check: KubernetesVersionCheck{
  409. KubeadmVersion: "v1.6.6", //Same version
  410. KubernetesVersion: "v1.6.6",
  411. },
  412. expectWarnings: false,
  413. },
  414. {
  415. check: KubernetesVersionCheck{
  416. KubeadmVersion: "v1.6.6", //KubernetesVersion version older than KubeadmVersion
  417. KubernetesVersion: "v1.5.5",
  418. },
  419. expectWarnings: false,
  420. },
  421. {
  422. check: KubernetesVersionCheck{
  423. KubeadmVersion: "v1.6.6", //KubernetesVersion newer than KubeadmVersion, within the same minor release (new patch)
  424. KubernetesVersion: "v1.6.7",
  425. },
  426. expectWarnings: false,
  427. },
  428. {
  429. check: KubernetesVersionCheck{
  430. KubeadmVersion: "v1.6.6", //KubernetesVersion newer than KubeadmVersion, in a different minor/in pre-release
  431. KubernetesVersion: "v1.7.0-alpha.0",
  432. },
  433. expectWarnings: true,
  434. },
  435. {
  436. check: KubernetesVersionCheck{
  437. KubeadmVersion: "v1.6.6", //KubernetesVersion newer than KubeadmVersion, in a different minor/stable
  438. KubernetesVersion: "v1.7.0",
  439. },
  440. expectWarnings: true,
  441. },
  442. {
  443. check: KubernetesVersionCheck{
  444. KubeadmVersion: "v0.0.0", //"super-custom" builds - Skip this check
  445. KubernetesVersion: "v1.7.0",
  446. },
  447. expectWarnings: false,
  448. },
  449. }
  450. for _, rt := range tests {
  451. warning, _ := rt.check.Check()
  452. if (warning != nil) != rt.expectWarnings {
  453. t.Errorf(
  454. "failed KubernetesVersionCheck:\n\texpected: %t\n\t actual: %t (KubeadmVersion:%s, KubernetesVersion: %s)",
  455. rt.expectWarnings,
  456. (warning != nil),
  457. rt.check.KubeadmVersion,
  458. rt.check.KubernetesVersion,
  459. )
  460. }
  461. }
  462. }
  463. func TestHTTPProxyCIDRCheck(t *testing.T) {
  464. var tests = []struct {
  465. check HTTPProxyCIDRCheck
  466. expectWarnings bool
  467. }{
  468. {
  469. check: HTTPProxyCIDRCheck{
  470. Proto: "https",
  471. CIDR: "127.0.0.0/8",
  472. }, // Loopback addresses never should produce proxy warnings
  473. expectWarnings: false,
  474. },
  475. {
  476. check: HTTPProxyCIDRCheck{
  477. Proto: "https",
  478. CIDR: "10.96.0.0/12",
  479. }, // Expected to be accessed directly, we set NO_PROXY to 10.0.0.0/8
  480. expectWarnings: false,
  481. },
  482. {
  483. check: HTTPProxyCIDRCheck{
  484. Proto: "https",
  485. CIDR: "192.168.0.0/16",
  486. }, // Expected to go via proxy as this range is not listed in NO_PROXY
  487. expectWarnings: true,
  488. },
  489. {
  490. check: HTTPProxyCIDRCheck{
  491. Proto: "https",
  492. CIDR: "2001:db8::/56",
  493. }, // Expected to be accessed directly, part of 2001:db8::/48 in NO_PROXY
  494. expectWarnings: false,
  495. },
  496. {
  497. check: HTTPProxyCIDRCheck{
  498. Proto: "https",
  499. CIDR: "2001:db8:1::/56",
  500. }, // Expected to go via proxy, range is not in 2001:db8::/48
  501. expectWarnings: true,
  502. },
  503. }
  504. // Save current content of *_proxy and *_PROXY variables.
  505. savedEnv := resetProxyEnv(t)
  506. defer restoreEnv(savedEnv)
  507. for _, rt := range tests {
  508. warning, _ := rt.check.Check()
  509. if (warning != nil) != rt.expectWarnings {
  510. t.Errorf(
  511. "failed HTTPProxyCIDRCheck:\n\texpected: %t\n\t actual: %t (CIDR:%s). Warnings: %v",
  512. rt.expectWarnings,
  513. (warning != nil),
  514. rt.check.CIDR,
  515. warning,
  516. )
  517. }
  518. }
  519. }
  520. func TestHTTPProxyCheck(t *testing.T) {
  521. var tests = []struct {
  522. name string
  523. check HTTPProxyCheck
  524. expectWarnings bool
  525. }{
  526. {
  527. name: "Loopback address",
  528. check: HTTPProxyCheck{
  529. Proto: "https",
  530. Host: "127.0.0.1",
  531. }, // Loopback addresses never should produce proxy warnings
  532. expectWarnings: false,
  533. },
  534. {
  535. name: "IPv4 direct access",
  536. check: HTTPProxyCheck{
  537. Proto: "https",
  538. Host: "10.96.0.1",
  539. }, // Expected to be accessed directly, we set NO_PROXY to 10.0.0.0/8
  540. expectWarnings: false,
  541. },
  542. {
  543. name: "IPv4 via proxy",
  544. check: HTTPProxyCheck{
  545. Proto: "https",
  546. Host: "192.168.0.1",
  547. }, // Expected to go via proxy as this range is not listed in NO_PROXY
  548. expectWarnings: true,
  549. },
  550. {
  551. name: "IPv6 direct access",
  552. check: HTTPProxyCheck{
  553. Proto: "https",
  554. Host: "[2001:db8::1:15]",
  555. }, // Expected to be accessed directly, part of 2001:db8::/48 in NO_PROXY
  556. expectWarnings: false,
  557. },
  558. {
  559. name: "IPv6 via proxy",
  560. check: HTTPProxyCheck{
  561. Proto: "https",
  562. Host: "[2001:db8:1::1:15]",
  563. }, // Expected to go via proxy, range is not in 2001:db8::/48
  564. expectWarnings: true,
  565. },
  566. }
  567. // Save current content of *_proxy and *_PROXY variables.
  568. savedEnv := resetProxyEnv(t)
  569. defer restoreEnv(savedEnv)
  570. for _, rt := range tests {
  571. warning, _ := rt.check.Check()
  572. if (warning != nil) != rt.expectWarnings {
  573. t.Errorf(
  574. "%s failed HTTPProxyCheck:\n\texpected: %t\n\t actual: %t (Host:%s). Warnings: %v",
  575. rt.name,
  576. rt.expectWarnings,
  577. (warning != nil),
  578. rt.check.Host,
  579. warning,
  580. )
  581. }
  582. }
  583. }
  584. // resetProxyEnv is helper function that unsets all *_proxy variables
  585. // and return previously set values as map. This can be used to restore
  586. // original state of the environment.
  587. func resetProxyEnv(t *testing.T) map[string]string {
  588. savedEnv := make(map[string]string)
  589. for _, e := range os.Environ() {
  590. pair := strings.Split(e, "=")
  591. if strings.HasSuffix(strings.ToLower(pair[0]), "_proxy") {
  592. savedEnv[pair[0]] = pair[1]
  593. os.Unsetenv(pair[0])
  594. }
  595. }
  596. t.Log("Saved environment: ", savedEnv)
  597. os.Setenv("HTTP_PROXY", "http://proxy.example.com:3128")
  598. os.Setenv("NO_PROXY", "example.com,10.0.0.0/8,2001:db8::/48")
  599. // Check if we can reliably execute tests:
  600. // ProxyFromEnvironment caches the *_proxy environment variables and
  601. // if ProxyFromEnvironment already executed before our test with empty
  602. // HTTP_PROXY it will make these tests return false positive failures
  603. req, err := http.NewRequest("GET", "http://host.fake.tld/", nil)
  604. if err != nil {
  605. t.Fatalf("unexpected err: %v", err)
  606. }
  607. proxy, err := http.ProxyFromEnvironment(req)
  608. if err != nil {
  609. t.Fatalf("unexpected err: %v", err)
  610. }
  611. if proxy == nil {
  612. t.Skip("test skipped as ProxyFromEnvironment already initialized in environment without defined HTTP proxy")
  613. }
  614. t.Log("http.ProxyFromEnvironment is usable, continue executing test")
  615. return savedEnv
  616. }
  617. // restoreEnv is helper function to restores values
  618. // of environment variables from saved state in the map
  619. func restoreEnv(e map[string]string) {
  620. for k, v := range e {
  621. os.Setenv(k, v)
  622. }
  623. }
  624. func TestKubeletVersionCheck(t *testing.T) {
  625. cases := []struct {
  626. kubeletVersion string
  627. k8sVersion string
  628. expectErrors bool
  629. expectWarnings bool
  630. }{
  631. {"v" + constants.CurrentKubernetesVersion.WithPatch(2).String(), "", false, false}, // check minimally supported version when there is no information about control plane
  632. {"v1.11.3", "v1.11.8", true, false}, // too old kubelet (older than kubeadmconstants.MinimumKubeletVersion), should fail.
  633. {"v" + constants.MinimumKubeletVersion.String(), constants.MinimumControlPlaneVersion.WithPatch(5).String(), false, false}, // kubelet within same major.minor as control plane
  634. {"v" + constants.MinimumKubeletVersion.WithPatch(5).String(), constants.MinimumControlPlaneVersion.WithPatch(1).String(), false, false}, // kubelet is newer, but still within same major.minor as control plane
  635. {"v" + constants.MinimumKubeletVersion.String(), constants.CurrentKubernetesVersion.WithPatch(1).String(), false, false}, // kubelet is lower than control plane, but newer than minimally supported
  636. {"v" + constants.CurrentKubernetesVersion.WithPreRelease("alpha.1").String(), constants.MinimumControlPlaneVersion.WithPatch(1).String(), true, false}, // kubelet is newer (development build) than control plane, should fail.
  637. {"v" + constants.CurrentKubernetesVersion.String(), constants.MinimumControlPlaneVersion.WithPatch(5).String(), true, false}, // kubelet is newer (release) than control plane, should fail.
  638. }
  639. for _, tc := range cases {
  640. t.Run(tc.kubeletVersion, func(t *testing.T) {
  641. fcmd := fakeexec.FakeCmd{
  642. CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
  643. func() ([]byte, error) { return []byte("Kubernetes " + tc.kubeletVersion), nil },
  644. },
  645. }
  646. fexec := &fakeexec.FakeExec{
  647. CommandScript: []fakeexec.FakeCommandAction{
  648. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  649. },
  650. }
  651. check := KubeletVersionCheck{KubernetesVersion: tc.k8sVersion, exec: fexec}
  652. warnings, errors := check.Check()
  653. switch {
  654. case warnings != nil && !tc.expectWarnings:
  655. t.Errorf("KubeletVersionCheck: unexpected warnings for kubelet version %q and Kubernetes version %q. Warnings: %v", tc.kubeletVersion, tc.k8sVersion, warnings)
  656. case warnings == nil && tc.expectWarnings:
  657. t.Errorf("KubeletVersionCheck: expected warnings for kubelet version %q and Kubernetes version %q but got nothing", tc.kubeletVersion, tc.k8sVersion)
  658. case errors != nil && !tc.expectErrors:
  659. t.Errorf("KubeletVersionCheck: unexpected errors for kubelet version %q and Kubernetes version %q. errors: %v", tc.kubeletVersion, tc.k8sVersion, errors)
  660. case errors == nil && tc.expectErrors:
  661. t.Errorf("KubeletVersionCheck: expected errors for kubelet version %q and Kubernetes version %q but got nothing", tc.kubeletVersion, tc.k8sVersion)
  662. }
  663. })
  664. }
  665. }
  666. func TestSetHasItemOrAll(t *testing.T) {
  667. var tests = []struct {
  668. ignoreSet sets.String
  669. testString string
  670. expectedResult bool
  671. }{
  672. {sets.NewString(), "foo", false},
  673. {sets.NewString("all"), "foo", true},
  674. {sets.NewString("all", "bar"), "foo", true},
  675. {sets.NewString("bar"), "foo", false},
  676. {sets.NewString("baz", "foo", "bar"), "foo", true},
  677. {sets.NewString("baz", "bar", "foo"), "Foo", true},
  678. }
  679. for _, rt := range tests {
  680. t.Run(rt.testString, func(t *testing.T) {
  681. result := setHasItemOrAll(rt.ignoreSet, rt.testString)
  682. if result != rt.expectedResult {
  683. t.Errorf(
  684. "setHasItemOrAll: expected: %v actual: %v (arguments: %q, %q)",
  685. rt.expectedResult, result,
  686. rt.ignoreSet,
  687. rt.testString,
  688. )
  689. }
  690. })
  691. }
  692. }
  693. func TestImagePullCheck(t *testing.T) {
  694. fcmd := fakeexec.FakeCmd{
  695. RunScript: []fakeexec.FakeRunAction{
  696. // Test case 1: img1 and img2 exist, img3 doesn't exist
  697. func() ([]byte, []byte, error) { return nil, nil, nil },
  698. func() ([]byte, []byte, error) { return nil, nil, nil },
  699. func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
  700. // Test case 2: images don't exist
  701. func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
  702. func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
  703. func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
  704. },
  705. CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
  706. // Test case1: pull only img3
  707. func() ([]byte, error) { return nil, nil },
  708. // Test case 2: fail to pull image2 and image3
  709. func() ([]byte, error) { return nil, nil },
  710. func() ([]byte, error) { return []byte("error"), &fakeexec.FakeExitError{Status: 1} },
  711. func() ([]byte, error) { return []byte("error"), &fakeexec.FakeExitError{Status: 1} },
  712. },
  713. }
  714. fexec := fakeexec.FakeExec{
  715. CommandScript: []fakeexec.FakeCommandAction{
  716. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  717. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  718. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  719. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  720. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  721. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  722. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  723. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  724. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  725. func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
  726. },
  727. LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/docker", nil },
  728. }
  729. containerRuntime, err := utilruntime.NewContainerRuntime(&fexec, constants.DefaultDockerCRISocket)
  730. if err != nil {
  731. t.Errorf("unexpected NewContainerRuntime error: %v", err)
  732. }
  733. check := ImagePullCheck{
  734. runtime: containerRuntime,
  735. imageList: []string{"img1", "img2", "img3"},
  736. }
  737. warnings, errors := check.Check()
  738. if len(warnings) != 0 {
  739. t.Fatalf("did not expect any warnings but got %q", warnings)
  740. }
  741. if len(errors) != 0 {
  742. t.Fatalf("expected 1 errors but got %d: %q", len(errors), errors)
  743. }
  744. warnings, errors = check.Check()
  745. if len(warnings) != 0 {
  746. t.Fatalf("did not expect any warnings but got %q", warnings)
  747. }
  748. if len(errors) != 2 {
  749. t.Fatalf("expected 2 errors but got %d: %q", len(errors), errors)
  750. }
  751. }
  752. func TestNumCPUCheck(t *testing.T) {
  753. var tests = []struct {
  754. numCPU int
  755. numErrors int
  756. numWarnings int
  757. }{
  758. {0, 0, 0},
  759. {999999999, 1, 0},
  760. }
  761. for _, rt := range tests {
  762. t.Run(fmt.Sprintf("number of CPUs: %d", rt.numCPU), func(t *testing.T) {
  763. warnings, errors := NumCPUCheck{NumCPU: rt.numCPU}.Check()
  764. if len(warnings) != rt.numWarnings {
  765. t.Errorf("expected %d warning(s) but got %d: %q", rt.numWarnings, len(warnings), warnings)
  766. }
  767. if len(errors) != rt.numErrors {
  768. t.Errorf("expected %d warning(s) but got %d: %q", rt.numErrors, len(errors), errors)
  769. }
  770. })
  771. }
  772. }