From 87fb2c973d8f9a8a1868ab0c2da504095d04715b Mon Sep 17 00:00:00 2001
From: Brandon Philips <brandon@ifup.co>
Date: Fri, 6 Dec 2013 21:19:30 -0800
Subject: [PATCH] server: add socket activation

This adds the ability to socket activate docker by passing in
`-H fd://*` along with examples systemd configuration files.

The fastest way to test this is to run:

```
/usr/lib/systemd/systemd-activate -l 127.0.0.1:2001 /usr/bin/docker -d -H 'fd://*'
docker -H tcp://127.0.0.1:2001 ps
```

Docker-DCO-1.1-Signed-off-by: Brandon Philips <brandon.philips@coreos.com> (github: philips)
---
 api.go                                        | 64 +++++++++++++++++--
 .../systemd/socket-activation/docker.service  | 11 ++++
 .../systemd/socket-activation/docker.socket   |  8 +++
 server.go                                     | 26 ++++----
 systemd/listendfd.go                          | 41 ++++++++++++
 utils/utils.go                                |  2 +
 6 files changed, 130 insertions(+), 22 deletions(-)
 create mode 100644 contrib/init/systemd/socket-activation/docker.service
 create mode 100644 contrib/init/systemd/socket-activation/docker.socket
 create mode 100644 systemd/listendfd.go

diff --git a/api.go b/api.go
index bf9f29b57f..e522583087 100644
--- a/api.go
+++ b/api.go
@@ -24,6 +24,7 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"syscall"
 )
 
 const (
@@ -1081,16 +1082,66 @@ func ServeRequest(srv *Server, apiversion float64, w http.ResponseWriter, req *h
 	return nil
 }
 
+// ServeFD creates an http.Server and sets it up to serve given a socket activated
+// argument.
+func ServeFd(addr string, handle http.Handler) error {
+	ls, e := systemd.ListenFD(addr)
+	if e != nil {
+		return e
+	}
+
+	chErrors := make(chan error, len(ls))
+
+	// Since ListenFD will return one or more sockets we have
+	// to create a go func to spawn off multiple serves
+	for i, _ := range(ls) {
+		listener := ls[i]
+		go func () {
+			httpSrv := http.Server{Handler: handle}
+			chErrors <- httpSrv.Serve(listener)
+		}()
+	}
+
+	for i := 0; i < len(ls); i += 1 {
+		err := <-chErrors
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// ListenAndServe sets up the required http.Server and gets it listening for
+// each addr passed in and does protocol specific checking.
 func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
 	r, err := createRouter(srv, logging)
 	if err != nil {
 		return err
 	}
-	l, e := net.Listen(proto, addr)
-	if e != nil {
-		return e
+
+	if proto == "fd" {
+		return ServeFd(addr, r)
 	}
+
 	if proto == "unix" {
+		if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) {
+			return err
+		}
+	}
+
+	l, err := net.Listen(proto, addr)
+	if err != nil {
+		return err
+	}
+
+	// Basic error and sanity checking
+	switch proto {
+	case "tcp":
+		if !strings.HasPrefix(addr, "127.0.0.1") {
+			log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
+		}
+	case "unix":
 		if err := os.Chmod(addr, 0660); err != nil {
 			return err
 		}
@@ -1110,11 +1161,10 @@ func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
 				return err
 			}
 		}
+	default:
+		return fmt.Errorf("Invalid protocol format.")
 	}
-	httpSrv := http.Server{Addr: addr, Handler: r}
 
-	log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
-	// Tell the init daemon we are accepting requests
-	go systemd.SdNotify("READY=1")
+	httpSrv := http.Server{Addr: addr, Handler: r}
 	return httpSrv.Serve(l)
 }
diff --git a/contrib/init/systemd/socket-activation/docker.service b/contrib/init/systemd/socket-activation/docker.service
new file mode 100644
index 0000000000..0b39c28328
--- /dev/null
+++ b/contrib/init/systemd/socket-activation/docker.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Docker Application Container Engine
+Documentation=http://docs.docker.io
+After=network.target
+
+[Service]
+ExecStartPre=/bin/mount --make-rprivate /
+ExecStart=/usr/bin/docker -d -H fd://*
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/init/systemd/socket-activation/docker.socket b/contrib/init/systemd/socket-activation/docker.socket
new file mode 100644
index 0000000000..3635c89385
--- /dev/null
+++ b/contrib/init/systemd/socket-activation/docker.socket
@@ -0,0 +1,8 @@
+[Unit]
+Description=Docker Socket for the API
+
+[Socket]
+ListenStream=/var/run/docker.sock
+
+[Install]
+WantedBy=sockets.target
diff --git a/server.go b/server.go
index b4add79771..b87ec659f8 100644
--- a/server.go
+++ b/server.go
@@ -10,6 +10,7 @@ import (
 	"github.com/dotcloud/docker/pkg/cgroups"
 	"github.com/dotcloud/docker/pkg/graphdb"
 	"github.com/dotcloud/docker/registry"
+	"github.com/dotcloud/docker/systemd"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -114,29 +115,20 @@ func jobInitApi(job *engine.Job) engine.Status {
 	return engine.StatusOK
 }
 
+// ListenAndServe loops through all of the protocols sent in to docker and spawns
+// off a go routine to setup a serving http.Server for each.
 func (srv *Server) ListenAndServe(job *engine.Job) engine.Status {
 	protoAddrs := job.Args
 	chErrors := make(chan error, len(protoAddrs))
+
 	for _, protoAddr := range protoAddrs {
 		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
-		switch protoAddrParts[0] {
-		case "unix":
-			if err := syscall.Unlink(protoAddrParts[1]); err != nil && !os.IsNotExist(err) {
-				log.Fatal(err)
-			}
-		case "tcp":
-			if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
-				log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
-			}
-		default:
-			job.Errorf("Invalid protocol format.")
-			return engine.StatusErr
-		}
-		go func() {
-			// FIXME: merge Server.ListenAndServe with ListenAndServe
+		go func () {
+			log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1])
 			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging"))
 		}()
 	}
+
 	for i := 0; i < len(protoAddrs); i += 1 {
 		err := <-chErrors
 		if err != nil {
@@ -144,6 +136,10 @@ func (srv *Server) ListenAndServe(job *engine.Job) engine.Status {
 			return engine.StatusErr
 		}
 	}
+
+	// Tell the init daemon we are accepting requests
+	go systemd.SdNotify("READY=1")
+
 	return engine.StatusOK
 }
 
diff --git a/systemd/listendfd.go b/systemd/listendfd.go
new file mode 100644
index 0000000000..a8fdb09ca4
--- /dev/null
+++ b/systemd/listendfd.go
@@ -0,0 +1,41 @@
+package systemd
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"strconv"
+
+	"github.com/coreos/go-systemd/activation"
+)
+
+// ListenFD returns the specified socket activated files as a slice of
+// net.Listeners or all of the activated files if "*" is given.
+func ListenFD(addr string) ([]net.Listener, error) {
+	files := activation.Files(false)
+	if files == nil || len(files) == 0 {
+		return nil, errors.New("No sockets found")
+	}
+
+	fdNum, _ := strconv.Atoi(addr)
+	fdOffset := fdNum - 3
+	if (addr != "*") && (len(files) < int(fdOffset)+1) {
+		return nil, errors.New("Too few socket activated files passed in")
+	}
+
+	// socket activation
+	listeners := make([]net.Listener, len(files))
+	for i, f := range files {
+		var err error
+		listeners[i], err = net.FileListener(f)
+		if err != nil {
+			return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error())
+		}
+	}
+
+	if addr == "*" {
+		return listeners, nil
+	}
+
+	return []net.Listener{listeners[fdOffset]}, nil
+}
diff --git a/utils/utils.go b/utils/utils.go
index 2a11397212..542ab49702 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -767,6 +767,8 @@ func ParseHost(defaultHost string, defaultPort int, defaultUnix, addr string) (s
 	case strings.HasPrefix(addr, "tcp://"):
 		proto = "tcp"
 		addr = strings.TrimPrefix(addr, "tcp://")
+	case strings.HasPrefix(addr, "fd://"):
+		return addr, nil
 	case addr == "":
 		proto = "unix"
 		addr = defaultUnix