[lxc-devel] [lxd/master] Use custom netcat

tych0 on Github lxc-bot at linuxcontainers.org
Wed May 4 20:24:06 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160504/564d88f6/attachment.bin>
-------------- next part --------------
From 90dbec90d21db966c68724b4b41e8c12845eaf5a Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 4 May 2016 18:55:55 +0000
Subject: [PATCH 1/2] daemon: move execPath to a global

In the next patch we'll use this in the rsync code, and rather than have to
keep passing it around everywhere, let's just make it a global.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 26 +++++++++++++-------------
 lxd/daemon.go        |  9 +--------
 lxd/main.go          |  6 ++++++
 3 files changed, 20 insertions(+), 21 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 2d8e22e..6788860 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -419,12 +419,12 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Setup the hooks
-	err = lxcSetConfigItem(cc, "lxc.hook.pre-start", fmt.Sprintf("%s callhook %s %d start", c.daemon.execPath, shared.VarPath(""), c.id))
+	err = lxcSetConfigItem(cc, "lxc.hook.pre-start", fmt.Sprintf("%s callhook %s %d start", execPath, shared.VarPath(""), c.id))
 	if err != nil {
 		return err
 	}
 
-	err = lxcSetConfigItem(cc, "lxc.hook.post-stop", fmt.Sprintf("%s callhook %s %d stop", c.daemon.execPath, shared.VarPath(""), c.id))
+	err = lxcSetConfigItem(cc, "lxc.hook.post-stop", fmt.Sprintf("%s callhook %s %d stop", execPath, shared.VarPath(""), c.id))
 	if err != nil {
 		return err
 	}
@@ -1141,7 +1141,7 @@ func (c *containerLXC) Start(stateful bool) error {
 		}
 
 		out, err := exec.Command(
-			c.daemon.execPath,
+			execPath,
 			"forkmigrate",
 			c.name,
 			c.daemon.lxcpath,
@@ -1177,7 +1177,7 @@ func (c *containerLXC) Start(stateful bool) error {
 
 	// Start the LXC container
 	out, err := exec.Command(
-		c.daemon.execPath,
+		execPath,
 		"forkstart",
 		c.name,
 		c.daemon.lxcpath,
@@ -1210,7 +1210,7 @@ func (c *containerLXC) StartFromMigration(imagesDir string) error {
 
 	// Start the LXC container
 	out, err := exec.Command(
-		c.daemon.execPath,
+		execPath,
 		"forkmigrate",
 		c.name,
 		c.daemon.lxcpath,
@@ -1734,7 +1734,7 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 		}
 
 		out, err := exec.Command(
-			c.daemon.execPath,
+			execPath,
 			"forkmigrate",
 			c.name,
 			c.daemon.lxcpath,
@@ -2841,7 +2841,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 
 	// Get the file from the container
 	out, err := exec.Command(
-		c.daemon.execPath,
+		execPath,
 		"forkgetfile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
@@ -2952,7 +2952,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 
 	// Push the file to the container
 	out, err := exec.Command(
-		c.daemon.execPath,
+		execPath,
 		"forkputfile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
@@ -3007,7 +3007,7 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 		envSlice = append(envSlice, fmt.Sprintf("%s=%s", k, v))
 	}
 
-	args := []string{c.daemon.execPath, "forkexec", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
+	args := []string{execPath, "forkexec", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
 
 	args = append(args, "--")
 	args = append(args, "env")
@@ -3018,7 +3018,7 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 	args = append(args, command...)
 
 	cmd := exec.Cmd{}
-	cmd.Path = c.daemon.execPath
+	cmd.Path = execPath
 	cmd.Args = args
 	cmd.Stdin = stdin
 	cmd.Stdout = stdout
@@ -3120,7 +3120,7 @@ func (c *containerLXC) networkState() map[string]shared.ContainerStateNetwork {
 
 	// Get the network state from the container
 	out, err := exec.Command(
-		c.daemon.execPath,
+		execPath,
 		"forkgetnet",
 		fmt.Sprintf("%d", pid)).CombinedOutput()
 
@@ -3317,7 +3317,7 @@ func (c *containerLXC) insertMount(source, target, fstype string, flags int) err
 	mntsrc := filepath.Join("/dev/.lxd-mounts", filepath.Base(tmpMount))
 	pidStr := fmt.Sprintf("%d", pid)
 
-	out, err := exec.Command(c.daemon.execPath, "forkmount", pidStr, mntsrc, target).CombinedOutput()
+	out, err := exec.Command(execPath, "forkmount", pidStr, mntsrc, target).CombinedOutput()
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
@@ -3347,7 +3347,7 @@ func (c *containerLXC) removeMount(mount string) error {
 
 	// Remove the mount from the container
 	pidStr := fmt.Sprintf("%d", pid)
-	out, err := exec.Command(c.daemon.execPath, "forkumount", pidStr, mount).CombinedOutput()
+	out, err := exec.Command(execPath, "forkumount", pidStr, mount).CombinedOutput()
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
diff --git a/lxd/daemon.go b/lxd/daemon.go
index b7a2e4b..49a6f7f 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -75,7 +75,6 @@ type Daemon struct {
 	pruneChan           chan bool
 	shutdownChan        chan bool
 	resetAutoUpdateChan chan bool
-	execPath            string
 
 	Storage storage
 
@@ -544,14 +543,8 @@ func (d *Daemon) Init() error {
 	d.shutdownChan = make(chan bool)
 
 	/* Set the executable path */
-	absPath, err := os.Readlink("/proc/self/exe")
-	if err != nil {
-		return err
-	}
-	d.execPath = absPath
-
 	/* Set the LVM environment */
-	err = os.Setenv("LVM_SUPPRESS_FD_WARNINGS", "1")
+	err := os.Setenv("LVM_SUPPRESS_FD_WARNINGS", "1")
 	if err != nil {
 		return err
 	}
diff --git a/lxd/main.go b/lxd/main.go
index 0766853..80c632d 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -50,9 +50,15 @@ var argVersion = gnuflag.Bool("version", false, "")
 // Global variables
 var debug bool
 var verbose bool
+var execPath string
 
 func init() {
 	rand.Seed(time.Now().UTC().UnixNano())
+	absPath, err := os.Readlink("/proc/self/exe")
+	if err != nil {
+		absPath = "bad-exec-path"
+	}
+	execPath = absPath
 }
 
 func main() {

From d288531690d37cc6764094c6626c255095a9779e Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 4 May 2016 16:27:50 +0000
Subject: [PATCH 2/2] rsync: use custom netcat instead of nc -U

For whatever reason, it seemed (through checking via some combination of
strace and printing websocket frame lengths) that netcat was doing some
buffering of the socket contents and causing a hang in some cases. Instead,
let's use our own very dumb implementation of netcat instead, avoiding any
buffering.

Now that we are execing ourself to do rsync, we can't do any tests in go
test, because the test binary is not the lxd binary, and so we'll fork bomb
ourself. So, let's get rid of the rsync test. This doesn't get rid of test
coverage, since we're doing a cold migration in the test suite anyway.

Closes #1944

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/main.go       |   4 +++
 lxd/rsync.go      |  51 +++++++++++++++++++++++++-
 lxd/rsync_test.go | 106 ------------------------------------------------------
 3 files changed, 54 insertions(+), 107 deletions(-)
 delete mode 100644 lxd/rsync_test.go

diff --git a/lxd/main.go b/lxd/main.go
index 80c632d..27265b8 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -158,6 +158,8 @@ func run() error {
 		fmt.Printf("        Start a container\n")
 		fmt.Printf("    callhook\n")
 		fmt.Printf("        Call a container hook\n")
+		fmt.Printf("    netcat\n")
+		fmt.Printf("        Mirror a unix socket to stdin/stdout")
 	}
 
 	// Parse the arguments
@@ -233,6 +235,8 @@ func run() error {
 				fmt.Fprintf(os.Stderr, "error: %v\n", err)
 			}
 			os.Exit(ret)
+		case "netcat":
+			return Netcat(os.Args[1:])
 		}
 	}
 
diff --git a/lxd/rsync.go b/lxd/rsync.go
index 1d043df..8f49313 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -7,6 +7,7 @@ import (
 	"net"
 	"os"
 	"os/exec"
+	"sync"
 
 	"github.com/gorilla/websocket"
 
@@ -93,7 +94,7 @@ func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 	 * command (i.e. the command to run on --server). However, we're
 	 * hardcoding that at the other end, so we can just ignore it.
 	 */
-	rsyncCmd := fmt.Sprintf("sh -c \"nc -U %s\"", f.Name())
+	rsyncCmd := fmt.Sprintf("sh -c \"%s netcat %s\"", execPath, f.Name())
 	cmd := exec.Command(
 		"rsync",
 		"-arvP",
@@ -169,3 +170,51 @@ func rsyncRecvCmd(path string) *exec.Cmd {
 func RsyncRecv(path string, conn *websocket.Conn) error {
 	return rsyncWebsocket(path, rsyncRecvCmd(path), conn)
 }
+
+// Netcat is called with:
+//
+//    lxd netcat /path/to/unix/socket
+//
+// and does unbuffered netcatting of to socket to stdin/stdout. Any arguments
+// after the path to the unix socket are ignored, so that this can be passed
+// directly to rsync as the sync command.
+func Netcat(args []string) error {
+	if len(args) < 2 {
+		return fmt.Errorf("Bad arguments %q", args)
+	}
+
+	uAddr, err := net.ResolveUnixAddr("unix", args[1])
+	if err != nil {
+		return err
+	}
+
+	conn, err := net.DialUnix("unix", nil, uAddr)
+	if err != nil {
+		return err
+	}
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	go func() {
+		io.Copy(os.Stdout, conn)
+		f, _ := os.Create("/tmp/done_stdout")
+		f.Close()
+		conn.Close()
+		f, _ = os.Create("/tmp/done_close")
+		f.Close()
+		wg.Done()
+	}()
+
+	go func() {
+		io.Copy(conn, os.Stdin)
+		f, _ := os.Create("/tmp/done_stdin")
+		f.Close()
+	}()
+
+	f, _ := os.Create("/tmp/done_spawning_goroutines")
+	f.Close()
+	wg.Wait()
+
+	return nil
+}
diff --git a/lxd/rsync_test.go b/lxd/rsync_test.go
deleted file mode 100644
index 3aa94f3..0000000
--- a/lxd/rsync_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package main
-
-import (
-	"io"
-	"io/ioutil"
-	"os"
-	"path"
-	"testing"
-
-	"github.com/lxc/lxd/shared"
-)
-
-const helloWorld = "hello world\n"
-
-func TestRsyncSendRecv(t *testing.T) {
-	source, err := ioutil.TempDir("", "lxd_test_source_")
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	defer os.RemoveAll(source)
-
-	sink, err := ioutil.TempDir("", "lxd_test_sink_")
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	defer os.RemoveAll(sink)
-
-	/* now, write something to rsync over */
-	f, err := os.Create(path.Join(source, "foo"))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	f.Write([]byte(helloWorld))
-	f.Close()
-
-	send, sendConn, _, err := rsyncSendSetup(shared.AddSlash(source))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	recv := rsyncRecvCmd(sink)
-
-	recvOut, err := recv.StdoutPipe()
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	recvIn, err := recv.StdinPipe()
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if err := recv.Start(); err != nil {
-		t.Error(err)
-		return
-	}
-
-	go func() {
-		defer sendConn.Close()
-		if _, err := io.Copy(sendConn, recvOut); err != nil {
-			t.Error(err)
-		}
-
-		if err := recv.Wait(); err != nil {
-			t.Error(err)
-		}
-
-	}()
-
-	/*
-	 * We close the socket in the above gofunc, but go tells us
-	 * https://github.com/golang/go/issues/4373 that this is an error
-	 * because we were reading from a socket that was closed. Thus, we
-	 * ignore it
-	 */
-	io.Copy(recvIn, sendConn)
-
-	if err := send.Wait(); err != nil {
-		t.Error(err)
-		return
-	}
-
-	f, err = os.Open(path.Join(sink, "foo"))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	defer f.Close()
-
-	buf, err := ioutil.ReadAll(f)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if string(buf) != helloWorld {
-		t.Errorf("expected %s got %s", helloWorld, buf)
-		return
-	}
-}


More information about the lxc-devel mailing list