From 5cb2347f7716f89ae83c67acd6f23a34342fa38c Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Fri, 5 Jun 2015 13:03:21 +0200 Subject: [PATCH 1/6] Use our own Docker client --- common/docker/client.go | 85 +++++++++++++++++++++++++++++++++++++++ common/updater/updater.go | 47 ---------------------- prog/weavedns/main.go | 13 ++++-- prog/weaver/main.go | 11 +++-- 4 files changed, 102 insertions(+), 54 deletions(-) create mode 100644 common/docker/client.go delete mode 100644 common/updater/updater.go diff --git a/common/docker/client.go b/common/docker/client.go new file mode 100644 index 00000000..93f548a1 --- /dev/null +++ b/common/docker/client.go @@ -0,0 +1,85 @@ +package docker + +import ( + "regexp" + + "github.com/fsouza/go-dockerclient" + . "github.com/weaveworks/weave/common" +) + +// Regexp that matches container IDs +var containerIDregexp = regexp.MustCompile("^[a-f0-9]+$") + +// IsContainerID returns True if the string provided is a valid container id +func IsContainerID(idStr string) bool { + return containerIDregexp.MatchString(idStr) +} + +// An observer for container events +type ContainerObserver interface { + ContainerDied(ident string) error +} + +type Client struct { + *docker.Client +} + +// NewClient creates a new Docker client +func NewClient(apiPath string) (*Client, error) { + dc, err := docker.NewClient(apiPath) + if err != nil { + return nil, err + } + return &Client{dc}, nil +} + +// Start starts the client +func (c *Client) Start(apiPath string) error { + client, err := docker.NewClient(apiPath) + if err != nil { + Error.Printf("[docker] Unable to connect to Docker API on %s: %s", apiPath, err) + return err + } + + env, err := client.Version() + if err != nil { + Error.Printf("[docker] Unable to connect to Docker API on %s: %s", apiPath, err) + return err + } + Info.Printf("[docker] Using Docker API on %s: %v", apiPath, env) + return nil +} + +// AddObserver adds an observer for docker events +func (c *Client) AddObserver(ob ContainerObserver) error { + events := make(chan *docker.APIEvents) + if err := c.AddEventListener(events); err != nil { + Error.Printf("[docker] Unable to add listener to Docker API: %s", err) + return err + } + + go func() { + for event := range events { + switch event.Status { + case "die": + id := event.ID + ob.ContainerDied(id) + } + } + }() + return nil +} + +// IsContainerRunning returns true if the container is up and running +func (c *Client) IsContainerRunning(idStr string) (bool, error) { + // check with Docker whether the container is really running + container, err := c.InspectContainer(idStr) + if err != nil { + if _, notThere := err.(*docker.NoSuchContainer); notThere { + return false, nil + } + return false, err + } + + return container.State.Running, nil +} diff --git a/common/updater/updater.go b/common/updater/updater.go deleted file mode 100644 index f46a0dea..00000000 --- a/common/updater/updater.go +++ /dev/null @@ -1,47 +0,0 @@ -package updater - -import ( - "github.com/fsouza/go-dockerclient" - . "github.com/weaveworks/weave/common" -) - -type ContainerObserver interface { - ContainerDied(ident string) error -} - -func checkError(err error, apiPath string) { - if err != nil { - Error.Fatalf("[updater] Unable to connect to Docker API on %s: %s", - apiPath, err) - } -} - -func Start(apiPath string, ob ContainerObserver) error { - client, err := docker.NewClient(apiPath) - checkError(err, apiPath) - - env, err := client.Version() - checkError(err, apiPath) - - events := make(chan *docker.APIEvents) - err = client.AddEventListener(events) - checkError(err, apiPath) - - Info.Printf("[updater] Using Docker API on %s: %v", apiPath, env) - - go func() { - for event := range events { - handleEvent(ob, event, client) - } - }() - return nil -} - -func handleEvent(ob ContainerObserver, event *docker.APIEvents, client *docker.Client) error { - switch event.Status { - case "die": - id := event.ID - ob.ContainerDied(id) - } - return nil -} diff --git a/prog/weavedns/main.go b/prog/weavedns/main.go index 9b6ab270..81bc3f7b 100644 --- a/prog/weavedns/main.go +++ b/prog/weavedns/main.go @@ -8,7 +8,7 @@ import ( "github.com/miekg/dns" . "github.com/weaveworks/weave/common" - "github.com/weaveworks/weave/common/updater" + "github.com/weaveworks/weave/common/docker" weavedns "github.com/weaveworks/weave/nameserver" weavenet "github.com/weaveworks/weave/net" ) @@ -18,6 +18,7 @@ var version = "(unreleased version)" func main() { var ( justVersion bool + dockerCli *docker.Client ifaceName string apiPath string domain string @@ -95,14 +96,18 @@ func main() { if err != nil { Error.Fatal("[main] Unable to initialize the Zone database", err) } - err = zone.Start() - if err != nil { + if err := zone.Start(); err != nil { Error.Fatal("[main] Unable to start the Zone database", err) } defer zone.Stop() + dockerCli, err = docker.NewClient(apiPath) + if err != nil { + Error.Fatal("[main] Unable to start docker client", err) + } + if watch { - err := updater.Start(apiPath, zone) + err := dockerCli.AddObserver(zone) if err != nil { Error.Fatal("[main] Unable to start watcher", err) } diff --git a/prog/weaver/main.go b/prog/weaver/main.go index 0ce26cd3..984becb7 100644 --- a/prog/weaver/main.go +++ b/prog/weaver/main.go @@ -15,7 +15,7 @@ import ( "github.com/davecheney/profile" "github.com/gorilla/mux" . "github.com/weaveworks/weave/common" - "github.com/weaveworks/weave/common/updater" + "github.com/weaveworks/weave/common/docker" "github.com/weaveworks/weave/ipam" "github.com/weaveworks/weave/ipam/address" weavenet "github.com/weaveworks/weave/net" @@ -222,10 +222,15 @@ func createAllocator(router *weave.Router, apiPath string, ipRangeStr string, de allocator.SetInterfaces(router.NewGossip("IPallocation", allocator)) allocator.Start() - err := updater.Start(apiPath, allocator) + + dockerCli, err := docker.NewClient(apiPath) if err != nil { - log.Fatal("Unable to start watcher", err) + Error.Fatal("Unable to start docker client", err) } + if err = dockerCli.AddObserver(allocator); err != nil { + Error.Fatal("Unable to start watcher", err) + } + return allocator, defaultSubnet } From 1889a4a87fe73ea57707c1e74b595bb4f72a5c76 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Fri, 5 Jun 2015 13:15:06 +0200 Subject: [PATCH 2/6] Simplify the arguments passed to nameserver/http.go:ServeHTTP() (by using information found in some other objects we are already passing, like the server) --- nameserver/http.go | 13 +++++++------ nameserver/http_test.go | 7 +++++-- prog/weavedns/main.go | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/nameserver/http.go b/nameserver/http.go index ce438bee..bbfc02b5 100644 --- a/nameserver/http.go +++ b/nameserver/http.go @@ -24,14 +24,14 @@ func extractFQDN(r *http.Request) (string, string) { return "*", "" } -func ServeHTTP(listener net.Listener, version string, server *DNSServer, domain string, db Zone) { +func ServeHTTP(listener net.Listener, version string, server *DNSServer) { muxRouter := mux.NewRouter() muxRouter.Methods("GET").Path("/status").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "weave DNS", version) fmt.Fprintln(w, server.Status()) - fmt.Fprintln(w, db.Status()) + fmt.Fprintln(w, server.Zone.Status()) }) muxRouter.Methods("PUT").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -55,9 +55,10 @@ func ServeHTTP(listener net.Listener, version string, server *DNSServer, domain return } + domain := server.Zone.Domain() if dns.IsSubDomain(domain, name) { Info.Printf("[http] Adding %s -> %s", name, ipStr) - if err := db.AddRecord(idStr, name, ip); err != nil { + if err := server.Zone.AddRecord(idStr, name, ip); err != nil { if _, ok := err.(DuplicateError); !ok { httpErrorAndLog( Error, w, "Internal error", http.StatusInternalServerError, @@ -86,7 +87,7 @@ func ServeHTTP(listener net.Listener, version string, server *DNSServer, domain fqdnStr, fqdn := extractFQDN(r) Info.Printf("[http] Deleting ID %s, IP %s, FQDN %s", idStr, ipStr, fqdnStr) - db.DeleteRecords(idStr, fqdn, ip) + server.Zone.DeleteRecords(idStr, fqdn, ip) }) muxRouter.Methods("DELETE").Path("/name/{id:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -96,14 +97,14 @@ func ServeHTTP(listener net.Listener, version string, server *DNSServer, domain fqdnStr, fqdn := extractFQDN(r) Info.Printf("[http] Deleting ID %s, IP *, FQDN %s", idStr, fqdnStr) - db.DeleteRecords(idStr, fqdn, net.IP{}) + server.Zone.DeleteRecords(idStr, fqdn, net.IP{}) }) muxRouter.Methods("DELETE").Path("/name").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fqdnStr, fqdn := extractFQDN(r) Info.Printf("[http] Deleting ID *, IP *, FQDN %s", fqdnStr) - db.DeleteRecords("", fqdn, net.IP{}) + server.Zone.DeleteRecords("", fqdn, net.IP{}) }) http.Handle("/", muxRouter) diff --git a/nameserver/http_test.go b/nameserver/http_test.go index 44e5bfc0..86bc86db 100644 --- a/nameserver/http_test.go +++ b/nameserver/http_test.go @@ -27,7 +27,7 @@ func TestHttp(t *testing.T) { dockerIP = "9.8.7.6" ) - zone, err := NewZoneDb(ZoneConfig{}) + zone, err := NewZoneDb(ZoneConfig{Domain: testDomain}) wt.AssertNoErr(t, err) err = zone.Start() wt.AssertNoErr(t, err) @@ -38,8 +38,11 @@ func TestHttp(t *testing.T) { t.Fatal("Unable to create http listener: ", err) } + // Create a useless server... it will not even be started + srv, _ := NewDNSServer(DNSServerConfig{Zone: zone}) + port := httpListener.Addr().(*net.TCPAddr).Port - go ServeHTTP(httpListener, "", nil, testDomain, zone) + go ServeHTTP(httpListener, "", srv) // Ask the http server to add our test address into the database addrParts := strings.Split(testAddr1, "/") diff --git a/prog/weavedns/main.go b/prog/weavedns/main.go index 81bc3f7b..75093352 100644 --- a/prog/weavedns/main.go +++ b/prog/weavedns/main.go @@ -145,7 +145,7 @@ func main() { } go SignalHandlerLoop(srv) - go weavedns.ServeHTTP(httpListener, version, srv, domain, zone) + go weavedns.ServeHTTP(httpListener, version, srv) err = srv.Start() if err != nil { From 698d3523222833013708db928e2111aae2849745 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Fri, 5 Jun 2015 13:35:18 +0200 Subject: [PATCH 3/6] Check with Docker whether the container is really running --- nameserver/http.go | 17 ++++++++++++++++- nameserver/http_test.go | 2 +- prog/weavedns/main.go | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/nameserver/http.go b/nameserver/http.go index bbfc02b5..19a6534a 100644 --- a/nameserver/http.go +++ b/nameserver/http.go @@ -9,6 +9,7 @@ import ( "github.com/gorilla/mux" "github.com/miekg/dns" . "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/common/docker" ) func httpErrorAndLog(level *log.Logger, w http.ResponseWriter, msg string, @@ -24,7 +25,7 @@ func extractFQDN(r *http.Request) (string, string) { return "*", "" } -func ServeHTTP(listener net.Listener, version string, server *DNSServer) { +func ServeHTTP(listener net.Listener, version string, server *DNSServer, dockerCli *docker.Client) { muxRouter := mux.NewRouter() @@ -66,6 +67,20 @@ func ServeHTTP(listener net.Listener, version string, server *DNSServer) { return } // oh, I already know this. whatever. } + + if dockerCli != nil { + if !docker.IsContainerID(idStr) { + Debug.Printf("[http] '%s' does not seem a container id", idStr) + return + } + + if running, err := dockerCli.IsContainerRunning(idStr); err != nil { + Error.Printf("[http] Could not check container status: %s", err) + } else if !running { + Info.Printf("[http] '%s' is not running: removing", idStr) + server.Zone.DeleteRecords(idStr, name, ip) + } + } } else { Info.Printf("[http] Ignoring name %s, not in %s", name, domain) } diff --git a/nameserver/http_test.go b/nameserver/http_test.go index 86bc86db..6e8b3ec0 100644 --- a/nameserver/http_test.go +++ b/nameserver/http_test.go @@ -42,7 +42,7 @@ func TestHttp(t *testing.T) { srv, _ := NewDNSServer(DNSServerConfig{Zone: zone}) port := httpListener.Addr().(*net.TCPAddr).Port - go ServeHTTP(httpListener, "", srv) + go ServeHTTP(httpListener, "", srv, nil) // Ask the http server to add our test address into the database addrParts := strings.Split(testAddr1, "/") diff --git a/prog/weavedns/main.go b/prog/weavedns/main.go index 75093352..7f8334b0 100644 --- a/prog/weavedns/main.go +++ b/prog/weavedns/main.go @@ -145,7 +145,7 @@ func main() { } go SignalHandlerLoop(srv) - go weavedns.ServeHTTP(httpListener, version, srv) + go weavedns.ServeHTTP(httpListener, version, srv, dockerCli) err = srv.Start() if err != nil { From 086daca3d0d4b5f6431c3922a9340331c133a595 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 12 Jun 2015 14:45:29 +0100 Subject: [PATCH 4/6] Fold communication-checking code into docker.Client.NewClient() --- common/docker/client.go | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/common/docker/client.go b/common/docker/client.go index 93f548a1..2c71fb42 100644 --- a/common/docker/client.go +++ b/common/docker/client.go @@ -24,30 +24,20 @@ type Client struct { *docker.Client } -// NewClient creates a new Docker client +// NewClient creates a new Docker client and checks we can talk to Docker func NewClient(apiPath string) (*Client, error) { dc, err := docker.NewClient(apiPath) if err != nil { return nil, err } - return &Client{dc}, nil -} - -// Start starts the client -func (c *Client) Start(apiPath string) error { - client, err := docker.NewClient(apiPath) - if err != nil { - Error.Printf("[docker] Unable to connect to Docker API on %s: %s", apiPath, err) - return err - } + client := &Client{dc} env, err := client.Version() if err != nil { - Error.Printf("[docker] Unable to connect to Docker API on %s: %s", apiPath, err) - return err + return nil, err } Info.Printf("[docker] Using Docker API on %s: %v", apiPath, env) - return nil + return client, nil } // AddObserver adds an observer for docker events From 799e202d6d24e904aa8a0f346c612a9fa8a8be75 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 12 Jun 2015 14:45:48 +0100 Subject: [PATCH 5/6] Make error message more readable --- prog/weavedns/main.go | 2 +- prog/weaver/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prog/weavedns/main.go b/prog/weavedns/main.go index 7f8334b0..0c843b43 100644 --- a/prog/weavedns/main.go +++ b/prog/weavedns/main.go @@ -103,7 +103,7 @@ func main() { dockerCli, err = docker.NewClient(apiPath) if err != nil { - Error.Fatal("[main] Unable to start docker client", err) + Error.Fatal("[main] Unable to start docker client: ", err) } if watch { diff --git a/prog/weaver/main.go b/prog/weaver/main.go index 984becb7..1c4de362 100644 --- a/prog/weaver/main.go +++ b/prog/weaver/main.go @@ -225,7 +225,7 @@ func createAllocator(router *weave.Router, apiPath string, ipRangeStr string, de dockerCli, err := docker.NewClient(apiPath) if err != nil { - Error.Fatal("Unable to start docker client", err) + Error.Fatal("Unable to start docker client: ", err) } if err = dockerCli.AddObserver(allocator); err != nil { Error.Fatal("Unable to start watcher", err) From 7941612daded305df1b8ff131fae9669fe6ac0da Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 12 Jun 2015 15:01:06 +0100 Subject: [PATCH 6/6] remove one level of indentation --- nameserver/http.go | 50 +++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/nameserver/http.go b/nameserver/http.go index 19a6534a..f5d5bafe 100644 --- a/nameserver/http.go +++ b/nameserver/http.go @@ -57,32 +57,32 @@ func ServeHTTP(listener net.Listener, version string, server *DNSServer, dockerC } domain := server.Zone.Domain() - if dns.IsSubDomain(domain, name) { - Info.Printf("[http] Adding %s -> %s", name, ipStr) - if err := server.Zone.AddRecord(idStr, name, ip); err != nil { - if _, ok := err.(DuplicateError); !ok { - httpErrorAndLog( - Error, w, "Internal error", http.StatusInternalServerError, - "Unexpected error from DB: %s", err) - return - } // oh, I already know this. whatever. - } - - if dockerCli != nil { - if !docker.IsContainerID(idStr) { - Debug.Printf("[http] '%s' does not seem a container id", idStr) - return - } - - if running, err := dockerCli.IsContainerRunning(idStr); err != nil { - Error.Printf("[http] Could not check container status: %s", err) - } else if !running { - Info.Printf("[http] '%s' is not running: removing", idStr) - server.Zone.DeleteRecords(idStr, name, ip) - } - } - } else { + if !dns.IsSubDomain(domain, name) { Info.Printf("[http] Ignoring name %s, not in %s", name, domain) + return + } + Info.Printf("[http] Adding %s -> %s", name, ipStr) + if err := server.Zone.AddRecord(idStr, name, ip); err != nil { + if _, ok := err.(DuplicateError); !ok { + httpErrorAndLog( + Error, w, "Internal error", http.StatusInternalServerError, + "Unexpected error from DB: %s", err) + return + } // oh, I already know this. whatever. + } + + if dockerCli != nil { + if !docker.IsContainerID(idStr) { + Debug.Printf("[http] '%s' does not seem to be a container id", idStr) + return + } + + if running, err := dockerCli.IsContainerRunning(idStr); err != nil { + Error.Printf("[http] Could not check container status: %s", err) + } else if !running { + Info.Printf("[http] '%s' is not running: removing", idStr) + server.Zone.DeleteRecords(idStr, name, ip) + } } })