plugin_test.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package plugins // import "github.com/docker/docker/pkg/plugins"
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "io"
  6. "net/http"
  7. "os"
  8. "path/filepath"
  9. "runtime"
  10. "sync"
  11. "testing"
  12. "time"
  13. "github.com/docker/docker/pkg/plugins/transport"
  14. "github.com/docker/go-connections/tlsconfig"
  15. "github.com/pkg/errors"
  16. "gotest.tools/v3/assert"
  17. )
  18. const (
  19. fruitPlugin = "fruit"
  20. fruitImplements = "apple"
  21. )
  22. // regression test for deadlock in handlers
  23. func TestPluginAddHandler(t *testing.T) {
  24. t.Parallel()
  25. // make a plugin which is pre-activated
  26. p := &Plugin{activateWait: sync.NewCond(&sync.Mutex{})}
  27. p.Manifest = &Manifest{Implements: []string{"bananas"}}
  28. storage.Lock()
  29. storage.plugins["qwerty"] = p
  30. storage.Unlock()
  31. testActive(t, p)
  32. Handle("bananas", func(_ string, _ *Client) {})
  33. testActive(t, p)
  34. }
  35. func TestPluginWaitBadPlugin(t *testing.T) {
  36. p := &Plugin{activateWait: sync.NewCond(&sync.Mutex{})}
  37. p.activateErr = errors.New("some junk happened")
  38. testActive(t, p)
  39. }
  40. func testActive(t *testing.T, p *Plugin) {
  41. done := make(chan struct{})
  42. go func() {
  43. p.waitActive()
  44. close(done)
  45. }()
  46. select {
  47. case <-time.After(100 * time.Millisecond):
  48. _, f, l, _ := runtime.Caller(1)
  49. t.Fatalf("%s:%d: deadlock in waitActive", filepath.Base(f), l)
  50. case <-done:
  51. }
  52. }
  53. func TestGet(t *testing.T) {
  54. // TODO: t.Parallel()
  55. // TestPluginWithNoManifest also registers fruitPlugin
  56. p := &Plugin{name: fruitPlugin, activateWait: sync.NewCond(&sync.Mutex{})}
  57. p.Manifest = &Manifest{Implements: []string{fruitImplements}}
  58. storage.Lock()
  59. storage.plugins[fruitPlugin] = p
  60. storage.Unlock()
  61. t.Run("success", func(t *testing.T) {
  62. plugin, err := Get(fruitPlugin, fruitImplements)
  63. if err != nil {
  64. t.Fatal(err)
  65. }
  66. if p.Name() != plugin.Name() {
  67. t.Errorf("no matching plugin with name %s found", plugin.Name())
  68. }
  69. if plugin.Client() != nil {
  70. t.Error("expected nil Client but found one")
  71. }
  72. if !plugin.IsV1() {
  73. t.Error("Expected true for V1 plugin")
  74. }
  75. })
  76. // check negative case where plugin fruit doesn't implement banana
  77. t.Run("not implemented", func(t *testing.T) {
  78. _, err := Get("fruit", "banana")
  79. assert.Assert(t, errors.Is(err, ErrNotImplements))
  80. })
  81. // check negative case where plugin vegetable doesn't exist
  82. t.Run("not exists", func(t *testing.T) {
  83. _, err := Get(testNonExistingPlugin, "no-such-implementation")
  84. assert.Assert(t, errors.Is(err, ErrNotFound))
  85. })
  86. }
  87. func TestPluginWithNoManifest(t *testing.T) {
  88. // TODO: t.Parallel()
  89. // TestGet also registers fruitPlugin
  90. mux, addr := setupRemotePluginServer(t)
  91. m := Manifest{[]string{fruitImplements}}
  92. var buf bytes.Buffer
  93. if err := json.NewEncoder(&buf).Encode(m); err != nil {
  94. t.Fatal(err)
  95. }
  96. mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
  97. if r.Method != http.MethodPost {
  98. t.Fatalf("Expected POST, got %s\n", r.Method)
  99. }
  100. header := w.Header()
  101. header.Set("Content-Type", transport.VersionMimetype)
  102. io.Copy(w, &buf)
  103. })
  104. p := &Plugin{
  105. name: fruitPlugin,
  106. activateWait: sync.NewCond(&sync.Mutex{}),
  107. Addr: addr,
  108. TLSConfig: &tlsconfig.Options{InsecureSkipVerify: true},
  109. }
  110. storage.Lock()
  111. storage.plugins[fruitPlugin] = p
  112. storage.Unlock()
  113. plugin, err := Get(fruitPlugin, fruitImplements)
  114. if err != nil {
  115. t.Fatal(err)
  116. }
  117. if p.Name() != plugin.Name() {
  118. t.Fatalf("No matching plugin with name %s found", plugin.Name())
  119. }
  120. }
  121. func TestGetAll(t *testing.T) {
  122. t.Parallel()
  123. tmpdir := t.TempDir()
  124. r := LocalRegistry{
  125. socketsPath: tmpdir,
  126. specsPaths: []string{tmpdir},
  127. }
  128. p := filepath.Join(tmpdir, "example.json")
  129. spec := `{
  130. "Name": "example",
  131. "Addr": "https://example.com/docker/plugin"
  132. }`
  133. if err := os.WriteFile(p, []byte(spec), 0o644); err != nil {
  134. t.Fatal(err)
  135. }
  136. plugin, err := r.Plugin("example")
  137. if err != nil {
  138. t.Fatal(err)
  139. }
  140. plugin.Manifest = &Manifest{Implements: []string{"apple"}}
  141. storage.Lock()
  142. storage.plugins["example"] = plugin
  143. storage.Unlock()
  144. fetchedPlugins, err := r.GetAll("apple")
  145. if err != nil {
  146. t.Fatal(err)
  147. }
  148. if fetchedPlugins[0].Name() != plugin.Name() {
  149. t.Fatalf("Expected to get plugin with name %s", plugin.Name())
  150. }
  151. }