diff --git a/libnetwork/cmd/readme_test/readme.go b/libnetwork/cmd/readme_test/readme.go new file mode 100644 index 0000000000..90e2409c8f --- /dev/null +++ b/libnetwork/cmd/readme_test/readme.go @@ -0,0 +1,53 @@ +package main + +import ( + "github.com/docker/libnetwork" + "github.com/docker/libnetwork/pkg/options" + "github.com/docker/libnetwork/sandbox" +) + +func main() { + // Create a new controller instance + controller := libnetwork.New() + + option := options.Generic{} + driver, err := controller.NewNetworkDriver("simplebridge", option) + if err != nil { + return + } + + netOptions := options.Generic{} + // Create a network for containers to join. + network, err := controller.NewNetwork(driver, "network1", netOptions) + if err != nil { + return + } + + // For a new container: create a sandbox instance (providing a unique key). + // For linux it is a filesystem path + networkPath := "/var/lib/docker/.../4d23e" + networkNamespace, err := sandbox.NewSandbox(networkPath) + if err != nil { + return + } + + // For each new container: allocate IP and interfaces. The returned network + // settings will be used for container infos (inspect and such), as well as + // iptables rules for port publishing. + _, sinfo, err := network.CreateEndpoint("Endpoint1", networkNamespace.Key(), "") + if err != nil { + return + } + + // Add interfaces to the namespace. + for _, iface := range sinfo.Interfaces { + if err := networkNamespace.AddInterface(iface); err != nil { + return + } + } + + // Set the gateway IP + if err := networkNamespace.SetGateway(sinfo.Gateway); err != nil { + return + } +} diff --git a/libnetwork/drivers/bridge/bridge.go b/libnetwork/drivers/bridge/bridge.go index c5d0b9ac42..7fcf179b05 100644 --- a/libnetwork/drivers/bridge/bridge.go +++ b/libnetwork/drivers/bridge/bridge.go @@ -156,7 +156,7 @@ func (d *driver) CreateNetwork(id driverapi.UUID, option interface{}) error { // We ensure that the bridge has the expectedIPv4 and IPv6 addresses in // the case of a previously existing device. - {bridgeAlreadyExists, setupVerifyConfiguredAddresses}, + {bridgeAlreadyExists, setupVerifyAndReconcile}, // Setup the bridge to allocate containers IPv4 addresses in the // specified subnet. diff --git a/libnetwork/drivers/bridge/setup_verify.go b/libnetwork/drivers/bridge/setup_verify.go index 0c995c3acc..a66cb63004 100644 --- a/libnetwork/drivers/bridge/setup_verify.go +++ b/libnetwork/drivers/bridge/setup_verify.go @@ -6,7 +6,7 @@ import ( "github.com/vishvananda/netlink" ) -func setupVerifyConfiguredAddresses(config *Configuration, i *bridgeInterface) error { +func setupVerifyAndReconcile(config *Configuration, i *bridgeInterface) error { // Fetch a single IPv4 and a slice of IPv6 addresses from the bridge. addrv4, addrsv6, err := i.addresses() if err != nil { @@ -29,6 +29,12 @@ func setupVerifyConfiguredAddresses(config *Configuration, i *bridgeInterface) e return fmt.Errorf("Bridge IPv6 addresses do not match the expected bridge configuration %s", bridgeIPv6) } + // By this time we have either configured a new bridge with an IP address + // or made sure an existing bridge's IP matches the configuration + // Now is the time to cache these states in the bridgeInterface. + i.bridgeIPv4 = addrv4.IPNet + i.bridgeIPv6 = bridgeIPv6 + return nil } diff --git a/libnetwork/error.go b/libnetwork/error.go new file mode 100644 index 0000000000..da2d3f18f8 --- /dev/null +++ b/libnetwork/error.go @@ -0,0 +1,63 @@ +package libnetwork + +import ( + "errors" + "fmt" +) + +var ( + // ErrNilNetworkDriver is returned if a nil network driver + // is passed to NewNetwork api. + ErrNilNetworkDriver = errors.New("nil NetworkDriver instance") + // ErrInvalidNetworkDriver is returned if an invalid driver + // instance is passed. + ErrInvalidNetworkDriver = errors.New("invalid driver bound to network") +) + +// NetworkTypeError type is returned when the network type string is not +// known to libnetwork. +type NetworkTypeError string + +func (nt NetworkTypeError) Error() string { + return fmt.Sprintf("unknown driver %q", string(nt)) +} + +// NetworkNameError is returned when a network with the same name already exists. +type NetworkNameError string + +func (name NetworkNameError) Error() string { + return fmt.Sprintf("network with name %s already exists", string(name)) +} + +// UnknownNetworkError is returned when libnetwork could not find in it's database +// a network with the same name and id. +type UnknownNetworkError struct { + name string + id string +} + +func (une *UnknownNetworkError) Error() string { + return fmt.Sprintf("unknown network %s id %s", une.name, une.id) +} + +// ActiveEndpointsError is returned when a network is deleted which has active +// endpoints in it. +type ActiveEndpointsError struct { + name string + id string +} + +func (aee *ActiveEndpointsError) Error() string { + return fmt.Sprintf("network with name %s id %s has active endpoints", aee.name, aee.id) +} + +// UnknownEndpointError is returned when libnetwork could not find in it's database +// an endpoint with the same name and id. +type UnknownEndpointError struct { + name string + id string +} + +func (uee *UnknownEndpointError) Error() string { + return fmt.Sprintf("unknown endpoint %s id %s", uee.name, uee.id) +} diff --git a/libnetwork/libnetwork_test.go b/libnetwork/libnetwork_test.go index f122ef229c..2f6a1006e5 100644 --- a/libnetwork/libnetwork_test.go +++ b/libnetwork/libnetwork_test.go @@ -8,15 +8,27 @@ import ( "github.com/docker/libnetwork" _ "github.com/docker/libnetwork/drivers/bridge" "github.com/docker/libnetwork/pkg/options" - "github.com/vishvananda/netlink" ) var bridgeName = "dockertest0" -func TestSimplebridge(t *testing.T) { - bridge := &netlink.Bridge{LinkAttrs: netlink.LinkAttrs{Name: bridgeName}} - netlink.LinkDel(bridge) +func createTestNetwork(networkType, networkName string, option options.Generic) (libnetwork.Network, error) { + controller := libnetwork.New() + driver, err := controller.NewNetworkDriver(networkType, option) + if err != nil { + return nil, err + } + + network, err := controller.NewNetwork(driver, networkName, "") + if err != nil { + return nil, err + } + + return network, nil +} + +func TestSimplebridge(t *testing.T) { ip, subnet, err := net.ParseCIDR("192.168.100.1/24") if err != nil { t.Fatal(err) @@ -36,7 +48,7 @@ func TestSimplebridge(t *testing.T) { cidrv6.IP = ip log.Debug("Adding a simple bridge") - options := options.Generic{ + option := options.Generic{ "BridgeName": bridgeName, "AddressIPv4": subnet, "FixedCIDR": cidr, @@ -48,14 +60,7 @@ func TestSimplebridge(t *testing.T) { "EnableIPForwarding": true, "AllowNonDefaultBridge": true} - controller := libnetwork.New() - - driver, err := controller.NewNetworkDriver("simplebridge", options) - if err != nil { - t.Fatal(err) - } - - network, err := controller.NewNetwork(driver, "testnetwork", "") + network, err := createTestNetwork("simplebridge", "testnetwork", option) if err != nil { t.Fatal(err) } @@ -73,3 +78,206 @@ func TestSimplebridge(t *testing.T) { t.Fatal(err) } } + +func TestUnknownDriver(t *testing.T) { + _, err := createTestNetwork("unknowndriver", "testnetwork", options.Generic{}) + if err == nil { + t.Fatal("Expected to fail. But instead succeeded") + } + + if _, ok := err.(libnetwork.NetworkTypeError); !ok { + t.Fatalf("Did not fail with expected error. Actual error: %v", err) + } +} + +func TestNilDriver(t *testing.T) { + controller := libnetwork.New() + + option := options.Generic{} + _, err := controller.NewNetwork(nil, "dummy", option) + if err == nil { + t.Fatal("Expected to fail. But instead succeeded") + } + + if err != libnetwork.ErrNilNetworkDriver { + t.Fatalf("Did not fail with expected error. Actual error: %v", err) + } +} + +func TestNoInitDriver(t *testing.T) { + controller := libnetwork.New() + + option := options.Generic{} + _, err := controller.NewNetwork(&libnetwork.NetworkDriver{}, "dummy", option) + if err == nil { + t.Fatal("Expected to fail. But instead succeeded") + } + + if err != libnetwork.ErrInvalidNetworkDriver { + t.Fatalf("Did not fail with expected error. Actual error: %v", err) + } +} + +func TestDuplicateNetwork(t *testing.T) { + controller := libnetwork.New() + + option := options.Generic{} + driver, err := controller.NewNetworkDriver("simplebridge", option) + if err != nil { + t.Fatal(err) + } + + _, err = controller.NewNetwork(driver, "testnetwork", "") + if err != nil { + t.Fatal(err) + } + + _, err = controller.NewNetwork(driver, "testnetwork", "") + if err == nil { + t.Fatal("Expected to fail. But instead succeeded") + } + + if _, ok := err.(libnetwork.NetworkNameError); !ok { + t.Fatalf("Did not fail with expected error. Actual error: %v", err) + } +} + +func TestNetworkName(t *testing.T) { + networkName := "testnetwork" + + n, err := createTestNetwork("simplebridge", networkName, options.Generic{}) + if err != nil { + t.Fatal(err) + } + + if n.Name() != networkName { + t.Fatalf("Expected network name %s, got %s", networkName, n.Name()) + } +} + +func TestNetworkType(t *testing.T) { + networkType := "simplebridge" + + n, err := createTestNetwork(networkType, "testnetwork", options.Generic{}) + if err != nil { + t.Fatal(err) + } + + if n.Type() != networkType { + t.Fatalf("Expected network type %s, got %s", networkType, n.Type()) + } +} + +func TestNetworkID(t *testing.T) { + networkType := "simplebridge" + + n, err := createTestNetwork(networkType, "testnetwork", options.Generic{}) + if err != nil { + t.Fatal(err) + } + + if n.ID() == "" { + t.Fatal("Expected non-empty network id") + } +} + +func TestDeleteNetworkWithActiveEndpoints(t *testing.T) { + option := options.Generic{ + "BridgeName": bridgeName, + "AllowNonDefaultBridge": true} + + network, err := createTestNetwork("simplebridge", "testnetwork", option) + if err != nil { + t.Fatal(err) + } + + ep, _, err := network.CreateEndpoint("testep", "", "") + if err != nil { + t.Fatal(err) + } + + err = network.Delete() + if err == nil { + t.Fatal("Expected to fail. But instead succeeded") + } + + if _, ok := err.(*libnetwork.ActiveEndpointsError); !ok { + t.Fatalf("Did not fail with expected error. Actual error: %v", err) + } + + // Done testing. Now cleanup. + if err := ep.Delete(); err != nil { + t.Fatal(err) + } + + if err := network.Delete(); err != nil { + t.Fatal(err) + } +} + +func TestUnknownNetwork(t *testing.T) { + option := options.Generic{ + "BridgeName": bridgeName, + "AllowNonDefaultBridge": true} + + network, err := createTestNetwork("simplebridge", "testnetwork", option) + if err != nil { + t.Fatal(err) + } + + err = network.Delete() + if err != nil { + t.Fatal(err) + } + + err = network.Delete() + if err == nil { + t.Fatal("Expected to fail. But instead succeeded") + } + + if _, ok := err.(*libnetwork.UnknownNetworkError); !ok { + t.Fatalf("Did not fail with expected error. Actual error: %v", err) + } +} + +func TestUnknownEndpoint(t *testing.T) { + ip, subnet, err := net.ParseCIDR("192.168.100.1/24") + if err != nil { + t.Fatal(err) + } + subnet.IP = ip + + option := options.Generic{ + "BridgeName": bridgeName, + "AddressIPv4": subnet, + "AllowNonDefaultBridge": true} + + network, err := createTestNetwork("simplebridge", "testnetwork", option) + if err != nil { + t.Fatal(err) + } + + ep, _, err := network.CreateEndpoint("testep", "", "") + if err != nil { + t.Fatal(err) + } + + err = ep.Delete() + if err != nil { + t.Fatal(err) + } + + err = ep.Delete() + if err == nil { + t.Fatal("Expected to fail. But instead succeeded") + } + + if _, ok := err.(*libnetwork.UnknownEndpointError); !ok { + t.Fatalf("Did not fail with expected error. Actual error: %v", err) + } + + // Done testing. Now cleanup + if err := network.Delete(); err != nil { + t.Fatal(err) + } +} diff --git a/libnetwork/network.go b/libnetwork/network.go index ff0a4dead3..ee6836c3ee 100644 --- a/libnetwork/network.go +++ b/libnetwork/network.go @@ -39,7 +39,6 @@ create network namespaces and allocate interfaces for containers to use. package libnetwork import ( - "fmt" "sync" "github.com/docker/docker/pkg/common" @@ -84,6 +83,7 @@ type Endpoint interface { // NetworkDriver provides a reference to driver and way to push driver specific config type NetworkDriver struct { + networkType string internalDriver driverapi.Driver } @@ -121,19 +121,32 @@ func New() NetworkController { func (c *controller) NewNetworkDriver(networkType string, options interface{}) (*NetworkDriver, error) { d, ok := c.drivers[networkType] if !ok { - return nil, fmt.Errorf("unknown driver %q", networkType) + return nil, NetworkTypeError(networkType) } if err := d.Config(options); err != nil { return nil, err } - return &NetworkDriver{internalDriver: d}, nil + return &NetworkDriver{networkType: networkType, internalDriver: d}, nil } -// NewNetwork creates a new network of the specified networkType. The options +// NewNetwork creates a new network of the specified NetworkDriver. The options // are driver specific and modeled in a generic way. func (c *controller) NewNetwork(nd *NetworkDriver, name string, options interface{}) (Network, error) { + if nd == nil { + return nil, ErrNilNetworkDriver + } + + c.Lock() + for _, n := range c.networks { + if n.name == name { + c.Unlock() + return nil, NetworkNameError(name) + } + } + c.Unlock() + network := &network{ name: name, id: driverapi.UUID(common.GenerateRandomID()), @@ -143,7 +156,7 @@ func (c *controller) NewNetwork(nd *NetworkDriver, name string, options interfac d := network.driver.internalDriver if d == nil { - return nil, fmt.Errorf("invalid driver bound to network") + return nil, ErrInvalidNetworkDriver } if err := d.CreateNetwork(network.id, options); err != nil { @@ -166,7 +179,11 @@ func (n *network) ID() string { } func (n *network) Type() string { - return n.networkType + if n.driver == nil { + return "" + } + + return n.driver.networkType } func (n *network) Delete() error { @@ -176,7 +193,7 @@ func (n *network) Delete() error { _, ok := n.ctrlr.networks[n.id] if !ok { n.ctrlr.Unlock() - return fmt.Errorf("unknown network %s id %s", n.name, n.id) + return &UnknownNetworkError{name: n.name, id: string(n.id)} } n.Lock() @@ -184,7 +201,7 @@ func (n *network) Delete() error { n.Unlock() if numEps != 0 { n.ctrlr.Unlock() - return fmt.Errorf("network %s has active endpoints", n.id) + return &ActiveEndpointsError{name: n.name, id: string(n.id)} } delete(n.ctrlr.networks, n.id) @@ -228,7 +245,7 @@ func (ep *endpoint) Delete() error { _, ok := n.endpoints[ep.id] if !ok { n.Unlock() - return fmt.Errorf("unknown endpoint %s id %s", ep.name, ep.id) + return &UnknownEndpointError{name: ep.name, id: string(ep.id)} } delete(n.endpoints, ep.id)