downwardapi_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. /*
  2. Copyright 2015 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 downwardapi
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "os"
  18. "path/filepath"
  19. "testing"
  20. "k8s.io/api/core/v1"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/types"
  23. clientset "k8s.io/client-go/kubernetes"
  24. "k8s.io/client-go/kubernetes/fake"
  25. utiltesting "k8s.io/client-go/util/testing"
  26. "k8s.io/kubernetes/pkg/fieldpath"
  27. "k8s.io/kubernetes/pkg/volume"
  28. "k8s.io/kubernetes/pkg/volume/emptydir"
  29. volumetest "k8s.io/kubernetes/pkg/volume/testing"
  30. )
  31. const (
  32. downwardAPIDir = "..data"
  33. testPodUID = types.UID("test_pod_uid")
  34. testNamespace = "test_metadata_namespace"
  35. testName = "test_metadata_name"
  36. )
  37. func newTestHost(t *testing.T, clientset clientset.Interface) (string, volume.VolumeHost) {
  38. tempDir, err := utiltesting.MkTmpdir("downwardApi_volume_test.")
  39. if err != nil {
  40. t.Fatalf("can't make a temp rootdir: %v", err)
  41. }
  42. return tempDir, volumetest.NewFakeVolumeHost(t, tempDir, clientset, emptydir.ProbeVolumePlugins())
  43. }
  44. func TestCanSupport(t *testing.T) {
  45. pluginMgr := volume.VolumePluginMgr{}
  46. tmpDir, host := newTestHost(t, nil)
  47. defer os.RemoveAll(tmpDir)
  48. pluginMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host)
  49. plugin, err := pluginMgr.FindPluginByName(downwardAPIPluginName)
  50. if err != nil {
  51. t.Errorf("Can't find the plugin by name")
  52. }
  53. if plugin.GetPluginName() != downwardAPIPluginName {
  54. t.Errorf("Wrong name: %s", plugin.GetPluginName())
  55. }
  56. if !plugin.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{DownwardAPI: &v1.DownwardAPIVolumeSource{}}}}) {
  57. t.Errorf("Expected true")
  58. }
  59. if plugin.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{}}}) {
  60. t.Errorf("Expected false")
  61. }
  62. }
  63. func TestDownwardAPI(t *testing.T) {
  64. labels1 := map[string]string{
  65. "key1": "value1",
  66. "key2": "value2",
  67. }
  68. labels2 := map[string]string{
  69. "key1": "value1",
  70. "key2": "value2",
  71. "key3": "value3",
  72. }
  73. annotations := map[string]string{
  74. "a1": "value1",
  75. "a2": "value2",
  76. "multiline": "c\nb\na",
  77. }
  78. testCases := []struct {
  79. name string
  80. files map[string]string
  81. modes map[string]int32
  82. podLabels map[string]string
  83. podAnnotations map[string]string
  84. steps []testStep
  85. }{
  86. {
  87. name: "test_labels",
  88. files: map[string]string{"labels": "metadata.labels"},
  89. podLabels: labels1,
  90. steps: []testStep{
  91. // for steps that involve files, stepName is also
  92. // used as the name of the file to verify
  93. verifyMapInFile{stepName{"labels"}, labels1},
  94. },
  95. },
  96. {
  97. name: "test_annotations",
  98. files: map[string]string{"annotations": "metadata.annotations"},
  99. podAnnotations: annotations,
  100. steps: []testStep{
  101. verifyMapInFile{stepName{"annotations"}, annotations},
  102. },
  103. },
  104. {
  105. name: "test_name",
  106. files: map[string]string{"name_file_name": "metadata.name"},
  107. steps: []testStep{
  108. verifyLinesInFile{stepName{"name_file_name"}, testName},
  109. },
  110. },
  111. {
  112. name: "test_namespace",
  113. files: map[string]string{"namespace_file_name": "metadata.namespace"},
  114. steps: []testStep{
  115. verifyLinesInFile{stepName{"namespace_file_name"}, testNamespace},
  116. },
  117. },
  118. {
  119. name: "test_write_twice_no_update",
  120. files: map[string]string{"labels": "metadata.labels"},
  121. podLabels: labels1,
  122. steps: []testStep{
  123. reSetUp{stepName{"resetup"}, false, nil},
  124. verifyMapInFile{stepName{"labels"}, labels1},
  125. },
  126. },
  127. {
  128. name: "test_write_twice_with_update",
  129. files: map[string]string{"labels": "metadata.labels"},
  130. podLabels: labels1,
  131. steps: []testStep{
  132. reSetUp{stepName{"resetup"}, true, labels2},
  133. verifyMapInFile{stepName{"labels"}, labels2},
  134. },
  135. },
  136. {
  137. name: "test_write_with_unix_path",
  138. files: map[string]string{
  139. "these/are/my/labels": "metadata.labels",
  140. "these/are/your/annotations": "metadata.annotations",
  141. },
  142. podLabels: labels1,
  143. podAnnotations: annotations,
  144. steps: []testStep{
  145. verifyMapInFile{stepName{"these/are/my/labels"}, labels1},
  146. verifyMapInFile{stepName{"these/are/your/annotations"}, annotations},
  147. },
  148. },
  149. {
  150. name: "test_write_with_two_consecutive_slashes_in_the_path",
  151. files: map[string]string{"this//labels": "metadata.labels"},
  152. podLabels: labels1,
  153. steps: []testStep{
  154. verifyMapInFile{stepName{"this/labels"}, labels1},
  155. },
  156. },
  157. {
  158. name: "test_default_mode",
  159. files: map[string]string{"name_file_name": "metadata.name"},
  160. steps: []testStep{
  161. verifyMode{stepName{"name_file_name"}, 0644},
  162. },
  163. },
  164. {
  165. name: "test_item_mode",
  166. files: map[string]string{"name_file_name": "metadata.name"},
  167. modes: map[string]int32{"name_file_name": 0400},
  168. steps: []testStep{
  169. verifyMode{stepName{"name_file_name"}, 0400},
  170. },
  171. },
  172. }
  173. for _, testCase := range testCases {
  174. test := newDownwardAPITest(t, testCase.name, testCase.files, testCase.podLabels, testCase.podAnnotations, testCase.modes)
  175. for _, step := range testCase.steps {
  176. test.t.Logf("Test case: %q Step: %q", testCase.name, step.getName())
  177. step.run(test)
  178. }
  179. test.tearDown()
  180. }
  181. }
  182. type downwardAPITest struct {
  183. t *testing.T
  184. name string
  185. plugin volume.VolumePlugin
  186. pod *v1.Pod
  187. mounter volume.Mounter
  188. volumePath string
  189. rootDir string
  190. }
  191. func newDownwardAPITest(t *testing.T, name string, volumeFiles, podLabels, podAnnotations map[string]string, modes map[string]int32) *downwardAPITest {
  192. defaultMode := int32(0644)
  193. var files []v1.DownwardAPIVolumeFile
  194. for path, fieldPath := range volumeFiles {
  195. file := v1.DownwardAPIVolumeFile{
  196. Path: path,
  197. FieldRef: &v1.ObjectFieldSelector{
  198. FieldPath: fieldPath,
  199. },
  200. }
  201. if mode, found := modes[path]; found {
  202. file.Mode = &mode
  203. }
  204. files = append(files, file)
  205. }
  206. podMeta := metav1.ObjectMeta{
  207. Name: testName,
  208. Namespace: testNamespace,
  209. Labels: podLabels,
  210. Annotations: podAnnotations,
  211. }
  212. clientset := fake.NewSimpleClientset(&v1.Pod{ObjectMeta: podMeta})
  213. pluginMgr := volume.VolumePluginMgr{}
  214. rootDir, host := newTestHost(t, clientset)
  215. pluginMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host)
  216. plugin, err := pluginMgr.FindPluginByName(downwardAPIPluginName)
  217. if err != nil {
  218. t.Errorf("Can't find the plugin by name")
  219. }
  220. volumeSpec := &v1.Volume{
  221. Name: name,
  222. VolumeSource: v1.VolumeSource{
  223. DownwardAPI: &v1.DownwardAPIVolumeSource{
  224. DefaultMode: &defaultMode,
  225. Items: files,
  226. },
  227. },
  228. }
  229. podMeta.UID = testPodUID
  230. pod := &v1.Pod{ObjectMeta: podMeta}
  231. mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{})
  232. if err != nil {
  233. t.Errorf("Failed to make a new Mounter: %v", err)
  234. }
  235. if mounter == nil {
  236. t.Fatalf("Got a nil Mounter")
  237. }
  238. volumePath := mounter.GetPath()
  239. err = mounter.SetUp(volume.MounterArgs{})
  240. if err != nil {
  241. t.Errorf("Failed to setup volume: %v", err)
  242. }
  243. // downwardAPI volume should create its own empty wrapper path
  244. podWrapperMetadataDir := fmt.Sprintf("%v/pods/%v/plugins/kubernetes.io~empty-dir/wrapped_%v", rootDir, testPodUID, name)
  245. if _, err := os.Stat(podWrapperMetadataDir); err != nil {
  246. if os.IsNotExist(err) {
  247. t.Errorf("SetUp() failed, empty-dir wrapper path was not created: %s", podWrapperMetadataDir)
  248. } else {
  249. t.Errorf("SetUp() failed: %v", err)
  250. }
  251. }
  252. return &downwardAPITest{
  253. t: t,
  254. plugin: plugin,
  255. pod: pod,
  256. mounter: mounter,
  257. volumePath: volumePath,
  258. rootDir: rootDir,
  259. }
  260. }
  261. func (test *downwardAPITest) tearDown() {
  262. unmounter, err := test.plugin.NewUnmounter(test.name, testPodUID)
  263. if err != nil {
  264. test.t.Errorf("Failed to make a new Unmounter: %v", err)
  265. }
  266. if unmounter == nil {
  267. test.t.Fatalf("Got a nil Unmounter")
  268. }
  269. if err := unmounter.TearDown(); err != nil {
  270. test.t.Errorf("Expected success, got: %v", err)
  271. }
  272. if _, err := os.Stat(test.volumePath); err == nil {
  273. test.t.Errorf("TearDown() failed, volume path still exists: %s", test.volumePath)
  274. } else if !os.IsNotExist(err) {
  275. test.t.Errorf("TearDown() failed: %v", err)
  276. }
  277. os.RemoveAll(test.rootDir)
  278. }
  279. // testStep represents a named step of downwardAPITest.
  280. // For steps that deal with files, step name also serves
  281. // as the name of the file that's used by the step.
  282. type testStep interface {
  283. getName() string
  284. run(*downwardAPITest)
  285. }
  286. type stepName struct {
  287. name string
  288. }
  289. func (step stepName) getName() string { return step.name }
  290. func doVerifyLinesInFile(t *testing.T, volumePath, filename string, expected string) {
  291. data, err := ioutil.ReadFile(filepath.Join(volumePath, filename))
  292. if err != nil {
  293. t.Errorf(err.Error())
  294. return
  295. }
  296. actualStr := string(data)
  297. expectedStr := expected
  298. if actualStr != expectedStr {
  299. t.Errorf("Found `%s`, expected `%s`", actualStr, expectedStr)
  300. }
  301. }
  302. type verifyLinesInFile struct {
  303. stepName
  304. expected string
  305. }
  306. func (step verifyLinesInFile) run(test *downwardAPITest) {
  307. doVerifyLinesInFile(test.t, test.volumePath, step.name, step.expected)
  308. }
  309. type verifyMapInFile struct {
  310. stepName
  311. expected map[string]string
  312. }
  313. func (step verifyMapInFile) run(test *downwardAPITest) {
  314. doVerifyLinesInFile(test.t, test.volumePath, step.name, fieldpath.FormatMap(step.expected))
  315. }
  316. type verifyMode struct {
  317. stepName
  318. expectedMode int32
  319. }
  320. func (step verifyMode) run(test *downwardAPITest) {
  321. fileInfo, err := os.Stat(filepath.Join(test.volumePath, step.name))
  322. if err != nil {
  323. test.t.Errorf(err.Error())
  324. return
  325. }
  326. actualMode := fileInfo.Mode()
  327. expectedMode := os.FileMode(step.expectedMode)
  328. if actualMode != expectedMode {
  329. test.t.Errorf("Found mode `%v` expected %v", actualMode, expectedMode)
  330. }
  331. }
  332. type reSetUp struct {
  333. stepName
  334. linkShouldChange bool
  335. newLabels map[string]string
  336. }
  337. func (step reSetUp) run(test *downwardAPITest) {
  338. if step.newLabels != nil {
  339. test.pod.ObjectMeta.Labels = step.newLabels
  340. }
  341. currentTarget, err := os.Readlink(filepath.Join(test.volumePath, downwardAPIDir))
  342. if err != nil {
  343. test.t.Errorf("labels file should be a link... %s\n", err.Error())
  344. }
  345. // now re-run Setup
  346. if err = test.mounter.SetUp(volume.MounterArgs{}); err != nil {
  347. test.t.Errorf("Failed to re-setup volume: %v", err)
  348. }
  349. // get the link of the link
  350. currentTarget2, err := os.Readlink(filepath.Join(test.volumePath, downwardAPIDir))
  351. if err != nil {
  352. test.t.Errorf(".current should be a link... %s\n", err.Error())
  353. }
  354. switch {
  355. case step.linkShouldChange && currentTarget2 == currentTarget:
  356. test.t.Errorf("Got and update between the two Setup... Target link should NOT be the same\n")
  357. case !step.linkShouldChange && currentTarget2 != currentTarget:
  358. test.t.Errorf("No update between the two Setup... Target link should be the same %s %s\n",
  359. currentTarget, currentTarget2)
  360. }
  361. }