diff --git a/plugin/net/driver.go b/plugin/net/driver.go index 966fe98b..675ca93d 100644 --- a/plugin/net/driver.go +++ b/plugin/net/driver.go @@ -2,9 +2,11 @@ package plugin import ( "fmt" + "strconv" "sync" "github.com/docker/libnetwork/drivers/remote/api" + "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/types" "github.com/vishvananda/netlink" @@ -15,18 +17,28 @@ import ( "github.com/weaveworks/weave/plugin/skel" ) -type driver struct { - scope string - noMulticastRoute bool - sync.RWMutex - endpoints map[string]struct{} +const ( + MulticastOption = "works.weave.multicast" +) + +type network struct { + hasMulticastRoute bool } -func New(client *docker.Client, weave *weaveapi.Client, scope string, noMulticastRoute bool) (skel.Driver, error) { +type driver struct { + scope string + docker *docker.Client + sync.RWMutex + endpoints map[string]struct{} + networks map[string]network +} + +func New(client *docker.Client, weave *weaveapi.Client, scope string) (skel.Driver, error) { driver := &driver{ - noMulticastRoute: noMulticastRoute, - scope: scope, - endpoints: make(map[string]struct{}), + scope: scope, + docker: client, + endpoints: make(map[string]struct{}), + networks: make(map[string]network), } _, err := NewWatcher(client, weave, driver) @@ -49,11 +61,33 @@ func (driver *driver) GetCapabilities() (*api.GetCapabilityResponse, error) { func (driver *driver) CreateNetwork(create *api.CreateNetworkRequest) error { driver.logReq("CreateNetwork", create, create.NetworkID) + _, err := driver.setupNetworkInfo(create.NetworkID, stringOptions(create)) + return err +} + +// Deal with excessively-generic way the options get decoded from JSON +func stringOptions(create *api.CreateNetworkRequest) map[string]string { + if create.Options != nil { + if data, found := create.Options[netlabel.GenericData]; found { + if options, ok := data.(map[string]interface{}); ok { + out := make(map[string]string, len(options)) + for key, value := range options { + if str, ok := value.(string); ok { + out[key] = str + } + } + return out + } + } + } return nil } -func (driver *driver) DeleteNetwork(delete *api.DeleteNetworkRequest) error { - driver.logReq("DeleteNetwork", delete, delete.NetworkID) +func (driver *driver) DeleteNetwork(delreq *api.DeleteNetworkRequest) error { + driver.logReq("DeleteNetwork", delreq, delreq.NetworkID) + driver.Lock() + delete(driver.networks, delreq.NetworkID) + driver.Unlock() return nil } @@ -96,6 +130,10 @@ func (driver *driver) EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointI func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error) { driver.logReq("JoinEndpoint", j, fmt.Sprintf("%s:%s to %s", j.NetworkID, j.EndpointID, j.SandboxKey)) + network, err := driver.findNetworkInfo(j.NetworkID) + if err != nil { + return nil, driver.error("JoinEndpoint", "unable to find network info: %s", err) + } name, peerName := vethPair(j.EndpointID) if _, err := weavenet.CreateAndAttachVeth(name, peerName, weavenet.WeaveBridgeName, 0, nil); err != nil { return nil, driver.error("JoinEndpoint", "%s", err) @@ -107,7 +145,7 @@ func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error DstPrefix: weavenet.VethName, }, } - if !driver.noMulticastRoute { + if network.hasMulticastRoute { multicastRoute := api.StaticRoute{ Destination: "224.0.0.0/4", RouteType: types.CONNECTED, @@ -118,6 +156,44 @@ func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error return response, nil } +func (driver *driver) findNetworkInfo(id string) (network, error) { + driver.Lock() + network, found := driver.networks[id] + driver.Unlock() + if found { + return network, nil + } + info, err := driver.docker.NetworkInfo(id) + if err != nil { + return network, err + } + return driver.setupNetworkInfo(id, info.Options) +} + +func (driver *driver) setupNetworkInfo(id string, options map[string]string) (network, error) { + var network network + for key, value := range options { + switch key { + case MulticastOption: + if value == "" { // interpret "--opt works.weave.multicast" as "turn it on" + network.hasMulticastRoute = true + } else { + var err error + if network.hasMulticastRoute, err = strconv.ParseBool(value); err != nil { + return network, fmt.Errorf("unrecognized value %q for option %s", value, key) + } + + } + default: + driver.warn("setupNetworkInfo", "unrecognized option: %s", key) + } + } + driver.Lock() + driver.networks[id] = network + driver.Unlock() + return network, nil +} + func (driver *driver) LeaveEndpoint(leave *api.LeaveRequest) error { driver.logReq("LeaveEndpoint", leave, fmt.Sprintf("%s:%s", leave.NetworkID, leave.EndpointID)) diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 3eb12a55..8ae47da1 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -42,7 +42,7 @@ func main() { flag.StringVar(&logLevel, "log-level", "info", "logging level (debug, info, warning, error)") flag.StringVar(&address, "socket", "/run/docker/plugins/weave.sock", "socket on which to listen") flag.StringVar(&meshAddress, "meshsocket", "/run/docker/plugins/weavemesh.sock", "socket on which to listen in mesh mode") - flag.BoolVar(&noMulticastRoute, "no-multicast-route", false, "do not add a multicast route to network endpoints") + flag.BoolVar(&noMulticastRoute, "no-multicast-route", false, "deprecated (this is now the default)") flag.Parse() @@ -73,18 +73,21 @@ func main() { } Log.Println("Weave plugin", version, "Command line options:", os.Args[1:]) + if noMulticastRoute { + Log.Warning("--no-multicast-route option has been removed; multicast is off by default") + } Log.Info(dockerClient.Info()) - err = run(dockerClient, weave, address, meshAddress, noMulticastRoute) + err = run(dockerClient, weave, address, meshAddress) if err != nil { Log.Fatal(err) } } -func run(dockerClient *docker.Client, weave *weaveapi.Client, address, meshAddress string, noMulticastRoute bool) error { +func run(dockerClient *docker.Client, weave *weaveapi.Client, address, meshAddress string) error { endChan := make(chan error, 1) if address != "" { - globalListener, err := listenAndServe(dockerClient, weave, address, noMulticastRoute, endChan, "global", false) + globalListener, err := listenAndServe(dockerClient, weave, address, endChan, "global", false) if err != nil { return err } @@ -92,7 +95,7 @@ func run(dockerClient *docker.Client, weave *weaveapi.Client, address, meshAddre defer globalListener.Close() } if meshAddress != "" { - meshListener, err := listenAndServe(dockerClient, weave, meshAddress, noMulticastRoute, endChan, "local", true) + meshListener, err := listenAndServe(dockerClient, weave, meshAddress, endChan, "local", true) if err != nil { return err } @@ -117,8 +120,8 @@ func run(dockerClient *docker.Client, weave *weaveapi.Client, address, meshAddre } } -func listenAndServe(dockerClient *docker.Client, weave *weaveapi.Client, address string, noMulticastRoute bool, endChan chan<- error, scope string, withIpam bool) (net.Listener, error) { - d, err := netplugin.New(dockerClient, weave, scope, noMulticastRoute) +func listenAndServe(dockerClient *docker.Client, weave *weaveapi.Client, address string, endChan chan<- error, scope string, withIpam bool) (net.Listener, error) { + d, err := netplugin.New(dockerClient, weave, scope) if err != nil { return nil, err } diff --git a/prog/weaveutil/plugin_network.go b/prog/weaveutil/plugin_network.go index a13d85f8..74891a11 100644 --- a/prog/weaveutil/plugin_network.go +++ b/prog/weaveutil/plugin_network.go @@ -4,7 +4,9 @@ package main import ( "fmt" - "github.com/fsouza/go-dockerclient" + docker "github.com/fsouza/go-dockerclient" + + "github.com/weaveworks/weave/plugin/net" ) func createPluginNetwork(args []string) error { @@ -23,6 +25,7 @@ func createPluginNetwork(args []string) error { CheckDuplicate: true, Driver: driverName, IPAM: docker.IPAMOptions{Driver: driverName}, + Options: map[string]interface{}{plugin.MulticastOption: "true"}, }) if err != docker.ErrNetworkAlreadyExists && err != nil { // Despite appearances to the contrary, CreateNetwork does diff --git a/site/plugin/plugin-how-it-works.md b/site/plugin/plugin-how-it-works.md index 6b5d8b96..b0561d05 100644 --- a/site/plugin/plugin-how-it-works.md +++ b/site/plugin/plugin-how-it-works.md @@ -26,6 +26,13 @@ store, but the first part of >**Note:** In the case of multiple networks using the `weave` driver, all containers are on the same virtual network but Docker allocates their addresses on different subnets so they cannot talk to each other directly. +The plugin accepts the following options via `docker network create ... --opt`: + + * `works.weave.multicast` -- tells weave to add a static IP + route for multicast traffic onto its interface. + +>**Note:** If you connect a container to multiple Weave networks, at + most one of them can have the multicast route enabled. **See Also** diff --git a/test/195_no_multicast_route_1_test.sh b/test/195_no_multicast_route_1_test.sh index a0d38ad7..0cbd46df 100755 --- a/test/195_no_multicast_route_1_test.sh +++ b/test/195_no_multicast_route_1_test.sh @@ -8,10 +8,13 @@ show_multicast_route_on() { start_suite "--no-multicast-route operation" +# In case one is left around from a previous test +$SSH $HOST1 docker network rm testmcasttrue testmcastfalse >/dev/null 2>&1 || true + # Ensure containers run either way have no multicast route weave_on $HOST1 launch-router weave_on $HOST1 launch-proxy --no-multicast-route -weave_on $HOST1 launch-plugin --no-multicast-route +weave_on $HOST1 launch-plugin # plugin defaults to multicast off except for 'net=weave' start_container $HOST1 --no-multicast-route --name c1 proxy_start_container $HOST1 --name c2 @@ -19,7 +22,16 @@ start_container_local_plugin $HOST1 --name=c3 assert "show_multicast_route_on $HOST1 c1" assert "show_multicast_route_on $HOST1 c2" -assert "show_multicast_route_on $HOST1 c3" +assert "show_multicast_route_on $HOST1 c3" "224.0.0.0/4 dev ethwe0 " + +# Now try via docker network options +# using ssh rather than docker -H because CircleCI docker client is older +$SSH $HOST1 docker network create --driver weavemesh --ipam-driver weavemesh --opt works.weave.multicast=false testmcastfalse +$SSH $HOST1 docker run --net=testmcastfalse --name c4 -di $SMALL_IMAGE /bin/sh +assert "show_multicast_route_on $HOST1 c4" +$SSH $HOST1 docker network create --driver weavemesh --ipam-driver weavemesh --opt works.weave.multicast testmcasttrue +$SSH $HOST1 docker run --net=testmcasttrue --name c5 -di $SMALL_IMAGE /bin/sh +assert "show_multicast_route_on $HOST1 c5" "224.0.0.0/4 dev ethwe0 " # Ensure current proxy options are obeyed on container start docker_on $HOST1 stop -t 1 c2 @@ -30,6 +42,7 @@ proxy docker_on $HOST1 start c2 assert "show_multicast_route_on $HOST1 c2" "224.0.0.0/4 dev ethwe " -docker_on $HOST1 rm -f c3 +docker_on $HOST1 rm -f c3 c4 c5 +$SSH $HOST1 docker network rm testmcasttrue testmcastfalse end_suite