fake_client.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  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 libdocker
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "hash/fnv"
  18. "math/rand"
  19. "os"
  20. "reflect"
  21. "sort"
  22. "strconv"
  23. "strings"
  24. "sync"
  25. "time"
  26. dockertypes "github.com/docker/docker/api/types"
  27. dockercontainer "github.com/docker/docker/api/types/container"
  28. dockerimagetypes "github.com/docker/docker/api/types/image"
  29. "k8s.io/api/core/v1"
  30. "k8s.io/apimachinery/pkg/util/clock"
  31. )
  32. type CalledDetail struct {
  33. name string
  34. arguments []interface{}
  35. }
  36. // NewCalledDetail create a new call detail item.
  37. func NewCalledDetail(name string, arguments []interface{}) CalledDetail {
  38. return CalledDetail{name: name, arguments: arguments}
  39. }
  40. // FakeDockerClient is a simple fake docker client, so that kubelet can be run for testing without requiring a real docker setup.
  41. type FakeDockerClient struct {
  42. sync.Mutex
  43. Clock clock.Clock
  44. RunningContainerList []dockertypes.Container
  45. ExitedContainerList []dockertypes.Container
  46. ContainerMap map[string]*dockertypes.ContainerJSON
  47. ImageInspects map[string]*dockertypes.ImageInspect
  48. Images []dockertypes.ImageSummary
  49. ImageIDsNeedingAuth map[string]dockertypes.AuthConfig
  50. Errors map[string]error
  51. called []CalledDetail
  52. pulled []string
  53. EnableTrace bool
  54. RandGenerator *rand.Rand
  55. // Created, Started, Stopped and Removed all contain container docker ID
  56. Created []string
  57. Started []string
  58. Stopped []string
  59. Removed []string
  60. // Images pulled by ref (name or ID).
  61. ImagesPulled []string
  62. VersionInfo dockertypes.Version
  63. Information dockertypes.Info
  64. ExecInspect *dockertypes.ContainerExecInspect
  65. execCmd []string
  66. EnableSleep bool
  67. ImageHistoryMap map[string][]dockerimagetypes.HistoryResponseItem
  68. }
  69. const (
  70. // Notice that if someday we also have minimum docker version requirement, this should also be updated.
  71. fakeDockerVersion = "1.13.1"
  72. fakeImageSize = 1024
  73. // Docker prepends '/' to the container name.
  74. dockerNamePrefix = "/"
  75. )
  76. func NewFakeDockerClient() *FakeDockerClient {
  77. return &FakeDockerClient{
  78. // Docker's API version does not include the patch number.
  79. VersionInfo: dockertypes.Version{Version: fakeDockerVersion, APIVersion: strings.TrimSuffix(MinimumDockerAPIVersion, ".0")},
  80. Errors: make(map[string]error),
  81. ContainerMap: make(map[string]*dockertypes.ContainerJSON),
  82. Clock: clock.RealClock{},
  83. // default this to true, so that we trace calls, image pulls and container lifecycle
  84. EnableTrace: true,
  85. ImageInspects: make(map[string]*dockertypes.ImageInspect),
  86. ImageIDsNeedingAuth: make(map[string]dockertypes.AuthConfig),
  87. RandGenerator: rand.New(rand.NewSource(time.Now().UnixNano())),
  88. }
  89. }
  90. func (f *FakeDockerClient) WithClock(c clock.Clock) *FakeDockerClient {
  91. f.Lock()
  92. defer f.Unlock()
  93. f.Clock = c
  94. return f
  95. }
  96. func (f *FakeDockerClient) WithVersion(version, apiVersion string) *FakeDockerClient {
  97. f.Lock()
  98. defer f.Unlock()
  99. f.VersionInfo = dockertypes.Version{Version: version, APIVersion: apiVersion}
  100. return f
  101. }
  102. func (f *FakeDockerClient) WithTraceDisabled() *FakeDockerClient {
  103. f.Lock()
  104. defer f.Unlock()
  105. f.EnableTrace = false
  106. return f
  107. }
  108. func (f *FakeDockerClient) WithRandSource(source rand.Source) *FakeDockerClient {
  109. f.Lock()
  110. defer f.Unlock()
  111. f.RandGenerator = rand.New(source)
  112. return f
  113. }
  114. func (f *FakeDockerClient) appendCalled(callDetail CalledDetail) {
  115. if f.EnableTrace {
  116. f.called = append(f.called, callDetail)
  117. }
  118. }
  119. func (f *FakeDockerClient) appendPulled(pull string) {
  120. if f.EnableTrace {
  121. f.pulled = append(f.pulled, pull)
  122. }
  123. }
  124. func (f *FakeDockerClient) appendContainerTrace(traceCategory string, containerName string) {
  125. if !f.EnableTrace {
  126. return
  127. }
  128. switch traceCategory {
  129. case "Created":
  130. f.Created = append(f.Created, containerName)
  131. case "Started":
  132. f.Started = append(f.Started, containerName)
  133. case "Stopped":
  134. f.Stopped = append(f.Stopped, containerName)
  135. case "Removed":
  136. f.Removed = append(f.Removed, containerName)
  137. }
  138. }
  139. func (f *FakeDockerClient) InjectError(fn string, err error) {
  140. f.Lock()
  141. defer f.Unlock()
  142. f.Errors[fn] = err
  143. }
  144. func (f *FakeDockerClient) InjectErrors(errs map[string]error) {
  145. f.Lock()
  146. defer f.Unlock()
  147. for fn, err := range errs {
  148. f.Errors[fn] = err
  149. }
  150. }
  151. func (f *FakeDockerClient) ClearErrors() {
  152. f.Lock()
  153. defer f.Unlock()
  154. f.Errors = map[string]error{}
  155. }
  156. func (f *FakeDockerClient) ClearCalls() {
  157. f.Lock()
  158. defer f.Unlock()
  159. f.called = []CalledDetail{}
  160. f.pulled = []string{}
  161. f.Created = []string{}
  162. f.Started = []string{}
  163. f.Stopped = []string{}
  164. f.Removed = []string{}
  165. }
  166. func (f *FakeDockerClient) getCalledNames() []string {
  167. names := []string{}
  168. for _, detail := range f.called {
  169. names = append(names, detail.name)
  170. }
  171. return names
  172. }
  173. // Because the new data type returned by engine-api is too complex to manually initialize, we need a
  174. // fake container which is easier to initialize.
  175. type FakeContainer struct {
  176. ID string
  177. Name string
  178. Running bool
  179. ExitCode int
  180. Pid int
  181. CreatedAt time.Time
  182. StartedAt time.Time
  183. FinishedAt time.Time
  184. Config *dockercontainer.Config
  185. HostConfig *dockercontainer.HostConfig
  186. }
  187. // convertFakeContainer converts the fake container to real container
  188. func convertFakeContainer(f *FakeContainer) *dockertypes.ContainerJSON {
  189. if f.Config == nil {
  190. f.Config = &dockercontainer.Config{}
  191. }
  192. if f.HostConfig == nil {
  193. f.HostConfig = &dockercontainer.HostConfig{}
  194. }
  195. return &dockertypes.ContainerJSON{
  196. ContainerJSONBase: &dockertypes.ContainerJSONBase{
  197. ID: f.ID,
  198. Name: f.Name,
  199. Image: f.Config.Image,
  200. State: &dockertypes.ContainerState{
  201. Running: f.Running,
  202. ExitCode: f.ExitCode,
  203. Pid: f.Pid,
  204. StartedAt: dockerTimestampToString(f.StartedAt),
  205. FinishedAt: dockerTimestampToString(f.FinishedAt),
  206. },
  207. Created: dockerTimestampToString(f.CreatedAt),
  208. HostConfig: f.HostConfig,
  209. },
  210. Config: f.Config,
  211. NetworkSettings: &dockertypes.NetworkSettings{},
  212. }
  213. }
  214. func (f *FakeDockerClient) SetFakeContainers(containers []*FakeContainer) {
  215. f.Lock()
  216. defer f.Unlock()
  217. // Reset the lists and the map.
  218. f.ContainerMap = map[string]*dockertypes.ContainerJSON{}
  219. f.RunningContainerList = []dockertypes.Container{}
  220. f.ExitedContainerList = []dockertypes.Container{}
  221. for i := range containers {
  222. c := containers[i]
  223. f.ContainerMap[c.ID] = convertFakeContainer(c)
  224. container := dockertypes.Container{
  225. Names: []string{c.Name},
  226. ID: c.ID,
  227. }
  228. if c.Config != nil {
  229. container.Labels = c.Config.Labels
  230. }
  231. if c.Running {
  232. f.RunningContainerList = append(f.RunningContainerList, container)
  233. } else {
  234. f.ExitedContainerList = append(f.ExitedContainerList, container)
  235. }
  236. }
  237. }
  238. func (f *FakeDockerClient) SetFakeRunningContainers(containers []*FakeContainer) {
  239. for _, c := range containers {
  240. c.Running = true
  241. }
  242. f.SetFakeContainers(containers)
  243. }
  244. func (f *FakeDockerClient) AssertCalls(calls []string) (err error) {
  245. f.Lock()
  246. defer f.Unlock()
  247. if !reflect.DeepEqual(calls, f.getCalledNames()) {
  248. err = fmt.Errorf("expected %#v, got %#v", calls, f.getCalledNames())
  249. }
  250. return
  251. }
  252. func (f *FakeDockerClient) AssertCallDetails(calls ...CalledDetail) (err error) {
  253. f.Lock()
  254. defer f.Unlock()
  255. if !reflect.DeepEqual(calls, f.called) {
  256. err = fmt.Errorf("expected %#v, got %#v", calls, f.called)
  257. }
  258. return
  259. }
  260. // idsToNames converts container ids into names. The caller must hold the lock.
  261. func (f *FakeDockerClient) idsToNames(ids []string) ([]string, error) {
  262. names := []string{}
  263. for _, id := range ids {
  264. names = append(names, strings.TrimPrefix(f.ContainerMap[id].Name, dockerNamePrefix))
  265. }
  266. return names, nil
  267. }
  268. func (f *FakeDockerClient) AssertCreatedByNameWithOrder(created []string) error {
  269. f.Lock()
  270. defer f.Unlock()
  271. actualCreated, err := f.idsToNames(f.Created)
  272. if err != nil {
  273. return err
  274. }
  275. if !reflect.DeepEqual(created, actualCreated) {
  276. return fmt.Errorf("expected %#v, got %#v", created, actualCreated)
  277. }
  278. return nil
  279. }
  280. func (f *FakeDockerClient) AssertCreatedByName(created []string) error {
  281. f.Lock()
  282. defer f.Unlock()
  283. actualCreated, err := f.idsToNames(f.Created)
  284. if err != nil {
  285. return err
  286. }
  287. return sortedStringSlicesEqual(created, actualCreated)
  288. }
  289. func (f *FakeDockerClient) AssertStoppedByName(stopped []string) error {
  290. f.Lock()
  291. defer f.Unlock()
  292. actualStopped, err := f.idsToNames(f.Stopped)
  293. if err != nil {
  294. return err
  295. }
  296. return sortedStringSlicesEqual(stopped, actualStopped)
  297. }
  298. func (f *FakeDockerClient) AssertStopped(stopped []string) error {
  299. f.Lock()
  300. defer f.Unlock()
  301. // Copy stopped to avoid modifying it.
  302. actualStopped := append([]string{}, f.Stopped...)
  303. return sortedStringSlicesEqual(stopped, actualStopped)
  304. }
  305. func (f *FakeDockerClient) AssertImagesPulled(pulled []string) error {
  306. f.Lock()
  307. defer f.Unlock()
  308. // Copy pulled to avoid modifying it.
  309. actualPulled := append([]string{}, f.ImagesPulled...)
  310. return sortedStringSlicesEqual(pulled, actualPulled)
  311. }
  312. func (f *FakeDockerClient) AssertImagesPulledMsgs(expected []string) error {
  313. f.Lock()
  314. defer f.Unlock()
  315. // Copy pulled to avoid modifying it.
  316. actual := append([]string{}, f.pulled...)
  317. return sortedStringSlicesEqual(expected, actual)
  318. }
  319. func sortedStringSlicesEqual(expected, actual []string) error {
  320. sort.StringSlice(expected).Sort()
  321. sort.StringSlice(actual).Sort()
  322. if !reflect.DeepEqual(expected, actual) {
  323. return fmt.Errorf("expected %#v, got %#v", expected, actual)
  324. }
  325. return nil
  326. }
  327. func (f *FakeDockerClient) popError(op string) error {
  328. if f.Errors == nil {
  329. return nil
  330. }
  331. err, ok := f.Errors[op]
  332. if ok {
  333. delete(f.Errors, op)
  334. return err
  335. }
  336. return nil
  337. }
  338. // ListContainers is a test-spy implementation of Interface.ListContainers.
  339. // It adds an entry "list" to the internal method call record.
  340. func (f *FakeDockerClient) ListContainers(options dockertypes.ContainerListOptions) ([]dockertypes.Container, error) {
  341. f.Lock()
  342. defer f.Unlock()
  343. f.appendCalled(CalledDetail{name: "list"})
  344. err := f.popError("list")
  345. containerList := append([]dockertypes.Container{}, f.RunningContainerList...)
  346. if options.All {
  347. // Although the container is not sorted, but the container with the same name should be in order,
  348. // that is enough for us now.
  349. // TODO(random-liu): Is a fully sorted array needed?
  350. containerList = append(containerList, f.ExitedContainerList...)
  351. }
  352. // Filters containers with id, only support 1 id.
  353. idFilters := options.Filters.Get("id")
  354. if len(idFilters) != 0 {
  355. var filtered []dockertypes.Container
  356. for _, container := range containerList {
  357. for _, idFilter := range idFilters {
  358. if container.ID == idFilter {
  359. filtered = append(filtered, container)
  360. break
  361. }
  362. }
  363. }
  364. containerList = filtered
  365. }
  366. // Filters containers with status, only support 1 status.
  367. statusFilters := options.Filters.Get("status")
  368. if len(statusFilters) == 1 {
  369. var filtered []dockertypes.Container
  370. for _, container := range containerList {
  371. for _, statusFilter := range statusFilters {
  372. if toDockerContainerStatus(container.Status) == statusFilter {
  373. filtered = append(filtered, container)
  374. break
  375. }
  376. }
  377. }
  378. containerList = filtered
  379. }
  380. // Filters containers with label filter.
  381. labelFilters := options.Filters.Get("label")
  382. if len(labelFilters) != 0 {
  383. var filtered []dockertypes.Container
  384. for _, container := range containerList {
  385. match := true
  386. for _, labelFilter := range labelFilters {
  387. kv := strings.Split(labelFilter, "=")
  388. if len(kv) != 2 {
  389. return nil, fmt.Errorf("invalid label filter %q", labelFilter)
  390. }
  391. if container.Labels[kv[0]] != kv[1] {
  392. match = false
  393. break
  394. }
  395. }
  396. if match {
  397. filtered = append(filtered, container)
  398. }
  399. }
  400. containerList = filtered
  401. }
  402. return containerList, err
  403. }
  404. func toDockerContainerStatus(state string) string {
  405. switch {
  406. case strings.HasPrefix(state, StatusCreatedPrefix):
  407. return "created"
  408. case strings.HasPrefix(state, StatusRunningPrefix):
  409. return "running"
  410. case strings.HasPrefix(state, StatusExitedPrefix):
  411. return "exited"
  412. default:
  413. return "unknown"
  414. }
  415. }
  416. // InspectContainer is a test-spy implementation of Interface.InspectContainer.
  417. // It adds an entry "inspect" to the internal method call record.
  418. func (f *FakeDockerClient) InspectContainer(id string) (*dockertypes.ContainerJSON, error) {
  419. f.Lock()
  420. defer f.Unlock()
  421. f.appendCalled(CalledDetail{name: "inspect_container"})
  422. err := f.popError("inspect_container")
  423. if container, ok := f.ContainerMap[id]; ok {
  424. return container, err
  425. }
  426. if err != nil {
  427. // Use the custom error if it exists.
  428. return nil, err
  429. }
  430. return nil, fmt.Errorf("container %q not found", id)
  431. }
  432. // InspectContainerWithSize is a test-spy implementation of Interface.InspectContainerWithSize.
  433. // It adds an entry "inspect" to the internal method call record.
  434. func (f *FakeDockerClient) InspectContainerWithSize(id string) (*dockertypes.ContainerJSON, error) {
  435. f.Lock()
  436. defer f.Unlock()
  437. f.appendCalled(CalledDetail{name: "inspect_container_withsize"})
  438. err := f.popError("inspect_container_withsize")
  439. if container, ok := f.ContainerMap[id]; ok {
  440. return container, err
  441. }
  442. if err != nil {
  443. // Use the custom error if it exists.
  444. return nil, err
  445. }
  446. return nil, fmt.Errorf("container %q not found", id)
  447. }
  448. // InspectImageByRef is a test-spy implementation of Interface.InspectImageByRef.
  449. // It adds an entry "inspect" to the internal method call record.
  450. func (f *FakeDockerClient) InspectImageByRef(name string) (*dockertypes.ImageInspect, error) {
  451. f.Lock()
  452. defer f.Unlock()
  453. f.appendCalled(CalledDetail{name: "inspect_image"})
  454. if err := f.popError("inspect_image"); err != nil {
  455. return nil, err
  456. }
  457. if result, ok := f.ImageInspects[name]; ok {
  458. return result, nil
  459. }
  460. return nil, ImageNotFoundError{name}
  461. }
  462. // InspectImageByID is a test-spy implementation of Interface.InspectImageByID.
  463. // It adds an entry "inspect" to the internal method call record.
  464. func (f *FakeDockerClient) InspectImageByID(name string) (*dockertypes.ImageInspect, error) {
  465. f.Lock()
  466. defer f.Unlock()
  467. f.appendCalled(CalledDetail{name: "inspect_image"})
  468. if err := f.popError("inspect_image"); err != nil {
  469. return nil, err
  470. }
  471. if result, ok := f.ImageInspects[name]; ok {
  472. return result, nil
  473. }
  474. return nil, ImageNotFoundError{name}
  475. }
  476. // Sleeps random amount of time with the normal distribution with given mean and stddev
  477. // (in milliseconds), we never sleep less than cutOffMillis
  478. func (f *FakeDockerClient) normalSleep(mean, stdDev, cutOffMillis int) {
  479. if !f.EnableSleep {
  480. return
  481. }
  482. cutoff := (time.Duration)(cutOffMillis) * time.Millisecond
  483. delay := (time.Duration)(rand.NormFloat64()*float64(stdDev)+float64(mean)) * time.Millisecond
  484. if delay < cutoff {
  485. delay = cutoff
  486. }
  487. time.Sleep(delay)
  488. }
  489. // GetFakeContainerID generates a fake container id from container name with a hash.
  490. func GetFakeContainerID(name string) string {
  491. hash := fnv.New64a()
  492. hash.Write([]byte(name))
  493. return strconv.FormatUint(hash.Sum64(), 16)
  494. }
  495. // CreateContainer is a test-spy implementation of Interface.CreateContainer.
  496. // It adds an entry "create" to the internal method call record.
  497. func (f *FakeDockerClient) CreateContainer(c dockertypes.ContainerCreateConfig) (*dockercontainer.ContainerCreateCreatedBody, error) {
  498. f.Lock()
  499. defer f.Unlock()
  500. f.appendCalled(CalledDetail{name: "create"})
  501. if err := f.popError("create"); err != nil {
  502. return nil, err
  503. }
  504. // This is not a very good fake. We'll just add this container's name to the list.
  505. name := dockerNamePrefix + c.Name
  506. id := GetFakeContainerID(name)
  507. f.appendContainerTrace("Created", id)
  508. timestamp := f.Clock.Now()
  509. // The newest container should be in front, because we assume so in GetPodStatus()
  510. f.RunningContainerList = append([]dockertypes.Container{
  511. {ID: id, Names: []string{name}, Image: c.Config.Image, Created: timestamp.Unix(), State: StatusCreatedPrefix, Labels: c.Config.Labels},
  512. }, f.RunningContainerList...)
  513. f.ContainerMap[id] = convertFakeContainer(&FakeContainer{
  514. ID: id, Name: name, Config: c.Config, HostConfig: c.HostConfig, CreatedAt: timestamp})
  515. f.normalSleep(100, 25, 25)
  516. return &dockercontainer.ContainerCreateCreatedBody{ID: id}, nil
  517. }
  518. // StartContainer is a test-spy implementation of Interface.StartContainer.
  519. // It adds an entry "start" to the internal method call record.
  520. func (f *FakeDockerClient) StartContainer(id string) error {
  521. f.Lock()
  522. defer f.Unlock()
  523. f.appendCalled(CalledDetail{name: "start"})
  524. if err := f.popError("start"); err != nil {
  525. return err
  526. }
  527. f.appendContainerTrace("Started", id)
  528. container, ok := f.ContainerMap[id]
  529. if container.HostConfig.NetworkMode.IsContainer() {
  530. hostContainerID := container.HostConfig.NetworkMode.ConnectedContainer()
  531. found := false
  532. for _, container := range f.RunningContainerList {
  533. if container.ID == hostContainerID {
  534. found = true
  535. }
  536. }
  537. if !found {
  538. return fmt.Errorf("failed to start container \"%s\": Error response from daemon: cannot join network of a non running container: %s", id, hostContainerID)
  539. }
  540. }
  541. timestamp := f.Clock.Now()
  542. if !ok {
  543. container = convertFakeContainer(&FakeContainer{ID: id, Name: id, CreatedAt: timestamp})
  544. }
  545. container.State.Running = true
  546. container.State.Pid = os.Getpid()
  547. container.State.StartedAt = dockerTimestampToString(timestamp)
  548. r := f.RandGenerator.Uint32()
  549. container.NetworkSettings.IPAddress = fmt.Sprintf("10.%d.%d.%d", byte(r>>16), byte(r>>8), byte(r))
  550. f.ContainerMap[id] = container
  551. f.updateContainerStatus(id, StatusRunningPrefix)
  552. f.normalSleep(200, 50, 50)
  553. return nil
  554. }
  555. // StopContainer is a test-spy implementation of Interface.StopContainer.
  556. // It adds an entry "stop" to the internal method call record.
  557. func (f *FakeDockerClient) StopContainer(id string, timeout time.Duration) error {
  558. f.Lock()
  559. defer f.Unlock()
  560. f.appendCalled(CalledDetail{name: "stop"})
  561. if err := f.popError("stop"); err != nil {
  562. return err
  563. }
  564. f.appendContainerTrace("Stopped", id)
  565. // Container status should be Updated before container moved to ExitedContainerList
  566. f.updateContainerStatus(id, StatusExitedPrefix)
  567. var newList []dockertypes.Container
  568. for _, container := range f.RunningContainerList {
  569. if container.ID == id {
  570. // The newest exited container should be in front. Because we assume so in GetPodStatus()
  571. f.ExitedContainerList = append([]dockertypes.Container{container}, f.ExitedContainerList...)
  572. continue
  573. }
  574. newList = append(newList, container)
  575. }
  576. f.RunningContainerList = newList
  577. container, ok := f.ContainerMap[id]
  578. if !ok {
  579. container = convertFakeContainer(&FakeContainer{
  580. ID: id,
  581. Name: id,
  582. Running: false,
  583. StartedAt: time.Now().Add(-time.Second),
  584. FinishedAt: time.Now(),
  585. })
  586. } else {
  587. container.State.FinishedAt = dockerTimestampToString(f.Clock.Now())
  588. container.State.Running = false
  589. }
  590. f.ContainerMap[id] = container
  591. f.normalSleep(200, 50, 50)
  592. return nil
  593. }
  594. func (f *FakeDockerClient) RemoveContainer(id string, opts dockertypes.ContainerRemoveOptions) error {
  595. f.Lock()
  596. defer f.Unlock()
  597. f.appendCalled(CalledDetail{name: "remove"})
  598. err := f.popError("remove")
  599. if err != nil {
  600. return err
  601. }
  602. for i := range f.ExitedContainerList {
  603. if f.ExitedContainerList[i].ID == id {
  604. delete(f.ContainerMap, id)
  605. f.ExitedContainerList = append(f.ExitedContainerList[:i], f.ExitedContainerList[i+1:]...)
  606. f.appendContainerTrace("Removed", id)
  607. return nil
  608. }
  609. }
  610. for i := range f.RunningContainerList {
  611. // allow removal of running containers which are not running
  612. if f.RunningContainerList[i].ID == id && !f.ContainerMap[id].State.Running {
  613. delete(f.ContainerMap, id)
  614. f.RunningContainerList = append(f.RunningContainerList[:i], f.RunningContainerList[i+1:]...)
  615. f.appendContainerTrace("Removed", id)
  616. return nil
  617. }
  618. }
  619. // To be a good fake, report error if container is not stopped.
  620. return fmt.Errorf("container not stopped")
  621. }
  622. func (f *FakeDockerClient) UpdateContainerResources(id string, updateConfig dockercontainer.UpdateConfig) error {
  623. return nil
  624. }
  625. // Logs is a test-spy implementation of Interface.Logs.
  626. // It adds an entry "logs" to the internal method call record.
  627. func (f *FakeDockerClient) Logs(id string, opts dockertypes.ContainerLogsOptions, sopts StreamOptions) error {
  628. f.Lock()
  629. defer f.Unlock()
  630. f.appendCalled(CalledDetail{name: "logs"})
  631. return f.popError("logs")
  632. }
  633. func (f *FakeDockerClient) isAuthorizedForImage(image string, auth dockertypes.AuthConfig) bool {
  634. if reqd, exists := f.ImageIDsNeedingAuth[image]; !exists {
  635. return true // no auth needed
  636. } else {
  637. return auth.Username == reqd.Username && auth.Password == reqd.Password
  638. }
  639. }
  640. // PullImage is a test-spy implementation of Interface.PullImage.
  641. // It adds an entry "pull" to the internal method call record.
  642. func (f *FakeDockerClient) PullImage(image string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error {
  643. f.Lock()
  644. defer f.Unlock()
  645. f.appendCalled(CalledDetail{name: "pull"})
  646. err := f.popError("pull")
  647. if err == nil {
  648. if !f.isAuthorizedForImage(image, auth) {
  649. return ImageNotFoundError{ID: image}
  650. }
  651. authJson, _ := json.Marshal(auth)
  652. inspect := createImageInspectFromRef(image)
  653. f.ImageInspects[image] = inspect
  654. f.appendPulled(fmt.Sprintf("%s using %s", image, string(authJson)))
  655. f.Images = append(f.Images, *createImageFromImageInspect(*inspect))
  656. f.ImagesPulled = append(f.ImagesPulled, image)
  657. }
  658. return err
  659. }
  660. func (f *FakeDockerClient) Version() (*dockertypes.Version, error) {
  661. f.Lock()
  662. defer f.Unlock()
  663. v := f.VersionInfo
  664. return &v, f.popError("version")
  665. }
  666. func (f *FakeDockerClient) Info() (*dockertypes.Info, error) {
  667. return &f.Information, nil
  668. }
  669. func (f *FakeDockerClient) CreateExec(id string, opts dockertypes.ExecConfig) (*dockertypes.IDResponse, error) {
  670. f.Lock()
  671. defer f.Unlock()
  672. f.execCmd = opts.Cmd
  673. f.appendCalled(CalledDetail{name: "create_exec"})
  674. return &dockertypes.IDResponse{ID: "12345678"}, nil
  675. }
  676. func (f *FakeDockerClient) StartExec(startExec string, opts dockertypes.ExecStartCheck, sopts StreamOptions) error {
  677. f.Lock()
  678. defer f.Unlock()
  679. f.appendCalled(CalledDetail{name: "start_exec"})
  680. return nil
  681. }
  682. func (f *FakeDockerClient) AttachToContainer(id string, opts dockertypes.ContainerAttachOptions, sopts StreamOptions) error {
  683. f.Lock()
  684. defer f.Unlock()
  685. f.appendCalled(CalledDetail{name: "attach"})
  686. return nil
  687. }
  688. func (f *FakeDockerClient) InspectExec(id string) (*dockertypes.ContainerExecInspect, error) {
  689. return f.ExecInspect, f.popError("inspect_exec")
  690. }
  691. func (f *FakeDockerClient) ListImages(opts dockertypes.ImageListOptions) ([]dockertypes.ImageSummary, error) {
  692. f.Lock()
  693. defer f.Unlock()
  694. f.appendCalled(CalledDetail{name: "list_images"})
  695. err := f.popError("list_images")
  696. return f.Images, err
  697. }
  698. func (f *FakeDockerClient) RemoveImage(image string, opts dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDeleteResponseItem, error) {
  699. f.Lock()
  700. defer f.Unlock()
  701. f.appendCalled(CalledDetail{name: "remove_image", arguments: []interface{}{image, opts}})
  702. err := f.popError("remove_image")
  703. if err == nil {
  704. for i := range f.Images {
  705. if f.Images[i].ID == image {
  706. f.Images = append(f.Images[:i], f.Images[i+1:]...)
  707. break
  708. }
  709. }
  710. }
  711. return []dockertypes.ImageDeleteResponseItem{{Deleted: image}}, err
  712. }
  713. func (f *FakeDockerClient) InjectImages(images []dockertypes.ImageSummary) {
  714. f.Lock()
  715. defer f.Unlock()
  716. f.Images = append(f.Images, images...)
  717. for _, i := range images {
  718. f.ImageInspects[i.ID] = createImageInspectFromImage(i)
  719. }
  720. }
  721. func (f *FakeDockerClient) MakeImagesPrivate(images []dockertypes.ImageSummary, auth dockertypes.AuthConfig) {
  722. f.Lock()
  723. defer f.Unlock()
  724. for _, i := range images {
  725. f.ImageIDsNeedingAuth[i.ID] = auth
  726. }
  727. }
  728. func (f *FakeDockerClient) ResetImages() {
  729. f.Lock()
  730. defer f.Unlock()
  731. f.Images = []dockertypes.ImageSummary{}
  732. f.ImageInspects = make(map[string]*dockertypes.ImageInspect)
  733. f.ImageIDsNeedingAuth = make(map[string]dockertypes.AuthConfig)
  734. }
  735. func (f *FakeDockerClient) InjectImageInspects(inspects []dockertypes.ImageInspect) {
  736. f.Lock()
  737. defer f.Unlock()
  738. for _, i := range inspects {
  739. f.Images = append(f.Images, *createImageFromImageInspect(i))
  740. f.ImageInspects[i.ID] = &i
  741. }
  742. }
  743. func (f *FakeDockerClient) updateContainerStatus(id, status string) {
  744. for i := range f.RunningContainerList {
  745. if f.RunningContainerList[i].ID == id {
  746. f.RunningContainerList[i].Status = status
  747. }
  748. }
  749. }
  750. func (f *FakeDockerClient) ResizeExecTTY(id string, height, width uint) error {
  751. f.Lock()
  752. defer f.Unlock()
  753. f.appendCalled(CalledDetail{name: "resize_exec"})
  754. return nil
  755. }
  756. func (f *FakeDockerClient) ResizeContainerTTY(id string, height, width uint) error {
  757. f.Lock()
  758. defer f.Unlock()
  759. f.appendCalled(CalledDetail{name: "resize_container"})
  760. return nil
  761. }
  762. func createImageInspectFromRef(ref string) *dockertypes.ImageInspect {
  763. return &dockertypes.ImageInspect{
  764. ID: ref,
  765. RepoTags: []string{ref},
  766. // Image size is required to be non-zero for CRI integration.
  767. VirtualSize: fakeImageSize,
  768. Size: fakeImageSize,
  769. Config: &dockercontainer.Config{},
  770. }
  771. }
  772. func createImageInspectFromImage(image dockertypes.ImageSummary) *dockertypes.ImageInspect {
  773. return &dockertypes.ImageInspect{
  774. ID: image.ID,
  775. RepoTags: image.RepoTags,
  776. // Image size is required to be non-zero for CRI integration.
  777. VirtualSize: fakeImageSize,
  778. Size: fakeImageSize,
  779. Config: &dockercontainer.Config{},
  780. }
  781. }
  782. func createImageFromImageInspect(inspect dockertypes.ImageInspect) *dockertypes.ImageSummary {
  783. return &dockertypes.ImageSummary{
  784. ID: inspect.ID,
  785. RepoTags: inspect.RepoTags,
  786. // Image size is required to be non-zero for CRI integration.
  787. VirtualSize: fakeImageSize,
  788. Size: fakeImageSize,
  789. }
  790. }
  791. // dockerTimestampToString converts the timestamp to string
  792. func dockerTimestampToString(t time.Time) string {
  793. return t.Format(time.RFC3339Nano)
  794. }
  795. func (f *FakeDockerClient) ImageHistory(id string) ([]dockerimagetypes.HistoryResponseItem, error) {
  796. f.Lock()
  797. defer f.Unlock()
  798. f.appendCalled(CalledDetail{name: "image_history"})
  799. history := f.ImageHistoryMap[id]
  800. return history, nil
  801. }
  802. func (f *FakeDockerClient) InjectImageHistory(data map[string][]dockerimagetypes.HistoryResponseItem) {
  803. f.Lock()
  804. defer f.Unlock()
  805. f.ImageHistoryMap = data
  806. }
  807. // FakeDockerPuller is meant to be a simple wrapper around FakeDockerClient.
  808. // Please do not add more functionalities to it.
  809. type FakeDockerPuller struct {
  810. client Interface
  811. }
  812. func (f *FakeDockerPuller) Pull(image string, _ []v1.Secret) error {
  813. return f.client.PullImage(image, dockertypes.AuthConfig{}, dockertypes.ImagePullOptions{})
  814. }
  815. func (f *FakeDockerPuller) GetImageRef(image string) (string, error) {
  816. _, err := f.client.InspectImageByRef(image)
  817. if err != nil && IsImageNotFoundError(err) {
  818. return "", nil
  819. }
  820. return image, err
  821. }
  822. func (f *FakeDockerClient) GetContainerStats(id string) (*dockertypes.StatsJSON, error) {
  823. f.Lock()
  824. defer f.Unlock()
  825. f.appendCalled(CalledDetail{name: "getContainerStats"})
  826. return nil, fmt.Errorf("not implemented")
  827. }