container_log_manager_test.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /*
  2. Copyright 2018 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 logs
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io"
  18. "io/ioutil"
  19. "os"
  20. "path/filepath"
  21. "testing"
  22. "time"
  23. "github.com/stretchr/testify/assert"
  24. "github.com/stretchr/testify/require"
  25. "k8s.io/apimachinery/pkg/util/clock"
  26. runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
  27. critest "k8s.io/cri-api/pkg/apis/testing"
  28. )
  29. func TestGetAllLogs(t *testing.T) {
  30. dir, err := ioutil.TempDir("", "test-get-all-logs")
  31. require.NoError(t, err)
  32. defer os.RemoveAll(dir)
  33. testLogs := []string{
  34. "test-log.11111111-111111.gz",
  35. "test-log",
  36. "test-log.00000000-000000.gz",
  37. "test-log.19900322-000000.gz",
  38. "test-log.19900322-111111.gz",
  39. "test-log.19880620-000000", // unused log
  40. "test-log.19880620-000000.gz",
  41. "test-log.19880620-111111.gz",
  42. "test-log.20180101-000000",
  43. "test-log.20180101-000000.tmp", // temporary log
  44. }
  45. expectLogs := []string{
  46. "test-log.00000000-000000.gz",
  47. "test-log.11111111-111111.gz",
  48. "test-log.19880620-000000.gz",
  49. "test-log.19880620-111111.gz",
  50. "test-log.19900322-000000.gz",
  51. "test-log.19900322-111111.gz",
  52. "test-log.20180101-000000",
  53. "test-log",
  54. }
  55. for i := range testLogs {
  56. f, err := os.Create(filepath.Join(dir, testLogs[i]))
  57. require.NoError(t, err)
  58. f.Close()
  59. }
  60. got, err := GetAllLogs(filepath.Join(dir, "test-log"))
  61. assert.NoError(t, err)
  62. for i := range expectLogs {
  63. expectLogs[i] = filepath.Join(dir, expectLogs[i])
  64. }
  65. assert.Equal(t, expectLogs, got)
  66. }
  67. func TestRotateLogs(t *testing.T) {
  68. dir, err := ioutil.TempDir("", "test-rotate-logs")
  69. require.NoError(t, err)
  70. defer os.RemoveAll(dir)
  71. const (
  72. testMaxFiles = 3
  73. testMaxSize = 10
  74. )
  75. now := time.Now()
  76. f := critest.NewFakeRuntimeService()
  77. c := &containerLogManager{
  78. runtimeService: f,
  79. policy: LogRotatePolicy{
  80. MaxSize: testMaxSize,
  81. MaxFiles: testMaxFiles,
  82. },
  83. clock: clock.NewFakeClock(now),
  84. }
  85. testLogs := []string{
  86. "test-log-1",
  87. "test-log-2",
  88. "test-log-3",
  89. "test-log-4",
  90. "test-log-3.00000000-000001",
  91. "test-log-3.00000000-000000.gz",
  92. }
  93. testContent := []string{
  94. "short",
  95. "longer than 10 bytes",
  96. "longer than 10 bytes",
  97. "longer than 10 bytes",
  98. "the length doesn't matter",
  99. "the length doesn't matter",
  100. }
  101. for i := range testLogs {
  102. f, err := os.Create(filepath.Join(dir, testLogs[i]))
  103. require.NoError(t, err)
  104. _, err = f.Write([]byte(testContent[i]))
  105. require.NoError(t, err)
  106. f.Close()
  107. }
  108. testContainers := []*critest.FakeContainer{
  109. {
  110. ContainerStatus: runtimeapi.ContainerStatus{
  111. Id: "container-not-need-rotate",
  112. State: runtimeapi.ContainerState_CONTAINER_RUNNING,
  113. LogPath: filepath.Join(dir, testLogs[0]),
  114. },
  115. },
  116. {
  117. ContainerStatus: runtimeapi.ContainerStatus{
  118. Id: "container-need-rotate",
  119. State: runtimeapi.ContainerState_CONTAINER_RUNNING,
  120. LogPath: filepath.Join(dir, testLogs[1]),
  121. },
  122. },
  123. {
  124. ContainerStatus: runtimeapi.ContainerStatus{
  125. Id: "container-has-excess-log",
  126. State: runtimeapi.ContainerState_CONTAINER_RUNNING,
  127. LogPath: filepath.Join(dir, testLogs[2]),
  128. },
  129. },
  130. {
  131. ContainerStatus: runtimeapi.ContainerStatus{
  132. Id: "container-is-not-running",
  133. State: runtimeapi.ContainerState_CONTAINER_EXITED,
  134. LogPath: filepath.Join(dir, testLogs[3]),
  135. },
  136. },
  137. }
  138. f.SetFakeContainers(testContainers)
  139. require.NoError(t, c.rotateLogs())
  140. timestamp := now.Format(timestampFormat)
  141. logs, err := ioutil.ReadDir(dir)
  142. require.NoError(t, err)
  143. assert.Len(t, logs, 5)
  144. assert.Equal(t, testLogs[0], logs[0].Name())
  145. assert.Equal(t, testLogs[1]+"."+timestamp, logs[1].Name())
  146. assert.Equal(t, testLogs[4]+compressSuffix, logs[2].Name())
  147. assert.Equal(t, testLogs[2]+"."+timestamp, logs[3].Name())
  148. assert.Equal(t, testLogs[3], logs[4].Name())
  149. }
  150. func TestCleanupUnusedLog(t *testing.T) {
  151. dir, err := ioutil.TempDir("", "test-cleanup-unused-log")
  152. require.NoError(t, err)
  153. defer os.RemoveAll(dir)
  154. testLogs := []string{
  155. "test-log-1", // regular log
  156. "test-log-1.tmp", // temporary log
  157. "test-log-2", // unused log
  158. "test-log-2.gz", // compressed log
  159. }
  160. for i := range testLogs {
  161. testLogs[i] = filepath.Join(dir, testLogs[i])
  162. f, err := os.Create(testLogs[i])
  163. require.NoError(t, err)
  164. f.Close()
  165. }
  166. c := &containerLogManager{}
  167. got, err := c.cleanupUnusedLogs(testLogs)
  168. require.NoError(t, err)
  169. assert.Len(t, got, 2)
  170. assert.Equal(t, []string{testLogs[0], testLogs[3]}, got)
  171. logs, err := ioutil.ReadDir(dir)
  172. require.NoError(t, err)
  173. assert.Len(t, logs, 2)
  174. assert.Equal(t, testLogs[0], filepath.Join(dir, logs[0].Name()))
  175. assert.Equal(t, testLogs[3], filepath.Join(dir, logs[1].Name()))
  176. }
  177. func TestRemoveExcessLog(t *testing.T) {
  178. for desc, test := range map[string]struct {
  179. max int
  180. expect []string
  181. }{
  182. "MaxFiles equal to 2": {
  183. max: 2,
  184. expect: []string{},
  185. },
  186. "MaxFiles more than 2": {
  187. max: 3,
  188. expect: []string{"test-log-4"},
  189. },
  190. "MaxFiles more than log file number": {
  191. max: 6,
  192. expect: []string{"test-log-1", "test-log-2", "test-log-3", "test-log-4"},
  193. },
  194. } {
  195. t.Logf("TestCase %q", desc)
  196. dir, err := ioutil.TempDir("", "test-remove-excess-log")
  197. require.NoError(t, err)
  198. defer os.RemoveAll(dir)
  199. testLogs := []string{"test-log-3", "test-log-1", "test-log-2", "test-log-4"}
  200. for i := range testLogs {
  201. testLogs[i] = filepath.Join(dir, testLogs[i])
  202. f, err := os.Create(testLogs[i])
  203. require.NoError(t, err)
  204. f.Close()
  205. }
  206. c := &containerLogManager{policy: LogRotatePolicy{MaxFiles: test.max}}
  207. got, err := c.removeExcessLogs(testLogs)
  208. require.NoError(t, err)
  209. require.Len(t, got, len(test.expect))
  210. for i, name := range test.expect {
  211. assert.Equal(t, name, filepath.Base(got[i]))
  212. }
  213. logs, err := ioutil.ReadDir(dir)
  214. require.NoError(t, err)
  215. require.Len(t, logs, len(test.expect))
  216. for i, name := range test.expect {
  217. assert.Equal(t, name, logs[i].Name())
  218. }
  219. }
  220. }
  221. func TestCompressLog(t *testing.T) {
  222. dir, err := ioutil.TempDir("", "test-compress-log")
  223. require.NoError(t, err)
  224. defer os.RemoveAll(dir)
  225. testFile, err := ioutil.TempFile(dir, "test-rotate-latest-log")
  226. require.NoError(t, err)
  227. defer testFile.Close()
  228. testContent := "test log content"
  229. _, err = testFile.Write([]byte(testContent))
  230. require.NoError(t, err)
  231. testLog := testFile.Name()
  232. c := &containerLogManager{}
  233. require.NoError(t, c.compressLog(testLog))
  234. _, err = os.Stat(testLog + compressSuffix)
  235. assert.NoError(t, err, "log should be compressed")
  236. _, err = os.Stat(testLog + tmpSuffix)
  237. assert.Error(t, err, "temporary log should be renamed")
  238. _, err = os.Stat(testLog)
  239. assert.Error(t, err, "original log should be removed")
  240. rc, err := UncompressLog(testLog + compressSuffix)
  241. require.NoError(t, err)
  242. defer rc.Close()
  243. var buf bytes.Buffer
  244. _, err = io.Copy(&buf, rc)
  245. require.NoError(t, err)
  246. assert.Equal(t, testContent, buf.String())
  247. }
  248. func TestRotateLatestLog(t *testing.T) {
  249. dir, err := ioutil.TempDir("", "test-rotate-latest-log")
  250. require.NoError(t, err)
  251. defer os.RemoveAll(dir)
  252. for desc, test := range map[string]struct {
  253. runtimeError error
  254. maxFiles int
  255. expectError bool
  256. expectOriginal bool
  257. expectRotated bool
  258. }{
  259. "should successfully rotate log when MaxFiles is 2": {
  260. maxFiles: 2,
  261. expectError: false,
  262. expectOriginal: false,
  263. expectRotated: true,
  264. },
  265. "should restore original log when ReopenContainerLog fails": {
  266. runtimeError: fmt.Errorf("random error"),
  267. maxFiles: 2,
  268. expectError: true,
  269. expectOriginal: true,
  270. expectRotated: false,
  271. },
  272. } {
  273. t.Logf("TestCase %q", desc)
  274. now := time.Now()
  275. f := critest.NewFakeRuntimeService()
  276. c := &containerLogManager{
  277. runtimeService: f,
  278. policy: LogRotatePolicy{MaxFiles: test.maxFiles},
  279. clock: clock.NewFakeClock(now),
  280. }
  281. if test.runtimeError != nil {
  282. f.InjectError("ReopenContainerLog", test.runtimeError)
  283. }
  284. testFile, err := ioutil.TempFile(dir, "test-rotate-latest-log")
  285. require.NoError(t, err)
  286. defer testFile.Close()
  287. testLog := testFile.Name()
  288. rotatedLog := fmt.Sprintf("%s.%s", testLog, now.Format(timestampFormat))
  289. err = c.rotateLatestLog("test-id", testLog)
  290. assert.Equal(t, test.expectError, err != nil)
  291. _, err = os.Stat(testLog)
  292. assert.Equal(t, test.expectOriginal, err == nil)
  293. _, err = os.Stat(rotatedLog)
  294. assert.Equal(t, test.expectRotated, err == nil)
  295. assert.NoError(t, f.AssertCalls([]string{"ReopenContainerLog"}))
  296. }
  297. }