Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,71 @@ |
| 0 |
+package filesync |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "path/filepath" |
|
| 6 |
+ "testing" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/client/session" |
|
| 9 |
+ "github.com/docker/docker/client/session/testutil" |
|
| 10 |
+ "github.com/stretchr/testify/assert" |
|
| 11 |
+ "github.com/stretchr/testify/require" |
|
| 12 |
+ "golang.org/x/sync/errgroup" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+func TestFileSyncIncludePatterns(t *testing.T) {
|
|
| 16 |
+ tmpDir, err := ioutil.TempDir("", "fsynctest")
|
|
| 17 |
+ require.NoError(t, err) |
|
| 18 |
+ |
|
| 19 |
+ destDir, err := ioutil.TempDir("", "fsynctest")
|
|
| 20 |
+ require.NoError(t, err) |
|
| 21 |
+ |
|
| 22 |
+ err = ioutil.WriteFile(filepath.Join(tmpDir, "foo"), []byte("content1"), 0600)
|
|
| 23 |
+ require.NoError(t, err) |
|
| 24 |
+ |
|
| 25 |
+ err = ioutil.WriteFile(filepath.Join(tmpDir, "bar"), []byte("content2"), 0600)
|
|
| 26 |
+ require.NoError(t, err) |
|
| 27 |
+ |
|
| 28 |
+ s, err := session.NewSession("foo", "bar")
|
|
| 29 |
+ require.NoError(t, err) |
|
| 30 |
+ |
|
| 31 |
+ m, err := session.NewManager() |
|
| 32 |
+ require.NoError(t, err) |
|
| 33 |
+ |
|
| 34 |
+ fs := NewFSSyncProvider(tmpDir, nil) |
|
| 35 |
+ s.Allow(fs) |
|
| 36 |
+ |
|
| 37 |
+ dialer := session.Dialer(testutil.TestStream(testutil.Handler(m.HandleConn))) |
|
| 38 |
+ |
|
| 39 |
+ g, ctx := errgroup.WithContext(context.Background()) |
|
| 40 |
+ |
|
| 41 |
+ g.Go(func() error {
|
|
| 42 |
+ return s.Run(ctx, dialer) |
|
| 43 |
+ }) |
|
| 44 |
+ |
|
| 45 |
+ g.Go(func() (reterr error) {
|
|
| 46 |
+ c, err := m.Get(ctx, s.UUID()) |
|
| 47 |
+ if err != nil {
|
|
| 48 |
+ return err |
|
| 49 |
+ } |
|
| 50 |
+ if err := FSSync(ctx, c, FSSendRequestOpt{
|
|
| 51 |
+ DestDir: destDir, |
|
| 52 |
+ IncludePatterns: []string{"ba*"},
|
|
| 53 |
+ }); err != nil {
|
|
| 54 |
+ return err |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ _, err = ioutil.ReadFile(filepath.Join(destDir, "foo")) |
|
| 58 |
+ assert.Error(t, err) |
|
| 59 |
+ |
|
| 60 |
+ dt, err := ioutil.ReadFile(filepath.Join(destDir, "bar")) |
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ return err |
|
| 63 |
+ } |
|
| 64 |
+ assert.Equal(t, "content2", string(dt)) |
|
| 65 |
+ return s.Close() |
|
| 66 |
+ }) |
|
| 67 |
+ |
|
| 68 |
+ err = g.Wait() |
|
| 69 |
+ require.NoError(t, err) |
|
| 70 |
+} |
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package session |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "net" |
|
| 4 | 5 |
"net/http" |
| 5 | 6 |
"strings" |
| 6 | 7 |
"sync" |
| ... | ... |
@@ -49,8 +50,6 @@ func (sm *Manager) HandleHTTPRequest(ctx context.Context, w http.ResponseWriter, |
| 49 | 49 |
} |
| 50 | 50 |
|
| 51 | 51 |
uuid := r.Header.Get(headerSessionUUID) |
| 52 |
- name := r.Header.Get(headerSessionName) |
|
| 53 |
- sharedKey := r.Header.Get(headerSessionSharedKey) |
|
| 54 | 52 |
|
| 55 | 53 |
proto := r.Header.Get("Upgrade")
|
| 56 | 54 |
|
| ... | ... |
@@ -89,9 +88,25 @@ func (sm *Manager) HandleHTTPRequest(ctx context.Context, w http.ResponseWriter, |
| 89 | 89 |
conn.Write([]byte{})
|
| 90 | 90 |
resp.Write(conn) |
| 91 | 91 |
|
| 92 |
+ return sm.handleConn(ctx, conn, r.Header) |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+// HandleConn handles an incoming raw connection |
|
| 96 |
+func (sm *Manager) HandleConn(ctx context.Context, conn net.Conn, opts map[string][]string) error {
|
|
| 97 |
+ sm.mu.Lock() |
|
| 98 |
+ return sm.handleConn(ctx, conn, opts) |
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+// caller needs to take lock, this function will release it |
|
| 102 |
+func (sm *Manager) handleConn(ctx context.Context, conn net.Conn, opts map[string][]string) error {
|
|
| 92 | 103 |
ctx, cancel := context.WithCancel(ctx) |
| 93 | 104 |
defer cancel() |
| 94 | 105 |
|
| 106 |
+ h := http.Header(opts) |
|
| 107 |
+ uuid := h.Get(headerSessionUUID) |
|
| 108 |
+ name := h.Get(headerSessionName) |
|
| 109 |
+ sharedKey := h.Get(headerSessionSharedKey) |
|
| 110 |
+ |
|
| 95 | 111 |
ctx, cc, err := grpcClientConn(ctx, conn) |
| 96 | 112 |
if err != nil {
|
| 97 | 113 |
sm.mu.Unlock() |
| ... | ... |
@@ -111,7 +126,7 @@ func (sm *Manager) HandleHTTPRequest(ctx context.Context, w http.ResponseWriter, |
| 111 | 111 |
supported: make(map[string]struct{}),
|
| 112 | 112 |
} |
| 113 | 113 |
|
| 114 |
- for _, m := range r.Header[headerSessionMethod] {
|
|
| 114 |
+ for _, m := range opts[headerSessionMethod] {
|
|
| 115 | 115 |
c.supported[strings.ToLower(m)] = struct{}{}
|
| 116 | 116 |
} |
| 117 | 117 |
sm.sessions[uuid] = c |
| 118 | 118 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,70 @@ |
| 0 |
+package testutil |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ "net" |
|
| 5 |
+ "time" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/Sirupsen/logrus" |
|
| 8 |
+ "golang.org/x/net/context" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// Handler is function called to handle incoming connection |
|
| 12 |
+type Handler func(ctx context.Context, conn net.Conn, meta map[string][]string) error |
|
| 13 |
+ |
|
| 14 |
+// Dialer is a function for dialing an outgoing connection |
|
| 15 |
+type Dialer func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) |
|
| 16 |
+ |
|
| 17 |
+// TestStream creates an in memory session dialer for a handler function |
|
| 18 |
+func TestStream(handler Handler) Dialer {
|
|
| 19 |
+ s1, s2 := sockPair() |
|
| 20 |
+ return func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
|
| 21 |
+ go func() {
|
|
| 22 |
+ err := handler(context.TODO(), s1, meta) |
|
| 23 |
+ if err != nil {
|
|
| 24 |
+ logrus.Error(err) |
|
| 25 |
+ } |
|
| 26 |
+ s1.Close() |
|
| 27 |
+ }() |
|
| 28 |
+ return s2, nil |
|
| 29 |
+ } |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func sockPair() (*sock, *sock) {
|
|
| 33 |
+ pr1, pw1 := io.Pipe() |
|
| 34 |
+ pr2, pw2 := io.Pipe() |
|
| 35 |
+ return &sock{pw1, pr2, pw1}, &sock{pw2, pr1, pw2}
|
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+type sock struct {
|
|
| 39 |
+ io.Writer |
|
| 40 |
+ io.Reader |
|
| 41 |
+ io.Closer |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+func (s *sock) LocalAddr() net.Addr {
|
|
| 45 |
+ return dummyAddr{}
|
|
| 46 |
+} |
|
| 47 |
+func (s *sock) RemoteAddr() net.Addr {
|
|
| 48 |
+ return dummyAddr{}
|
|
| 49 |
+} |
|
| 50 |
+func (s *sock) SetDeadline(t time.Time) error {
|
|
| 51 |
+ return nil |
|
| 52 |
+} |
|
| 53 |
+func (s *sock) SetReadDeadline(t time.Time) error {
|
|
| 54 |
+ return nil |
|
| 55 |
+} |
|
| 56 |
+func (s *sock) SetWriteDeadline(t time.Time) error {
|
|
| 57 |
+ return nil |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 60 |
+type dummyAddr struct {
|
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+func (d dummyAddr) Network() string {
|
|
| 64 |
+ return "tcp" |
|
| 65 |
+} |
|
| 66 |
+ |
|
| 67 |
+func (d dummyAddr) String() string {
|
|
| 68 |
+ return "localhost" |
|
| 69 |
+} |