fake_docker_client.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. /*
  2. Copyright 2014 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 dockertools
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "math/rand"
  18. "os"
  19. "reflect"
  20. "sort"
  21. "sync"
  22. "time"
  23. dockertypes "github.com/docker/engine-api/types"
  24. dockercontainer "github.com/docker/engine-api/types/container"
  25. "k8s.io/kubernetes/pkg/api"
  26. )
  27. type calledDetail struct {
  28. name string
  29. arguments []interface{}
  30. }
  31. // FakeDockerClient is a simple fake docker client, so that kubelet can be run for testing without requiring a real docker setup.
  32. type FakeDockerClient struct {
  33. sync.Mutex
  34. RunningContainerList []dockertypes.Container
  35. ExitedContainerList []dockertypes.Container
  36. ContainerMap map[string]*dockertypes.ContainerJSON
  37. Image *dockertypes.ImageInspect
  38. Images []dockertypes.Image
  39. Errors map[string]error
  40. called []calledDetail
  41. pulled []string
  42. // Created, Stopped and Removed all container docker ID
  43. Created []string
  44. Started []string
  45. Stopped []string
  46. Removed []string
  47. VersionInfo dockertypes.Version
  48. Information dockertypes.Info
  49. ExecInspect *dockertypes.ContainerExecInspect
  50. execCmd []string
  51. EnableSleep bool
  52. ImageHistoryMap map[string][]dockertypes.ImageHistory
  53. }
  54. // We don't check docker version now, just set the docker version of fake docker client to 1.8.1.
  55. // Notice that if someday we also have minimum docker version requirement, this should also be updated.
  56. const fakeDockerVersion = "1.8.1"
  57. func NewFakeDockerClient() *FakeDockerClient {
  58. return NewFakeDockerClientWithVersion(fakeDockerVersion, minimumDockerAPIVersion)
  59. }
  60. func NewFakeDockerClientWithVersion(version, apiVersion string) *FakeDockerClient {
  61. return &FakeDockerClient{
  62. VersionInfo: dockertypes.Version{Version: version, APIVersion: apiVersion},
  63. Errors: make(map[string]error),
  64. ContainerMap: make(map[string]*dockertypes.ContainerJSON),
  65. }
  66. }
  67. func (f *FakeDockerClient) InjectError(fn string, err error) {
  68. f.Lock()
  69. defer f.Unlock()
  70. f.Errors[fn] = err
  71. }
  72. func (f *FakeDockerClient) InjectErrors(errs map[string]error) {
  73. f.Lock()
  74. defer f.Unlock()
  75. for fn, err := range errs {
  76. f.Errors[fn] = err
  77. }
  78. }
  79. func (f *FakeDockerClient) ClearErrors() {
  80. f.Lock()
  81. defer f.Unlock()
  82. f.Errors = map[string]error{}
  83. }
  84. func (f *FakeDockerClient) ClearCalls() {
  85. f.Lock()
  86. defer f.Unlock()
  87. f.called = []calledDetail{}
  88. f.Stopped = []string{}
  89. f.pulled = []string{}
  90. f.Created = []string{}
  91. f.Removed = []string{}
  92. }
  93. func (f *FakeDockerClient) getCalledNames() []string {
  94. names := []string{}
  95. for _, detail := range f.called {
  96. names = append(names, detail.name)
  97. }
  98. return names
  99. }
  100. // Because the new data type returned by engine-api is too complex to manually initialize, we need a
  101. // fake container which is easier to initialize.
  102. type FakeContainer struct {
  103. ID string
  104. Name string
  105. Running bool
  106. ExitCode int
  107. Pid int
  108. CreatedAt time.Time
  109. StartedAt time.Time
  110. FinishedAt time.Time
  111. Config *dockercontainer.Config
  112. HostConfig *dockercontainer.HostConfig
  113. }
  114. // convertFakeContainer converts the fake container to real container
  115. func convertFakeContainer(f *FakeContainer) *dockertypes.ContainerJSON {
  116. if f.Config == nil {
  117. f.Config = &dockercontainer.Config{}
  118. }
  119. if f.HostConfig == nil {
  120. f.HostConfig = &dockercontainer.HostConfig{}
  121. }
  122. return &dockertypes.ContainerJSON{
  123. ContainerJSONBase: &dockertypes.ContainerJSONBase{
  124. ID: f.ID,
  125. Name: f.Name,
  126. State: &dockertypes.ContainerState{
  127. Running: f.Running,
  128. ExitCode: f.ExitCode,
  129. Pid: f.Pid,
  130. StartedAt: dockerTimestampToString(f.StartedAt),
  131. FinishedAt: dockerTimestampToString(f.FinishedAt),
  132. },
  133. Created: dockerTimestampToString(f.CreatedAt),
  134. HostConfig: f.HostConfig,
  135. },
  136. Config: f.Config,
  137. NetworkSettings: &dockertypes.NetworkSettings{},
  138. }
  139. }
  140. func (f *FakeDockerClient) SetFakeContainers(containers []*FakeContainer) {
  141. f.Lock()
  142. defer f.Unlock()
  143. // Reset the lists and the map.
  144. f.ContainerMap = map[string]*dockertypes.ContainerJSON{}
  145. f.RunningContainerList = []dockertypes.Container{}
  146. f.ExitedContainerList = []dockertypes.Container{}
  147. for i := range containers {
  148. c := containers[i]
  149. f.ContainerMap[c.ID] = convertFakeContainer(c)
  150. container := dockertypes.Container{
  151. Names: []string{c.Name},
  152. ID: c.ID,
  153. }
  154. if c.Running {
  155. f.RunningContainerList = append(f.RunningContainerList, container)
  156. } else {
  157. f.ExitedContainerList = append(f.ExitedContainerList, container)
  158. }
  159. }
  160. }
  161. func (f *FakeDockerClient) SetFakeRunningContainers(containers []*FakeContainer) {
  162. for _, c := range containers {
  163. c.Running = true
  164. }
  165. f.SetFakeContainers(containers)
  166. }
  167. func (f *FakeDockerClient) AssertCalls(calls []string) (err error) {
  168. f.Lock()
  169. defer f.Unlock()
  170. if !reflect.DeepEqual(calls, f.getCalledNames()) {
  171. err = fmt.Errorf("expected %#v, got %#v", calls, f.getCalledNames())
  172. }
  173. return
  174. }
  175. func (f *FakeDockerClient) AssertCallDetails(calls []calledDetail) (err error) {
  176. f.Lock()
  177. defer f.Unlock()
  178. if !reflect.DeepEqual(calls, f.called) {
  179. err = fmt.Errorf("expected %#v, got %#v", calls, f.called)
  180. }
  181. return
  182. }
  183. func (f *FakeDockerClient) AssertCreated(created []string) error {
  184. f.Lock()
  185. defer f.Unlock()
  186. actualCreated := []string{}
  187. for _, c := range f.Created {
  188. dockerName, _, err := ParseDockerName(c)
  189. if err != nil {
  190. return fmt.Errorf("unexpected error: %v", err)
  191. }
  192. actualCreated = append(actualCreated, dockerName.ContainerName)
  193. }
  194. sort.StringSlice(created).Sort()
  195. sort.StringSlice(actualCreated).Sort()
  196. if !reflect.DeepEqual(created, actualCreated) {
  197. return fmt.Errorf("expected %#v, got %#v", created, actualCreated)
  198. }
  199. return nil
  200. }
  201. func (f *FakeDockerClient) AssertStarted(started []string) error {
  202. f.Lock()
  203. defer f.Unlock()
  204. sort.StringSlice(started).Sort()
  205. sort.StringSlice(f.Started).Sort()
  206. if !reflect.DeepEqual(started, f.Started) {
  207. return fmt.Errorf("expected %#v, got %#v", started, f.Started)
  208. }
  209. return nil
  210. }
  211. func (f *FakeDockerClient) AssertStopped(stopped []string) error {
  212. f.Lock()
  213. defer f.Unlock()
  214. sort.StringSlice(stopped).Sort()
  215. sort.StringSlice(f.Stopped).Sort()
  216. if !reflect.DeepEqual(stopped, f.Stopped) {
  217. return fmt.Errorf("expected %#v, got %#v", stopped, f.Stopped)
  218. }
  219. return nil
  220. }
  221. func (f *FakeDockerClient) popError(op string) error {
  222. if f.Errors == nil {
  223. return nil
  224. }
  225. err, ok := f.Errors[op]
  226. if ok {
  227. delete(f.Errors, op)
  228. return err
  229. } else {
  230. return nil
  231. }
  232. }
  233. // ListContainers is a test-spy implementation of DockerInterface.ListContainers.
  234. // It adds an entry "list" to the internal method call record.
  235. func (f *FakeDockerClient) ListContainers(options dockertypes.ContainerListOptions) ([]dockertypes.Container, error) {
  236. f.Lock()
  237. defer f.Unlock()
  238. f.called = append(f.called, calledDetail{name: "list"})
  239. err := f.popError("list")
  240. containerList := append([]dockertypes.Container{}, f.RunningContainerList...)
  241. if options.All {
  242. // Although the container is not sorted, but the container with the same name should be in order,
  243. // that is enough for us now.
  244. // TODO(random-liu): Is a fully sorted array needed?
  245. containerList = append(containerList, f.ExitedContainerList...)
  246. }
  247. return containerList, err
  248. }
  249. // InspectContainer is a test-spy implementation of DockerInterface.InspectContainer.
  250. // It adds an entry "inspect" to the internal method call record.
  251. func (f *FakeDockerClient) InspectContainer(id string) (*dockertypes.ContainerJSON, error) {
  252. f.Lock()
  253. defer f.Unlock()
  254. f.called = append(f.called, calledDetail{name: "inspect_container"})
  255. err := f.popError("inspect_container")
  256. if container, ok := f.ContainerMap[id]; ok {
  257. return container, err
  258. }
  259. return nil, err
  260. }
  261. // InspectImage is a test-spy implementation of DockerInterface.InspectImage.
  262. // It adds an entry "inspect" to the internal method call record.
  263. func (f *FakeDockerClient) InspectImage(name string) (*dockertypes.ImageInspect, error) {
  264. f.Lock()
  265. defer f.Unlock()
  266. f.called = append(f.called, calledDetail{name: "inspect_image"})
  267. err := f.popError("inspect_image")
  268. return f.Image, err
  269. }
  270. // Sleeps random amount of time with the normal distribution with given mean and stddev
  271. // (in milliseconds), we never sleep less than cutOffMillis
  272. func (f *FakeDockerClient) normalSleep(mean, stdDev, cutOffMillis int) {
  273. if !f.EnableSleep {
  274. return
  275. }
  276. cutoff := (time.Duration)(cutOffMillis) * time.Millisecond
  277. delay := (time.Duration)(rand.NormFloat64()*float64(stdDev)+float64(mean)) * time.Millisecond
  278. if delay < cutoff {
  279. delay = cutoff
  280. }
  281. time.Sleep(delay)
  282. }
  283. // CreateContainer is a test-spy implementation of DockerInterface.CreateContainer.
  284. // It adds an entry "create" to the internal method call record.
  285. func (f *FakeDockerClient) CreateContainer(c dockertypes.ContainerCreateConfig) (*dockertypes.ContainerCreateResponse, error) {
  286. f.Lock()
  287. defer f.Unlock()
  288. f.called = append(f.called, calledDetail{name: "create"})
  289. if err := f.popError("create"); err != nil {
  290. return nil, err
  291. }
  292. // This is not a very good fake. We'll just add this container's name to the list.
  293. // Docker likes to add a '/', so copy that behavior.
  294. name := "/" + c.Name
  295. id := name
  296. f.Created = append(f.Created, name)
  297. // The newest container should be in front, because we assume so in GetPodStatus()
  298. f.RunningContainerList = append([]dockertypes.Container{
  299. {ID: name, Names: []string{name}, Image: c.Config.Image, Labels: c.Config.Labels},
  300. }, f.RunningContainerList...)
  301. f.ContainerMap[name] = convertFakeContainer(&FakeContainer{ID: id, Name: name, Config: c.Config, HostConfig: c.HostConfig})
  302. f.normalSleep(100, 25, 25)
  303. return &dockertypes.ContainerCreateResponse{ID: id}, nil
  304. }
  305. // StartContainer is a test-spy implementation of DockerInterface.StartContainer.
  306. // It adds an entry "start" to the internal method call record.
  307. func (f *FakeDockerClient) StartContainer(id string) error {
  308. f.Lock()
  309. defer f.Unlock()
  310. f.called = append(f.called, calledDetail{name: "start"})
  311. if err := f.popError("start"); err != nil {
  312. return err
  313. }
  314. f.Started = append(f.Started, id)
  315. container, ok := f.ContainerMap[id]
  316. if !ok {
  317. container = convertFakeContainer(&FakeContainer{ID: id, Name: id})
  318. }
  319. container.State.Running = true
  320. container.State.Pid = os.Getpid()
  321. container.State.StartedAt = dockerTimestampToString(time.Now())
  322. container.NetworkSettings.IPAddress = "2.3.4.5"
  323. f.ContainerMap[id] = container
  324. f.updateContainerStatus(id, statusRunningPrefix)
  325. f.normalSleep(200, 50, 50)
  326. return nil
  327. }
  328. // StopContainer is a test-spy implementation of DockerInterface.StopContainer.
  329. // It adds an entry "stop" to the internal method call record.
  330. func (f *FakeDockerClient) StopContainer(id string, timeout int) error {
  331. f.Lock()
  332. defer f.Unlock()
  333. f.called = append(f.called, calledDetail{name: "stop"})
  334. if err := f.popError("stop"); err != nil {
  335. return err
  336. }
  337. f.Stopped = append(f.Stopped, id)
  338. // Container status should be Updated before container moved to ExitedContainerList
  339. f.updateContainerStatus(id, statusExitedPrefix)
  340. var newList []dockertypes.Container
  341. for _, container := range f.RunningContainerList {
  342. if container.ID == id {
  343. // The newest exited container should be in front. Because we assume so in GetPodStatus()
  344. f.ExitedContainerList = append([]dockertypes.Container{container}, f.ExitedContainerList...)
  345. continue
  346. }
  347. newList = append(newList, container)
  348. }
  349. f.RunningContainerList = newList
  350. container, ok := f.ContainerMap[id]
  351. if !ok {
  352. container = convertFakeContainer(&FakeContainer{
  353. ID: id,
  354. Name: id,
  355. Running: false,
  356. StartedAt: time.Now().Add(-time.Second),
  357. FinishedAt: time.Now(),
  358. })
  359. } else {
  360. container.State.FinishedAt = dockerTimestampToString(time.Now())
  361. container.State.Running = false
  362. }
  363. f.ContainerMap[id] = container
  364. f.normalSleep(200, 50, 50)
  365. return nil
  366. }
  367. func (f *FakeDockerClient) RemoveContainer(id string, opts dockertypes.ContainerRemoveOptions) error {
  368. f.Lock()
  369. defer f.Unlock()
  370. f.called = append(f.called, calledDetail{name: "remove"})
  371. err := f.popError("remove")
  372. if err != nil {
  373. return err
  374. }
  375. for i := range f.ExitedContainerList {
  376. if f.ExitedContainerList[i].ID == id {
  377. delete(f.ContainerMap, id)
  378. f.ExitedContainerList = append(f.ExitedContainerList[:i], f.ExitedContainerList[i+1:]...)
  379. f.Removed = append(f.Removed, id)
  380. return nil
  381. }
  382. }
  383. // To be a good fake, report error if container is not stopped.
  384. return fmt.Errorf("container not stopped")
  385. }
  386. // Logs is a test-spy implementation of DockerInterface.Logs.
  387. // It adds an entry "logs" to the internal method call record.
  388. func (f *FakeDockerClient) Logs(id string, opts dockertypes.ContainerLogsOptions, sopts StreamOptions) error {
  389. f.Lock()
  390. defer f.Unlock()
  391. f.called = append(f.called, calledDetail{name: "logs"})
  392. return f.popError("logs")
  393. }
  394. // PullImage is a test-spy implementation of DockerInterface.PullImage.
  395. // It adds an entry "pull" to the internal method call record.
  396. func (f *FakeDockerClient) PullImage(image string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error {
  397. f.Lock()
  398. defer f.Unlock()
  399. f.called = append(f.called, calledDetail{name: "pull"})
  400. err := f.popError("pull")
  401. if err == nil {
  402. authJson, _ := json.Marshal(auth)
  403. f.pulled = append(f.pulled, fmt.Sprintf("%s using %s", image, string(authJson)))
  404. }
  405. return err
  406. }
  407. func (f *FakeDockerClient) Version() (*dockertypes.Version, error) {
  408. f.Lock()
  409. defer f.Unlock()
  410. return &f.VersionInfo, f.popError("version")
  411. }
  412. func (f *FakeDockerClient) Info() (*dockertypes.Info, error) {
  413. return &f.Information, nil
  414. }
  415. func (f *FakeDockerClient) CreateExec(id string, opts dockertypes.ExecConfig) (*dockertypes.ContainerExecCreateResponse, error) {
  416. f.Lock()
  417. defer f.Unlock()
  418. f.execCmd = opts.Cmd
  419. f.called = append(f.called, calledDetail{name: "create_exec"})
  420. return &dockertypes.ContainerExecCreateResponse{ID: "12345678"}, nil
  421. }
  422. func (f *FakeDockerClient) StartExec(startExec string, opts dockertypes.ExecStartCheck, sopts StreamOptions) error {
  423. f.Lock()
  424. defer f.Unlock()
  425. f.called = append(f.called, calledDetail{name: "start_exec"})
  426. return nil
  427. }
  428. func (f *FakeDockerClient) AttachToContainer(id string, opts dockertypes.ContainerAttachOptions, sopts StreamOptions) error {
  429. f.Lock()
  430. defer f.Unlock()
  431. f.called = append(f.called, calledDetail{name: "attach"})
  432. return nil
  433. }
  434. func (f *FakeDockerClient) InspectExec(id string) (*dockertypes.ContainerExecInspect, error) {
  435. return f.ExecInspect, f.popError("inspect_exec")
  436. }
  437. func (f *FakeDockerClient) ListImages(opts dockertypes.ImageListOptions) ([]dockertypes.Image, error) {
  438. f.called = append(f.called, calledDetail{name: "list_images"})
  439. err := f.popError("list_images")
  440. return f.Images, err
  441. }
  442. func (f *FakeDockerClient) RemoveImage(image string, opts dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDelete, error) {
  443. f.called = append(f.called, calledDetail{name: "remove_image", arguments: []interface{}{image, opts}})
  444. err := f.popError("remove_image")
  445. if err == nil {
  446. for i := range f.Images {
  447. if f.Images[i].ID == image {
  448. f.Images = append(f.Images[:i], f.Images[i+1:]...)
  449. break
  450. }
  451. }
  452. }
  453. return []dockertypes.ImageDelete{{Deleted: image}}, err
  454. }
  455. func (f *FakeDockerClient) InjectImages(images []dockertypes.Image) {
  456. f.Lock()
  457. defer f.Unlock()
  458. f.Images = append(f.Images, images...)
  459. }
  460. func (f *FakeDockerClient) updateContainerStatus(id, status string) {
  461. for i := range f.RunningContainerList {
  462. if f.RunningContainerList[i].ID == id {
  463. f.RunningContainerList[i].Status = status
  464. }
  465. }
  466. }
  467. func (f *FakeDockerClient) ResizeExecTTY(id string, height, width int) error {
  468. f.Lock()
  469. defer f.Unlock()
  470. f.called = append(f.called, calledDetail{name: "resize_exec"})
  471. return nil
  472. }
  473. func (f *FakeDockerClient) ResizeContainerTTY(id string, height, width int) error {
  474. f.Lock()
  475. defer f.Unlock()
  476. f.called = append(f.called, calledDetail{name: "resize_container"})
  477. return nil
  478. }
  479. // FakeDockerPuller is a stub implementation of DockerPuller.
  480. type FakeDockerPuller struct {
  481. sync.Mutex
  482. HasImages []string
  483. ImagesPulled []string
  484. // Every pull will return the first error here, and then reslice
  485. // to remove it. Will give nil errors if this slice is empty.
  486. ErrorsToInject []error
  487. }
  488. // Pull records the image pull attempt, and optionally injects an error.
  489. func (f *FakeDockerPuller) Pull(image string, secrets []api.Secret) (err error) {
  490. f.Lock()
  491. defer f.Unlock()
  492. f.ImagesPulled = append(f.ImagesPulled, image)
  493. if len(f.ErrorsToInject) > 0 {
  494. err = f.ErrorsToInject[0]
  495. f.ErrorsToInject = f.ErrorsToInject[1:]
  496. }
  497. return err
  498. }
  499. func (f *FakeDockerPuller) IsImagePresent(name string) (bool, error) {
  500. f.Lock()
  501. defer f.Unlock()
  502. if f.HasImages == nil {
  503. return true, nil
  504. }
  505. for _, s := range f.HasImages {
  506. if s == name {
  507. return true, nil
  508. }
  509. }
  510. return false, nil
  511. }
  512. func (f *FakeDockerClient) ImageHistory(id string) ([]dockertypes.ImageHistory, error) {
  513. f.Lock()
  514. defer f.Unlock()
  515. f.called = append(f.called, calledDetail{name: "image_history"})
  516. history := f.ImageHistoryMap[id]
  517. return history, nil
  518. }
  519. func (f *FakeDockerClient) InjectImageHistory(data map[string][]dockertypes.ImageHistory) {
  520. f.Lock()
  521. defer f.Unlock()
  522. f.ImageHistoryMap = data
  523. }
  524. // dockerTimestampToString converts the timestamp to string
  525. func dockerTimestampToString(t time.Time) string {
  526. return t.Format(time.RFC3339Nano)
  527. }