fake_client.go 25 KB

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