Network remote APIs using new router, --net=<user-defined-network> changes

* Moving Network Remote APIs out of experimental
* --net can now accept user created networks using network drivers/plugins
* Removed the experimental services concept and --default-network option
* Neccessary backend changes to accomodate multiple networks per container
* Integration Tests

Signed-off-by: David Calavera <david.calavera@gmail.com>
Signed-off-by: Madhu Venugopal <madhu@docker.com>
This commit is contained in:
Madhu Venugopal 2015-09-25 03:19:17 -07:00
parent aac5c44c10
commit 2ab94e11a2
29 changed files with 966 additions and 1380 deletions

View file

@ -5,11 +5,9 @@ import (
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/server/httputils"
dkrouter "github.com/docker/docker/api/server/router"
"github.com/docker/docker/daemon"
"github.com/gorilla/mux"
)
// router is a docker router that talks with the local docker daemon.
@ -31,11 +29,14 @@ func (l localRoute) Handler() httputils.APIFunc {
return l.handler
}
// Register adds the filtered handler to the mux.
func (l localRoute) Register(m *mux.Router, handler http.Handler) {
logrus.Debugf("Registering %s, %s", l.method, l.path)
m.Path(dkrouter.VersionMatcher + l.path).Methods(l.method).Handler(handler)
m.Path(l.path).Methods(l.method).Handler(handler)
// Method returns the http method that the route responds to.
func (l localRoute) Method() string {
return l.method
}
// Path returns the subpath where the route responds to.
func (l localRoute) Path() string {
return l.path
}
// NewRoute initialies a new local route for the reouter

View file

@ -1,26 +1,41 @@
package network
import (
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/server/router"
"github.com/docker/docker/api/server/router/local"
"github.com/docker/docker/daemon"
)
// networkRouter is a router to talk with the network controller
type networkRouter struct {
daemon *daemon.Daemon
routes []router.Route
}
// NewRouter initializes a new network router
func NewRouter(d *daemon.Daemon) router.Router {
r := &networkRouter{
daemon: d,
}
r.initRoutes()
return r
}
// Routes returns the available routes to the network controller
func (n networkRouter) Routes() []router.Route {
return n.routes
func (r *networkRouter) Routes() []router.Route {
return r.routes
}
type networkRoute struct {
path string
handler httputils.APIFunc
}
// Handler returns the APIFunc to let the server wrap it in middlewares
func (l networkRoute) Handler() httputils.APIFunc {
return l.handler
func (r *networkRouter) initRoutes() {
r.routes = []router.Route{
// GET
local.NewGetRoute("/networks", r.getNetworksList),
local.NewGetRoute("/networks/{id:.*}", r.getNetwork),
// POST
local.NewPostRoute("/networks/create", r.postNetworkCreate),
local.NewPostRoute("/networks/{id:.*}/connect", r.postNetworkConnect),
local.NewPostRoute("/networks/{id:.*}/disconnect", r.postNetworkDisconnect),
// DELETE
local.NewDeleteRoute("/networks/{id:.*}", r.deleteNetwork),
}
}

View file

@ -1,51 +0,0 @@
// +build experimental
package network
import (
"net/http"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/server/router"
"github.com/docker/docker/daemon"
"github.com/docker/libnetwork/api"
"github.com/gorilla/mux"
)
var httpMethods = []string{"GET", "POST", "PUT", "DELETE"}
// NewRouter initializes a new network router
func NewRouter(d *daemon.Daemon) router.Router {
c := d.NetworkController()
if c == nil {
return networkRouter{}
}
var routes []router.Route
netHandler := api.NewHTTPHandler(c)
// TODO: libnetwork should stop hijacking request/response.
// It should define API functions to add normally to the router.
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
netHandler(w, r)
return nil
}
for _, path := range []string{"/networks", "/services", "/sandboxes"} {
routes = append(routes, networkRoute{path, handler})
}
return networkRouter{routes}
}
// Register adds the filtered handler to the mux.
func (n networkRoute) Register(m *mux.Router, handler http.Handler) {
logrus.Debugf("Registering %s, %v", n.path, httpMethods)
subrouter := m.PathPrefix(router.VersionMatcher + n.path).Subrouter()
subrouter.Methods(httpMethods...).Handler(handler)
subrouter = m.PathPrefix(n.path).Subrouter()
subrouter.Methods(httpMethods...).Handler(handler)
}

View file

@ -0,0 +1,216 @@
package network
import (
"encoding/json"
"fmt"
"net/http"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types"
"github.com/docker/docker/daemon"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/libnetwork"
)
func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := httputils.ParseForm(r); err != nil {
return err
}
filter := r.Form.Get("filters")
netFilters, err := filters.FromParam(filter)
if err != nil {
return err
}
list := []*types.NetworkResource{}
var nameFilter, idFilter bool
var names, ids []string
if names, nameFilter = netFilters["name"]; nameFilter {
for _, name := range names {
if nw, err := n.daemon.GetNetwork(name, daemon.NetworkByName); err == nil {
list = append(list, buildNetworkResource(nw))
} else {
logrus.Errorf("failed to get network for filter=%s : %v", name, err)
}
}
}
if ids, idFilter = netFilters["id"]; idFilter {
for _, id := range ids {
for _, nw := range n.daemon.GetNetworksByID(id) {
list = append(list, buildNetworkResource(nw))
}
}
}
if !nameFilter && !idFilter {
nwList := n.daemon.GetNetworksByID("")
for _, nw := range nwList {
list = append(list, buildNetworkResource(nw))
}
}
return httputils.WriteJSON(w, http.StatusOK, list)
}
func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := httputils.ParseForm(r); err != nil {
return err
}
nw, err := n.daemon.FindNetwork(vars["id"])
if err != nil {
return err
}
return httputils.WriteJSON(w, http.StatusOK, buildNetworkResource(nw))
}
func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
var create types.NetworkCreate
var warning string
if err := httputils.ParseForm(r); err != nil {
return err
}
if err := httputils.CheckForJSON(r); err != nil {
return err
}
if err := json.NewDecoder(r.Body).Decode(&create); err != nil {
return err
}
nw, err := n.daemon.GetNetwork(create.Name, daemon.NetworkByName)
if _, ok := err.(libnetwork.ErrNoSuchNetwork); err != nil && !ok {
return err
}
if nw != nil {
if create.CheckDuplicate {
return libnetwork.NetworkNameError(create.Name)
}
warning = fmt.Sprintf("Network with name %s (id : %s) already exists", nw.Name(), nw.ID())
}
nw, err = n.daemon.CreateNetwork(create.Name, create.Driver, create.Options)
if err != nil {
return err
}
return httputils.WriteJSON(w, http.StatusCreated, &types.NetworkCreateResponse{
ID: nw.ID(),
Warning: warning,
})
}
func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
var connect types.NetworkConnect
if err := httputils.ParseForm(r); err != nil {
return err
}
if err := httputils.CheckForJSON(r); err != nil {
return err
}
if err := json.NewDecoder(r.Body).Decode(&connect); err != nil {
return err
}
nw, err := n.daemon.FindNetwork(vars["id"])
if err != nil {
return err
}
container, err := n.daemon.Get(connect.Container)
if err != nil {
return fmt.Errorf("invalid container %s : %v", container, err)
}
return container.ConnectToNetwork(nw.Name())
}
func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
var disconnect types.NetworkDisconnect
if err := httputils.ParseForm(r); err != nil {
return err
}
if err := httputils.CheckForJSON(r); err != nil {
return err
}
if err := json.NewDecoder(r.Body).Decode(&disconnect); err != nil {
return err
}
nw, err := n.daemon.FindNetwork(vars["id"])
if err != nil {
return err
}
container, err := n.daemon.Get(disconnect.Container)
if err != nil {
return fmt.Errorf("invalid container %s : %v", container, err)
}
return container.DisconnectFromNetwork(nw)
}
func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := httputils.ParseForm(r); err != nil {
return err
}
nw, err := n.daemon.FindNetwork(vars["id"])
if err != nil {
return err
}
return nw.Delete()
}
func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
r := &types.NetworkResource{}
if nw == nil {
return r
}
r.Name = nw.Name()
r.ID = nw.ID()
r.Driver = nw.Type()
r.Containers = make(map[string]types.EndpointResource)
epl := nw.Endpoints()
for _, e := range epl {
sb := e.Info().Sandbox()
if sb == nil {
continue
}
r.Containers[sb.ContainerID()] = buildEndpointResource(e)
}
return r
}
func buildEndpointResource(e libnetwork.Endpoint) types.EndpointResource {
er := types.EndpointResource{}
if e == nil {
return er
}
er.EndpointID = e.ID()
if iface := e.Info().Iface(); iface != nil {
if mac := iface.MacAddress(); mac != nil {
er.MacAddress = mac.String()
}
if ip := iface.Address(); len(ip.IP) > 0 {
er.IPv4Address = (&ip).String()
}
if ipv6 := iface.AddressIPv6(); len(ipv6.IP) > 0 {
er.IPv6Address = (&ipv6).String()
}
}
return er
}

View file

@ -1,20 +0,0 @@
// +build !experimental
package network
import (
"net/http"
"github.com/docker/docker/api/server/router"
"github.com/docker/docker/daemon"
"github.com/gorilla/mux"
)
// NewRouter initializes a new network router
func NewRouter(d *daemon.Daemon) router.Router {
return networkRouter{}
}
// Register adds the filtered handler to the mux.
func (n networkRoute) Register(m *mux.Router, handler http.Handler) {
}

View file

@ -1,15 +1,6 @@
package router
import (
"net/http"
"github.com/docker/docker/api/server/httputils"
"github.com/gorilla/mux"
)
// VersionMatcher defines a variable matcher to be parsed by the router
// when a request is about to be served.
const VersionMatcher = "/v{version:[0-9.]+}"
import "github.com/docker/docker/api/server/httputils"
// Router defines an interface to specify a group of routes to add the the docker server.
type Router interface {
@ -18,8 +9,10 @@ type Router interface {
// Route defines an individual API route in the docker server.
type Route interface {
// Register adds the handler route to the docker mux.
Register(*mux.Router, http.Handler)
// Handler returns the raw function to create the http handler.
Handler() httputils.APIFunc
// Method returns the http method that the route responds to.
Method() string
// Path returns the subpath where the route responds to.
Path() string
}

View file

@ -19,6 +19,10 @@ import (
"golang.org/x/net/context"
)
// versionMatcher defines a variable matcher to be parsed by the router
// when a request is about to be served.
const versionMatcher = "/v{version:[0-9.]+}"
// Config provides the configuration for the API server
type Config struct {
Logging bool
@ -177,10 +181,13 @@ func (s *Server) CreateMux() *mux.Router {
}
logrus.Debugf("Registering routers")
for _, router := range s.routers {
for _, r := range router.Routes() {
for _, apiRouter := range s.routers {
for _, r := range apiRouter.Routes() {
f := s.makeHTTPHandler(r.Handler())
r.Register(m, f)
logrus.Debugf("Registering %s, %s", r.Method(), r.Path())
m.Path(versionMatcher + r.Path()).Methods(r.Method()).Handler(f)
m.Path(r.Path()).Methods(r.Method()).Handler(f)
}
}

View file

@ -308,3 +308,44 @@ type VolumeCreateRequest struct {
Driver string // Driver is the name of the driver that should be used to create the volume
DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume.
}
// NetworkResource is the body of the "get network" http response message
type NetworkResource struct {
Name string `json:"name"`
ID string `json:"id"`
Driver string `json:"driver"`
Containers map[string]EndpointResource `json:"containers"`
Options map[string]interface{} `json:"options,omitempty"`
}
//EndpointResource contains network resources allocated and usd for a container in a network
type EndpointResource struct {
EndpointID string `json:"endpoint"`
MacAddress string `json:"mac_address"`
IPv4Address string `json:"ipv4_address"`
IPv6Address string `json:"ipv6_address"`
}
// NetworkCreate is the expected body of the "create network" http request message
type NetworkCreate struct {
Name string `json:"name"`
CheckDuplicate bool `json:"check_duplicate"`
Driver string `json:"driver"`
Options map[string]interface{} `json:"options"`
}
// NetworkCreateResponse is the response message sent by the server for network create call
type NetworkCreateResponse struct {
ID string `json:"id"`
Warning string `json:"warning"`
}
// NetworkConnect represents the data to be used to connect a container to the network
type NetworkConnect struct {
Container string `json:"container"`
}
// NetworkDisconnect represents the data to be used to disconnect a container from the network
type NetworkDisconnect struct {
Container string `json:"container"`
}

View file

@ -1,9 +0,0 @@
// +build experimental
package daemon
import flag "github.com/docker/docker/pkg/mflag"
func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) {
cmd.StringVar(&config.DefaultNetwork, []string{"-default-network"}, "", usageFn("Set default network"))
}

View file

@ -1,8 +0,0 @@
// +build !experimental
package daemon
import flag "github.com/docker/docker/pkg/mflag"
func (config *Config) attachExperimentalFlags(cmd *flag.FlagSet, usageFn func(string) string) {
}

View file

@ -77,6 +77,4 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin
cmd.BoolVar(&config.Bridge.EnableUserlandProxy, []string{"-userland-proxy"}, true, usageFn("Use userland proxy for loopback traffic"))
cmd.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, usageFn("Enable CORS headers in the remote API, this is deprecated by --api-cors-header"))
cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API"))
config.attachExperimentalFlags(cmd, usageFn)
}

View file

@ -412,7 +412,7 @@ func (container *Container) buildHostnameFile() error {
return ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
}
func (container *Container) buildSandboxOptions() ([]libnetwork.SandboxOption, error) {
func (container *Container) buildSandboxOptions(n libnetwork.Network) ([]libnetwork.SandboxOption, error) {
var (
sboxOptions []libnetwork.SandboxOption
err error
@ -487,6 +487,23 @@ func (container *Container) buildSandboxOptions() ([]libnetwork.SandboxOption, e
}
}
for _, extraHost := range container.hostConfig.ExtraHosts {
// allow IPv6 addresses in extra hosts; only split on first ":"
parts := strings.SplitN(extraHost, ":", 2)
sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(parts[0], parts[1]))
}
// Link feature is supported only for the default bridge network.
// return if this call to build join options is not for default bridge network
if n.Name() != "bridge" {
return sboxOptions, nil
}
ep, _ := container.getEndpointInNetwork(n)
if ep == nil {
return sboxOptions, nil
}
var childEndpoints, parentEndpoints []string
children, err := container.daemon.children(container.Name)
@ -503,17 +520,12 @@ func (container *Container) buildSandboxOptions() ([]libnetwork.SandboxOption, e
aliasList = aliasList + " " + child.Name[1:]
}
sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(aliasList, child.NetworkSettings.IPAddress))
if child.NetworkSettings.EndpointID != "" {
childEndpoints = append(childEndpoints, child.NetworkSettings.EndpointID)
cEndpoint, _ := child.getEndpointInNetwork(n)
if cEndpoint != nil && cEndpoint.ID() != "" {
childEndpoints = append(childEndpoints, cEndpoint.ID())
}
}
for _, extraHost := range container.hostConfig.ExtraHosts {
// allow IPv6 addresses in extra hosts; only split on first ":"
parts := strings.SplitN(extraHost, ":", 2)
sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(parts[0], parts[1]))
}
refs := container.daemon.containerGraph().RefPaths(container.ID)
for _, ref := range refs {
if ref.ParentID == "0" {
@ -528,8 +540,8 @@ func (container *Container) buildSandboxOptions() ([]libnetwork.SandboxOption, e
if c != nil && !container.daemon.configStore.DisableBridge && container.hostConfig.NetworkMode.IsPrivate() {
logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, container.NetworkSettings.IPAddress)
sboxOptions = append(sboxOptions, libnetwork.OptionParentUpdate(c.ID, ref.Name, container.NetworkSettings.IPAddress))
if c.NetworkSettings.EndpointID != "" {
parentEndpoints = append(parentEndpoints, c.NetworkSettings.EndpointID)
if ep.ID() != "" {
parentEndpoints = append(parentEndpoints, ep.ID())
}
}
}
@ -546,6 +558,11 @@ func (container *Container) buildSandboxOptions() ([]libnetwork.SandboxOption, e
return sboxOptions, nil
}
func (container *Container) getEndpointInNetwork(n libnetwork.Network) (libnetwork.Endpoint, error) {
endpointName := strings.TrimPrefix(container.Name, "/")
return n.EndpointByName(endpointName)
}
func (container *Container) buildPortMapInfo(ep libnetwork.Endpoint, networkSettings *network.Settings) (*network.Settings, error) {
if ep == nil {
return nil, derr.ErrorCodeEmptyEndpoint
@ -650,10 +667,38 @@ func (container *Container) updateJoinInfo(ep libnetwork.Endpoint) error {
return nil
}
func (container *Container) updateEndpointNetworkSettings(n libnetwork.Network, ep libnetwork.Endpoint) error {
networkSettings := &network.Settings{NetworkID: n.ID(), EndpointID: ep.ID()}
func (container *Container) updateNetworkSettings(n libnetwork.Network) error {
if container.NetworkSettings == nil {
container.NetworkSettings = &network.Settings{Networks: []string{}}
}
settings := container.NetworkSettings
networkSettings, err := container.buildPortMapInfo(ep, networkSettings)
for _, s := range settings.Networks {
sn, err := container.daemon.FindNetwork(s)
if err != nil {
continue
}
if sn.Name() == n.Name() {
// Avoid duplicate config
return nil
}
if !runconfig.NetworkMode(sn.Type()).IsPrivate() ||
!runconfig.NetworkMode(n.Type()).IsPrivate() {
return runconfig.ErrConflictSharedNetwork
}
if runconfig.NetworkMode(sn.Name()).IsNone() ||
runconfig.NetworkMode(n.Name()).IsNone() {
return runconfig.ErrConflictNoNetwork
}
}
settings.Networks = append(settings.Networks, n.Name())
return nil
}
func (container *Container) updateEndpointNetworkSettings(n libnetwork.Network, ep libnetwork.Endpoint) error {
networkSettings, err := container.buildPortMapInfo(ep, container.NetworkSettings)
if err != nil {
return err
}
@ -667,7 +712,6 @@ func (container *Container) updateEndpointNetworkSettings(n libnetwork.Network,
networkSettings.Bridge = container.daemon.configStore.Bridge.Iface
}
container.NetworkSettings = networkSettings
return nil
}
@ -688,7 +732,25 @@ func (container *Container) updateNetwork() error {
return derr.ErrorCodeNoSandbox.WithArgs(sid, err)
}
options, err := container.buildSandboxOptions()
// Find if container is connected to the default bridge network
var n libnetwork.Network
for _, name := range container.NetworkSettings.Networks {
sn, err := container.daemon.FindNetwork(name)
if err != nil {
continue
}
if sn.Name() == "bridge" {
n = sn
break
}
}
if n == nil {
// Not connected to the default bridge network; Nothing to do
return nil
}
options, err := container.buildSandboxOptions(n)
if err != nil {
return derr.ErrorCodeNetworkUpdate.WithArgs(err)
}
@ -781,20 +843,6 @@ func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointO
return createOptions, nil
}
func parseService(controller libnetwork.NetworkController, service string) (string, string, string) {
dn := controller.Config().Daemon.DefaultNetwork
dd := controller.Config().Daemon.DefaultDriver
snd := strings.Split(service, ".")
if len(snd) > 2 {
return strings.Join(snd[:len(snd)-2], "."), snd[len(snd)-2], snd[len(snd)-1]
}
if len(snd) > 1 {
return snd[0], snd[1], dd
}
return snd[0], dn, dd
}
func createNetwork(controller libnetwork.NetworkController, dnet string, driver string) (libnetwork.Network, error) {
createOptions := []libnetwork.NetworkOption{}
genericOption := options.Generic{}
@ -813,61 +861,69 @@ func createNetwork(controller libnetwork.NetworkController, dnet string, driver
}
func (container *Container) allocateNetwork() error {
mode := container.hostConfig.NetworkMode
controller := container.daemon.netController
if container.Config.NetworkDisabled || mode.IsContainer() {
return nil
}
networkDriver := string(mode)
service := container.Config.PublishService
networkName := mode.NetworkName()
if mode.IsDefault() {
if service != "" {
service, networkName, networkDriver = parseService(controller, service)
} else {
networkName = controller.Config().Daemon.DefaultNetwork
networkDriver = controller.Config().Daemon.DefaultDriver
settings := container.NetworkSettings.Networks
updateSettings := false
if settings == nil {
mode := container.hostConfig.NetworkMode
controller := container.daemon.netController
if container.Config.NetworkDisabled || mode.IsContainer() {
return nil
}
} else if service != "" {
return derr.ErrorCodeNetworkConflict
networkName := mode.NetworkName()
if mode.IsDefault() {
networkName = controller.Config().Daemon.DefaultNetwork
}
settings = []string{networkName}
updateSettings = true
}
if runconfig.NetworkMode(networkDriver).IsBridge() && container.daemon.configStore.DisableBridge {
container.Config.NetworkDisabled = true
return nil
}
if service == "" {
// dot character "." has a special meaning to support SERVICE[.NETWORK] format.
// For backward compatibility, replacing "." with "-", instead of failing
service = strings.Replace(container.Name, ".", "-", -1)
// Service names dont like "/" in them. removing it instead of failing for backward compatibility
service = strings.Replace(service, "/", "", -1)
}
if err := container.configureNetwork(networkName, service, networkDriver, mode.IsDefault()); err != nil {
return err
for _, n := range settings {
if err := container.connectToNetwork(n, updateSettings); err != nil {
if updateSettings {
return err
}
// dont fail a container restart case if the user removed the network
logrus.Warnf("Could not connect container %s : %v", container.ID, err)
}
}
return container.writeHostConfig()
}
func (container *Container) configureNetwork(networkName, service, networkDriver string, canCreateNetwork bool) error {
// ConnectToNetwork connects a container to a netork
func (container *Container) ConnectToNetwork(idOrName string) error {
if !container.Running {
return derr.ErrorCodeNotRunning.WithArgs(container.ID)
}
return container.connectToNetwork(idOrName, true)
}
func (container *Container) connectToNetwork(idOrName string, updateSettings bool) error {
if container.hostConfig.NetworkMode.IsContainer() {
return runconfig.ErrConflictSharedNetwork
}
if runconfig.NetworkMode(idOrName).IsBridge() &&
container.daemon.configStore.DisableBridge {
container.Config.NetworkDisabled = true
return nil
}
controller := container.daemon.netController
n, err := controller.NetworkByName(networkName)
n, err := container.daemon.FindNetwork(idOrName)
if err != nil {
if _, ok := err.(libnetwork.ErrNoSuchNetwork); !ok || !canCreateNetwork {
return err
}
return err
}
if n, err = createNetwork(controller, networkName, networkDriver); err != nil {
if updateSettings {
if err := container.updateNetworkSettings(n); err != nil {
return err
}
}
ep, err := n.EndpointByName(service)
ep, err := container.getEndpointInNetwork(n)
if err != nil {
if _, ok := err.(libnetwork.ErrNoSuchEndpoint); !ok {
return err
@ -878,7 +934,8 @@ func (container *Container) configureNetwork(networkName, service, networkDriver
return err
}
ep, err = n.CreateEndpoint(service, createOptions...)
endpointName := strings.TrimPrefix(container.Name, "/")
ep, err = n.CreateEndpoint(endpointName, createOptions...)
if err != nil {
return err
}
@ -897,7 +954,7 @@ func (container *Container) configureNetwork(networkName, service, networkDriver
return false
})
if sb == nil {
options, err := container.buildSandboxOptions()
options, err := container.buildSandboxOptions(n)
if err != nil {
return err
}
@ -1039,12 +1096,11 @@ func (container *Container) releaseNetwork() {
}
sid := container.NetworkSettings.SandboxID
eid := container.NetworkSettings.EndpointID
nid := container.NetworkSettings.NetworkID
networks := container.NetworkSettings.Networks
container.NetworkSettings = &network.Settings{}
container.NetworkSettings = &network.Settings{Networks: networks}
if sid == "" || nid == "" || eid == "" {
if sid == "" || len(networks) == 0 {
return
}
@ -1054,29 +1110,73 @@ func (container *Container) releaseNetwork() {
return
}
n, err := container.daemon.netController.NetworkByID(nid)
if err != nil {
logrus.Errorf("error locating network id %s: %v", nid, err)
return
}
ep, err := n.EndpointByID(eid)
if err != nil {
logrus.Errorf("error locating endpoint id %s: %v", eid, err)
return
for _, ns := range networks {
n, err := container.daemon.FindNetwork(ns)
if err != nil {
continue
}
container.disconnectFromNetwork(n, false)
}
if err := sb.Delete(); err != nil {
logrus.Errorf("Error deleting sandbox id %s for container %s: %v", sid, container.ID, err)
return
}
}
// DisconnectFromNetwork disconnects a container from a network
func (container *Container) DisconnectFromNetwork(n libnetwork.Network) error {
if !container.Running {
return derr.ErrorCodeNotRunning.WithArgs(container.ID)
}
// In addition to leaving all endpoints, delete implicitly created endpoint
if container.Config.PublishService == "" {
if err := ep.Delete(); err != nil {
logrus.Errorf("deleting endpoint failed: %v", err)
return container.disconnectFromNetwork(n, true)
}
func (container *Container) disconnectFromNetwork(n libnetwork.Network, updateSettings bool) error {
var (
ep libnetwork.Endpoint
sbox libnetwork.Sandbox
)
s := func(current libnetwork.Endpoint) bool {
if sb := current.Info().Sandbox(); sb != nil {
if sb.ContainerID() == container.ID {
ep = current
sbox = sb
return true
}
}
return false
}
n.WalkEndpoints(s)
if ep == nil {
return fmt.Errorf("could not locate network endpoint for container %s", container.ID)
}
if err := ep.Leave(sbox); err != nil {
return fmt.Errorf("container %s failed to leave network %s: %v", container.ID, n.Name(), err)
}
if err := ep.Delete(); err != nil {
return fmt.Errorf("endpoint delete failed for container %s on network %s: %v", container.ID, n.Name(), err)
}
if updateSettings {
networks := container.NetworkSettings.Networks
for i, s := range networks {
sn, err := container.daemon.FindNetwork(s)
if err != nil {
continue
}
if sn.Name() == n.Name() {
networks = append(networks[:i], networks[i+1:]...)
container.NetworkSettings.Networks = networks
break
}
}
}
return nil
}
func (container *Container) unmountVolumes(forceSyscall bool) error {

View file

@ -7,6 +7,7 @@ import (
"github.com/docker/docker/daemon/execdriver"
derr "github.com/docker/docker/errors"
"github.com/docker/libnetwork"
)
// DefaultPathEnv is deliberately empty on Windows as the default path will be set by
@ -38,6 +39,16 @@ func (container *Container) initializeNetworking() error {
return nil
}
// ConnectToNetwork connects a container to the network
func (container *Container) ConnectToNetwork(idOrName string) error {
return nil
}
// DisconnectFromNetwork disconnects a container from, the network
func (container *Container) DisconnectFromNetwork(n libnetwork.Network) error {
return nil
}
func (container *Container) setupWorkingDirectory() error {
return nil
}

View file

@ -1162,11 +1162,6 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
return verifyPlatformContainerSettings(daemon, hostConfig, config)
}
// NetworkController exposes the libnetwork interface to manage networks.
func (daemon *Daemon) NetworkController() libnetwork.NetworkController {
return daemon.netController
}
func configureVolumes(config *Config) (*store.VolumeStore, error) {
volumesDriver, err := local.New(config.Root)
if err != nil {

106
daemon/network.go Normal file
View file

@ -0,0 +1,106 @@
package daemon
import (
"errors"
"strings"
"github.com/docker/libnetwork"
"github.com/docker/libnetwork/netlabel"
)
const (
// NetworkByID represents a constant to find a network by its ID
NetworkByID = iota + 1
// NetworkByName represents a constant to find a network by its Name
NetworkByName
)
// FindNetwork function finds a network for a given string that can represent network name or id
func (daemon *Daemon) FindNetwork(idName string) (libnetwork.Network, error) {
// Find by Name
n, err := daemon.GetNetwork(idName, NetworkByName)
if _, ok := err.(libnetwork.ErrNoSuchNetwork); err != nil && !ok {
return nil, err
}
if n != nil {
return n, nil
}
// Find by id
n, err = daemon.GetNetwork(idName, NetworkByID)
if err != nil {
return nil, err
}
return n, nil
}
// GetNetwork function returns a network for a given string that represents the network and
// a hint to indicate if the string is an Id or Name of the network
func (daemon *Daemon) GetNetwork(idName string, by int) (libnetwork.Network, error) {
c := daemon.netController
switch by {
case NetworkByID:
list := daemon.GetNetworksByID(idName)
if len(list) == 0 {
return nil, libnetwork.ErrNoSuchNetwork(idName)
}
if len(list) > 1 {
return nil, libnetwork.ErrInvalidID(idName)
}
return list[0], nil
case NetworkByName:
if idName == "" {
idName = c.Config().Daemon.DefaultNetwork
}
return c.NetworkByName(idName)
}
return nil, errors.New("unexpected selector for GetNetwork")
}
// GetNetworksByID returns a list of networks whose ID partially matches zero or more networks
func (daemon *Daemon) GetNetworksByID(partialID string) []libnetwork.Network {
c := daemon.netController
list := []libnetwork.Network{}
l := func(nw libnetwork.Network) bool {
if strings.HasPrefix(nw.ID(), partialID) {
list = append(list, nw)
}
return false
}
c.WalkNetworks(l)
return list
}
// CreateNetwork creates a network with the given name, driver and other optional parameters
func (daemon *Daemon) CreateNetwork(name, driver string, options map[string]interface{}) (libnetwork.Network, error) {
c := daemon.netController
if driver == "" {
driver = c.Config().Daemon.DefaultDriver
}
if options == nil {
options = make(map[string]interface{})
}
_, ok := options[netlabel.GenericData]
if !ok {
options[netlabel.GenericData] = make(map[string]interface{})
}
return c.NewNetwork(driver, name, parseOptions(options)...)
}
func parseOptions(options map[string]interface{}) []libnetwork.NetworkOption {
var setFctList []libnetwork.NetworkOption
if options != nil {
setFctList = append(setFctList, libnetwork.NetworkOptionGeneric(options))
}
return setFctList
}

View file

@ -24,7 +24,7 @@ type Settings struct {
LinkLocalIPv6Address string
LinkLocalIPv6PrefixLen int
MacAddress string
NetworkID string
Networks []string
Ports nat.PortMap
SandboxKey string
SecondaryIPAddresses []Address

View file

@ -1,72 +1,201 @@
// +build experimental
package main
import (
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/go-check/check"
)
func (s *DockerSuite) TestApiNetworkGetDefaults(c *check.C) {
// By default docker daemon creates 3 networks. check if they are present
defaults := []string{"bridge", "host", "none"}
for _, nn := range defaults {
c.Assert(isNetworkAvailable(c, nn), check.Equals, true)
}
}
func (s *DockerSuite) TestApiNetworkCreateDelete(c *check.C) {
// Create a network
name := "testnetwork"
id := createNetwork(c, name, true)
c.Assert(isNetworkAvailable(c, name), check.Equals, true)
// POST another network with same name and CheckDuplicate must fail
createNetwork(c, name, false)
// delete the network and make sure it is deleted
deleteNetwork(c, id, true)
c.Assert(isNetworkAvailable(c, name), check.Equals, false)
}
func (s *DockerSuite) TestApiNetworkFilter(c *check.C) {
nr := getNetworkResource(c, getNetworkIDByName(c, "bridge"))
c.Assert(nr.Name, check.Equals, "bridge")
}
func (s *DockerSuite) TestApiNetworkInspect(c *check.C) {
// Inspect default bridge network
nr := getNetworkResource(c, "bridge")
c.Assert(nr.Name, check.Equals, "bridge")
// run a container and attach it to the default bridge network
out, _ := dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
containerID := strings.TrimSpace(out)
containerIP := findContainerIP(c, "test")
// inspect default bridge network again and make sure the container is connected
nr = getNetworkResource(c, nr.ID)
c.Assert(len(nr.Containers), check.Equals, 1)
c.Assert(nr.Containers[containerID], check.NotNil)
ip, _, err := net.ParseCIDR(nr.Containers[containerID].IPv4Address)
c.Assert(err, check.IsNil)
c.Assert(ip.String(), check.Equals, containerIP)
}
func (s *DockerSuite) TestApiNetworkConnectDisconnect(c *check.C) {
// Create test network
name := "testnetwork"
id := createNetwork(c, name, true)
nr := getNetworkResource(c, id)
c.Assert(nr.Name, check.Equals, name)
c.Assert(nr.ID, check.Equals, id)
c.Assert(len(nr.Containers), check.Equals, 0)
// run a container
out, _ := dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
containerID := strings.TrimSpace(out)
// connect the container to the test network
connectNetwork(c, nr.ID, containerID)
// inspect the network to make sure container is connected
nr = getNetworkResource(c, nr.ID)
c.Assert(len(nr.Containers), check.Equals, 1)
c.Assert(nr.Containers[containerID], check.NotNil)
// check if container IP matches network inspect
ip, _, err := net.ParseCIDR(nr.Containers[containerID].IPv4Address)
c.Assert(err, check.IsNil)
containerIP := findContainerIP(c, "test")
c.Assert(ip.String(), check.Equals, containerIP)
// disconnect container from the network
disconnectNetwork(c, nr.ID, containerID)
nr = getNetworkResource(c, nr.ID)
c.Assert(nr.Name, check.Equals, name)
c.Assert(len(nr.Containers), check.Equals, 0)
// delete the network
deleteNetwork(c, nr.ID, true)
}
func isNetworkAvailable(c *check.C, name string) bool {
status, body, err := sockRequest("GET", "/networks", nil)
c.Assert(status, check.Equals, http.StatusOK)
c.Assert(err, check.IsNil)
var inspectJSON []struct {
Name string
ID string
Type string
}
if err = json.Unmarshal(body, &inspectJSON); err != nil {
c.Fatalf("unable to unmarshal response body: %v", err)
}
for _, n := range inspectJSON {
nJSON := []types.NetworkResource{}
err = json.Unmarshal(body, &nJSON)
c.Assert(err, check.IsNil)
for _, n := range nJSON {
if n.Name == name {
return true
}
}
return false
}
func (s *DockerSuite) TestNetworkApiGetAll(c *check.C) {
defaults := []string{"bridge", "host", "none"}
for _, nn := range defaults {
if !isNetworkAvailable(c, nn) {
c.Fatalf("Missing Default network : %s", nn)
}
}
}
func (s *DockerSuite) TestNetworkApiCreateDelete(c *check.C) {
name := "testnetwork"
config := map[string]interface{}{
"name": name,
"network_type": "bridge",
}
status, resp, err := sockRequest("POST", "/networks", config)
c.Assert(status, check.Equals, http.StatusCreated)
func getNetworkIDByName(c *check.C, name string) string {
var (
v = url.Values{}
filterArgs = filters.Args{}
)
filterArgs["name"] = []string{name}
filterJSON, err := filters.ToParam(filterArgs)
c.Assert(err, check.IsNil)
v.Set("filters", filterJSON)
if !isNetworkAvailable(c, name) {
c.Fatalf("Network %s not found", name)
}
var id string
err = json.Unmarshal(resp, &id)
if err != nil {
c.Fatal(err)
}
status, _, err = sockRequest("DELETE", fmt.Sprintf("/networks/%s", id), nil)
status, body, err := sockRequest("GET", "/networks?"+v.Encode(), nil)
c.Assert(status, check.Equals, http.StatusOK)
c.Assert(err, check.IsNil)
if isNetworkAvailable(c, name) {
c.Fatalf("Network %s not deleted", name)
}
nJSON := []types.NetworkResource{}
err = json.Unmarshal(body, &nJSON)
c.Assert(err, check.IsNil)
c.Assert(len(nJSON), check.Equals, 1)
return nJSON[0].ID
}
func getNetworkResource(c *check.C, id string) *types.NetworkResource {
_, obj, err := sockRequest("GET", "/networks/"+id, nil)
c.Assert(err, check.IsNil)
nr := types.NetworkResource{}
err = json.Unmarshal(obj, &nr)
c.Assert(err, check.IsNil)
return &nr
}
func createNetwork(c *check.C, name string, shouldSucceed bool) string {
config := types.NetworkCreate{
Name: name,
Driver: "bridge",
CheckDuplicate: true,
}
status, resp, err := sockRequest("POST", "/networks/create", config)
if !shouldSucceed {
c.Assert(status, check.Not(check.Equals), http.StatusCreated)
return ""
}
c.Assert(status, check.Equals, http.StatusCreated)
c.Assert(err, check.IsNil)
var nr types.NetworkCreateResponse
err = json.Unmarshal(resp, &nr)
c.Assert(err, check.IsNil)
return nr.ID
}
func connectNetwork(c *check.C, nid, cid string) {
config := types.NetworkConnect{
Container: cid,
}
status, _, err := sockRequest("POST", "/networks/"+nid+"/connect", config)
c.Assert(status, check.Equals, http.StatusOK)
c.Assert(err, check.IsNil)
}
func disconnectNetwork(c *check.C, nid, cid string) {
config := types.NetworkConnect{
Container: cid,
}
status, _, err := sockRequest("POST", "/networks/"+nid+"/disconnect", config)
c.Assert(status, check.Equals, http.StatusOK)
c.Assert(err, check.IsNil)
}
func deleteNetwork(c *check.C, id string, shouldSucceed bool) {
status, _, err := sockRequest("DELETE", "/networks/"+id, nil)
if !shouldSucceed {
c.Assert(status, check.Not(check.Equals), http.StatusOK)
c.Assert(err, check.NotNil)
return
}
c.Assert(status, check.Equals, http.StatusOK)
c.Assert(err, check.IsNil)
}

View file

@ -1,40 +0,0 @@
// +build daemon,experimental,!windows
package main
import (
"os/exec"
"strings"
"github.com/go-check/check"
)
func assertNetwork(c *check.C, d *Daemon, name string) {
out, err := d.Cmd("network", "ls")
c.Assert(err, check.IsNil)
lines := strings.Split(out, "\n")
for i := 1; i < len(lines)-1; i++ {
if strings.Contains(lines[i], name) {
return
}
}
c.Fatalf("Network %s not found in network ls o/p", name)
}
func (s *DockerDaemonSuite) TestDaemonDefaultNetwork(c *check.C) {
testRequires(c, SameHostDaemon)
d := s.d
networkName := "testdefault"
err := d.StartWithBusybox("--default-network", "bridge:"+networkName)
c.Assert(err, check.IsNil)
_, err = d.Cmd("run", "busybox", "true")
c.Assert(err, check.IsNil)
assertNetwork(c, d, networkName)
ifconfigCmd := exec.Command("ifconfig", networkName)
_, _, _, err = runCommandWithStdoutStderr(ifconfigCmd)
c.Assert(err, check.IsNil)
}

View file

@ -77,7 +77,7 @@ func (s *DockerSuite) TestNetHostname(c *check.C) {
if out, _, err = runCommandWithOutput(runCmd); err == nil {
c.Fatalf(out, err)
}
checkContains("invalid --net: weird", out, c)
checkContains("network weird not found", out, c)
}
func (s *DockerSuite) TestConflictContainerNetworkAndLinks(c *check.C) {

View file

@ -3168,7 +3168,7 @@ func (s *DockerSuite) TestRunCreateContainerFailedCleanUp(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "unique_name"
_, _, err := dockerCmdWithError("run", "--name", name, "--link", "nothing:nothing", "busybox")
c.Assert(err, check.Not(check.IsNil), check.Commentf("Expected docker run to fail!"))
c.Assert(err, check.NotNil, check.Commentf("Expected docker run to fail!"))
containerID, err := inspectField(name, "Id")
c.Assert(containerID, check.Equals, "", check.Commentf("Expected not to have this container: %s!", containerID))
@ -3404,6 +3404,154 @@ func (s *DockerSuite) TestTwoContainersInNetHost(c *check.C) {
dockerCmd(c, "stop", "second")
}
func (s *DockerSuite) TestContainersInUserDefinedNetwork(c *check.C) {
testRequires(c, DaemonIsLinux)
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork")
dockerCmd(c, "run", "-d", "--net=testnetwork", "--name=first", "busybox", "top")
dockerCmd(c, "run", "-t", "--net=testnetwork", "--name=second", "busybox", "ping", "-c", "1", "first")
dockerCmd(c, "stop", "first")
dockerCmd(c, "stop", "second")
dockerCmd(c, "network", "rm", "testnetwork")
}
func (s *DockerSuite) TestContainersInMultipleNetworks(c *check.C) {
testRequires(c, DaemonIsLinux)
// Create 2 networks using bridge driver
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1")
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork2")
// Run and connect containers to testnetwork1
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=first", "busybox", "top")
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=second", "busybox", "top")
// Check connectivity between containers in testnetwork2
dockerCmd(c, "exec", "first", "ping", "-c", "1", "second.testnetwork1")
// Connect containers to testnetwork2
dockerCmd(c, "network", "connect", "testnetwork2", "first")
dockerCmd(c, "network", "connect", "testnetwork2", "second")
// Check connectivity between containers
dockerCmd(c, "exec", "second", "ping", "-c", "1", "first.testnetwork2")
dockerCmd(c, "stop", "first")
dockerCmd(c, "stop", "second")
dockerCmd(c, "network", "rm", "testnetwork1")
dockerCmd(c, "network", "rm", "testnetwork2")
}
func (s *DockerSuite) TestContainersNetworkIsolation(c *check.C) {
testRequires(c, DaemonIsLinux)
// Create 2 networks using bridge driver
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1")
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork2")
// Run 1 containers in testnetwork1 and another in testnetwork2
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=first", "busybox", "top")
dockerCmd(c, "run", "-d", "--net=testnetwork2", "--name=second", "busybox", "top")
// Check Isolation between containers : ping must fail
_, _, err := dockerCmdWithError("exec", "first", "ping", "-c", "1", "second")
c.Assert(err, check.NotNil)
// Connect first container to testnetwork2
dockerCmd(c, "network", "connect", "testnetwork2", "first")
// ping must succeed now
_, _, err = dockerCmdWithError("exec", "first", "ping", "-c", "1", "second")
c.Assert(err, check.IsNil)
// Disconnect first container from testnetwork2
dockerCmd(c, "network", "disconnect", "testnetwork2", "first")
// ping must fail again
_, _, err = dockerCmdWithError("exec", "first", "ping", "-c", "1", "second")
c.Assert(err, check.NotNil)
dockerCmd(c, "stop", "first")
dockerCmd(c, "stop", "second")
dockerCmd(c, "network", "rm", "testnetwork1")
dockerCmd(c, "network", "rm", "testnetwork2")
}
func (s *DockerSuite) TestNetworkRmWithActiveContainers(c *check.C) {
testRequires(c, DaemonIsLinux)
// Create 2 networks using bridge driver
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1")
// Run and connect containers to testnetwork1
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=first", "busybox", "top")
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=second", "busybox", "top")
// Network delete with active containers must fail
_, _, err := dockerCmdWithError("network", "rm", "testnetwork1")
c.Assert(err, check.NotNil)
dockerCmd(c, "stop", "first")
_, _, err = dockerCmdWithError("network", "rm", "testnetwork1")
c.Assert(err, check.NotNil)
dockerCmd(c, "stop", "second")
// Network delete must succeed after all the connected containers are inactive
dockerCmd(c, "network", "rm", "testnetwork1")
}
func (s *DockerSuite) TestContainerRestartInMultipleNetworks(c *check.C) {
testRequires(c, DaemonIsLinux)
// Create 2 networks using bridge driver
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1")
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork2")
// Run and connect containers to testnetwork1
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=first", "busybox", "top")
dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=second", "busybox", "top")
// Check connectivity between containers in testnetwork2
dockerCmd(c, "exec", "first", "ping", "-c", "1", "second.testnetwork1")
// Connect containers to testnetwork2
dockerCmd(c, "network", "connect", "testnetwork2", "first")
dockerCmd(c, "network", "connect", "testnetwork2", "second")
// Check connectivity between containers
dockerCmd(c, "exec", "second", "ping", "-c", "1", "first.testnetwork2")
// Stop second container and test ping failures on both networks
dockerCmd(c, "stop", "second")
_, _, err := dockerCmdWithError("exec", "first", "ping", "-c", "1", "second.testnetwork1")
c.Assert(err, check.NotNil)
_, _, err = dockerCmdWithError("exec", "first", "ping", "-c", "1", "second.testnetwork2")
c.Assert(err, check.NotNil)
// Start second container and connectivity must be restored on both networks
dockerCmd(c, "start", "second")
dockerCmd(c, "exec", "first", "ping", "-c", "1", "second.testnetwork1")
dockerCmd(c, "exec", "second", "ping", "-c", "1", "first.testnetwork2")
dockerCmd(c, "stop", "first")
dockerCmd(c, "stop", "second")
dockerCmd(c, "network", "rm", "testnetwork1")
dockerCmd(c, "network", "rm", "testnetwork2")
}
func (s *DockerSuite) TestContainerWithConflictingHostNetworks(c *check.C) {
testRequires(c, DaemonIsLinux)
// Run a container with --net=host
dockerCmd(c, "run", "-d", "--net=host", "--name=first", "busybox", "top")
// Create a network using bridge driver
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1")
// Connecting to the user defined network must fail
_, _, err := dockerCmdWithError("network", "connect", "testnetwork1", "first")
c.Assert(err, check.NotNil)
dockerCmd(c, "stop", "first")
dockerCmd(c, "network", "rm", "testnetwork1")
}
func (s *DockerSuite) TestContainerWithConflictingSharedNetwork(c *check.C) {
testRequires(c, DaemonIsLinux)
dockerCmd(c, "run", "-d", "--name=first", "busybox", "top")
// Run second container in first container's network namespace
dockerCmd(c, "run", "-d", "--net=container:first", "--name=second", "busybox", "top")
// Create a network using bridge driver
dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1")
// Connecting to the user defined network must fail
_, _, err := dockerCmdWithError("network", "connect", "testnetwork1", "second")
c.Assert(err, check.NotNil)
dockerCmd(c, "stop", "first")
dockerCmd(c, "stop", "second")
dockerCmd(c, "network", "rm", "testnetwork1")
}
// #11957 - stdin with no tty does not exit if stdin is not closed even though container exited
func (s *DockerSuite) TestRunStdinBlockedAfterContainerExit(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-i", "--name=test", "busybox", "true")

View file

@ -20,7 +20,6 @@ type Config struct {
AttachStdout bool // Attach the standard output
AttachStderr bool // Attach the standard error
ExposedPorts map[nat.Port]struct{} // List of exposed ports
PublishService string // Name of the network service exposed by the container
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.

View file

@ -24,7 +24,7 @@ func TestNetworkModeTest(t *testing.T) {
}
networkModeNames := map[NetworkMode]string{
"": "",
"something:weird": "",
"something:weird": "something:weird",
"bridge": "bridge",
DefaultDaemonNetworkMode(): "bridge",
"host": "host",

View file

@ -34,6 +34,8 @@ func (n NetworkMode) NetworkName() string {
return "none"
} else if n.IsDefault() {
return "default"
} else if n.IsUserDefined() {
return n.UserDefined()
}
return ""
}
@ -59,6 +61,19 @@ func (n NetworkMode) IsNone() bool {
return n == "none"
}
// IsUserDefined indicates user-created network
func (n NetworkMode) IsUserDefined() bool {
return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer()
}
//UserDefined indicates user-created network
func (n NetworkMode) UserDefined() string {
if n.IsUserDefined() {
return string(n)
}
return ""
}
// MergeConfigs merges the specified container Config and HostConfig.
// It creates a ContainerConfigWrapper.
func MergeConfigs(config *Config, hostConfig *HostConfig) *ContainerConfigWrapper {

View file

@ -17,6 +17,12 @@ import (
var (
// ErrConflictContainerNetworkAndLinks conflict between --net=container and links
ErrConflictContainerNetworkAndLinks = fmt.Errorf("Conflicting options: --net=container can't be used with links. This would result in undefined behavior")
// ErrConflictUserDefinedNetworkAndLinks conflict between --net=<NETWORK> and links
ErrConflictUserDefinedNetworkAndLinks = fmt.Errorf("Conflicting options: --net=<NETWORK> can't be used with links. This would result in undefined behavior")
// ErrConflictSharedNetwork conflict between private and other networks
ErrConflictSharedNetwork = fmt.Errorf("Container sharing network namespace with another container or host cannot be connected to any other network")
// ErrConflictNoNetwork conflict between private and other networks
ErrConflictNoNetwork = fmt.Errorf("Container cannot be connected to multiple networks with one of the networks in --none mode")
// ErrConflictNetworkAndDNS conflict between --dns and the network mode
ErrConflictNetworkAndDNS = fmt.Errorf("Conflicting options: --dns and the network mode (--net)")
// ErrConflictNetworkHostname conflict between the hostname and the network mode
@ -88,7 +94,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
flBlkioWeight = cmd.Int64([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
flSwappiness = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tuning container memory swappiness (0 to 100)")
flNetMode = cmd.String([]string{"-net"}, "default", "Set the Network mode for the container")
flNetMode = cmd.String([]string{"-net"}, "default", "Set the Network for the container")
flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
@ -122,8 +128,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options")
expFlags := attachExperimentalFlags(cmd)
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
@ -379,8 +383,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
VolumeDriver: *flVolumeDriver,
}
applyExperimentalFlags(expFlags, config, hostConfig)
// When allocating stdin in attached mode, close stdin at client disconnect
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true

View file

@ -1,19 +0,0 @@
// +build experimental
package runconfig
import flag "github.com/docker/docker/pkg/mflag"
type experimentalFlags struct {
flags map[string]interface{}
}
func attachExperimentalFlags(cmd *flag.FlagSet) *experimentalFlags {
flags := make(map[string]interface{})
flags["publish-service"] = cmd.String([]string{"-publish-service"}, "", "Publish this container as a service")
return &experimentalFlags{flags: flags}
}
func applyExperimentalFlags(exp *experimentalFlags, config *Config, hostConfig *HostConfig) {
config.PublishService = *(exp.flags["publish-service"]).(*string)
}

View file

@ -1,14 +0,0 @@
// +build !experimental
package runconfig
import flag "github.com/docker/docker/pkg/mflag"
type experimentalFlags struct{}
func attachExperimentalFlags(cmd *flag.FlagSet) *experimentalFlags {
return nil
}
func applyExperimentalFlags(flags *experimentalFlags, config *Config, hostConfig *HostConfig) {
}

View file

@ -15,14 +15,10 @@ func ValidateNetMode(c *Config, hc *HostConfig) error {
return nil
}
parts := strings.Split(string(hc.NetworkMode), ":")
switch mode := parts[0]; mode {
case "default", "bridge", "none", "host":
case "container":
if parts[0] == "container" {
if len(parts) < 2 || parts[1] == "" {
return fmt.Errorf("--net: invalid net mode: invalid container format container:<name|id>")
}
default:
return fmt.Errorf("invalid --net: %s", hc.NetworkMode)
}
if (hc.NetworkMode.IsHost() || hc.NetworkMode.IsContainer()) && c.Hostname != "" {
@ -37,6 +33,10 @@ func ValidateNetMode(c *Config, hc *HostConfig) error {
return ErrConflictContainerNetworkAndLinks
}
if hc.NetworkMode.IsUserDefined() && len(hc.Links) > 0 {
return ErrConflictUserDefinedNetworkAndLinks
}
if (hc.NetworkMode.IsHost() || hc.NetworkMode.IsContainer()) && len(hc.DNS) > 0 {
return ErrConflictNetworkAndDNS
}

View file

@ -1,949 +0,0 @@
package api
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/docker/libnetwork"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/types"
"github.com/gorilla/mux"
)
var (
successResponse = responseStatus{Status: "Success", StatusCode: http.StatusOK}
createdResponse = responseStatus{Status: "Created", StatusCode: http.StatusCreated}
mismatchResponse = responseStatus{Status: "Body/URI parameter mismatch", StatusCode: http.StatusBadRequest}
badQueryResponse = responseStatus{Status: "Unsupported query", StatusCode: http.StatusBadRequest}
)
const (
// Resource name regex
// Gorilla mux encloses the passed pattern with '^' and '$'. So we need to do some tricks
// to have mux eventually build a query regex which matches empty or word string (`^$|[\w]+`)
regex = "[a-zA-Z_0-9-]+"
qregx = "$|" + regex
// Router URL variable definition
nwName = "{" + urlNwName + ":" + regex + "}"
nwNameQr = "{" + urlNwName + ":" + qregx + "}"
nwID = "{" + urlNwID + ":" + regex + "}"
nwPIDQr = "{" + urlNwPID + ":" + qregx + "}"
epName = "{" + urlEpName + ":" + regex + "}"
epNameQr = "{" + urlEpName + ":" + qregx + "}"
epID = "{" + urlEpID + ":" + regex + "}"
epPIDQr = "{" + urlEpPID + ":" + qregx + "}"
sbID = "{" + urlSbID + ":" + regex + "}"
sbPIDQr = "{" + urlSbPID + ":" + qregx + "}"
cnIDQr = "{" + urlCnID + ":" + qregx + "}"
cnPIDQr = "{" + urlCnPID + ":" + qregx + "}"
// Internal URL variable name.They can be anything as
// long as they do not collide with query fields.
urlNwName = "network-name"
urlNwID = "network-id"
urlNwPID = "network-partial-id"
urlEpName = "endpoint-name"
urlEpID = "endpoint-id"
urlEpPID = "endpoint-partial-id"
urlSbID = "sandbox-id"
urlSbPID = "sandbox-partial-id"
urlCnID = "container-id"
urlCnPID = "container-partial-id"
// BridgeNetworkDriver is the built-in default for Network Driver
BridgeNetworkDriver = "bridge"
)
// NewHTTPHandler creates and initialize the HTTP handler to serve the requests for libnetwork
func NewHTTPHandler(c libnetwork.NetworkController) func(w http.ResponseWriter, req *http.Request) {
h := &httpHandler{c: c}
h.initRouter()
return h.handleRequest
}
type responseStatus struct {
Status string
StatusCode int
}
func (r *responseStatus) isOK() bool {
return r.StatusCode == http.StatusOK || r.StatusCode == http.StatusCreated
}
type processor func(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus)
type httpHandler struct {
c libnetwork.NetworkController
r *mux.Router
}
func (h *httpHandler) handleRequest(w http.ResponseWriter, req *http.Request) {
// Make sure the service is there
if h.c == nil {
http.Error(w, "NetworkController is not available", http.StatusServiceUnavailable)
return
}
// Get handler from router and execute it
h.r.ServeHTTP(w, req)
}
func (h *httpHandler) initRouter() {
m := map[string][]struct {
url string
qrs []string
fct processor
}{
"GET": {
// Order matters
{"/networks", []string{"name", nwNameQr}, procGetNetworks},
{"/networks", []string{"partial-id", nwPIDQr}, procGetNetworks},
{"/networks", nil, procGetNetworks},
{"/networks/" + nwID, nil, procGetNetwork},
{"/networks/" + nwID + "/endpoints", []string{"name", epNameQr}, procGetEndpoints},
{"/networks/" + nwID + "/endpoints", []string{"partial-id", epPIDQr}, procGetEndpoints},
{"/networks/" + nwID + "/endpoints", nil, procGetEndpoints},
{"/networks/" + nwID + "/endpoints/" + epID, nil, procGetEndpoint},
{"/services", []string{"network", nwNameQr}, procGetServices},
{"/services", []string{"name", epNameQr}, procGetServices},
{"/services", []string{"partial-id", epPIDQr}, procGetServices},
{"/services", nil, procGetServices},
{"/services/" + epID, nil, procGetService},
{"/services/" + epID + "/backend", nil, procGetSandbox},
{"/sandboxes", []string{"partial-container-id", cnPIDQr}, procGetSandboxes},
{"/sandboxes", []string{"container-id", cnIDQr}, procGetSandboxes},
{"/sandboxes", []string{"partial-id", sbPIDQr}, procGetSandboxes},
{"/sandboxes", nil, procGetSandboxes},
{"/sandboxes/" + sbID, nil, procGetSandbox},
},
"POST": {
{"/networks", nil, procCreateNetwork},
{"/networks/" + nwID + "/endpoints", nil, procCreateEndpoint},
{"/networks/" + nwID + "/endpoints/" + epID + "/sandboxes", nil, procJoinEndpoint},
{"/services", nil, procPublishService},
{"/services/" + epID + "/backend", nil, procAttachBackend},
{"/sandboxes", nil, procCreateSandbox},
},
"DELETE": {
{"/networks/" + nwID, nil, procDeleteNetwork},
{"/networks/" + nwID + "/endpoints/" + epID, nil, procDeleteEndpoint},
{"/networks/" + nwID + "/endpoints/" + epID + "/sandboxes/" + sbID, nil, procLeaveEndpoint},
{"/services/" + epID, nil, procUnpublishService},
{"/services/" + epID + "/backend/" + sbID, nil, procDetachBackend},
{"/sandboxes/" + sbID, nil, procDeleteSandbox},
},
}
h.r = mux.NewRouter()
for method, routes := range m {
for _, route := range routes {
r := h.r.Path("/{.*}" + route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
if route.qrs != nil {
r.Queries(route.qrs...)
}
r = h.r.Path(route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct))
if route.qrs != nil {
r.Queries(route.qrs...)
}
}
}
}
func makeHandler(ctrl libnetwork.NetworkController, fct processor) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
var (
body []byte
err error
)
if req.Body != nil {
body, err = ioutil.ReadAll(req.Body)
if err != nil {
http.Error(w, "Invalid body: "+err.Error(), http.StatusBadRequest)
return
}
}
res, rsp := fct(ctrl, mux.Vars(req), body)
if !rsp.isOK() {
http.Error(w, rsp.Status, rsp.StatusCode)
return
}
if res != nil {
writeJSON(w, rsp.StatusCode, res)
}
}
}
/*****************
Resource Builders
******************/
func buildNetworkResource(nw libnetwork.Network) *networkResource {
r := &networkResource{}
if nw != nil {
r.Name = nw.Name()
r.ID = nw.ID()
r.Type = nw.Type()
epl := nw.Endpoints()
r.Endpoints = make([]*endpointResource, 0, len(epl))
for _, e := range epl {
epr := buildEndpointResource(e)
r.Endpoints = append(r.Endpoints, epr)
}
}
return r
}
func buildEndpointResource(ep libnetwork.Endpoint) *endpointResource {
r := &endpointResource{}
if ep != nil {
r.Name = ep.Name()
r.ID = ep.ID()
r.Network = ep.Network()
}
return r
}
func buildSandboxResource(sb libnetwork.Sandbox) *sandboxResource {
r := &sandboxResource{}
if sb != nil {
r.ID = sb.ID()
r.Key = sb.Key()
r.ContainerID = sb.ContainerID()
}
return r
}
/****************
Options Parsers
*****************/
func (nc *networkCreate) parseOptions() []libnetwork.NetworkOption {
var setFctList []libnetwork.NetworkOption
if nc.Options != nil {
setFctList = append(setFctList, libnetwork.NetworkOptionGeneric(nc.Options))
}
return setFctList
}
func (sc *sandboxCreate) parseOptions() []libnetwork.SandboxOption {
var setFctList []libnetwork.SandboxOption
if sc.HostName != "" {
setFctList = append(setFctList, libnetwork.OptionHostname(sc.HostName))
}
if sc.DomainName != "" {
setFctList = append(setFctList, libnetwork.OptionDomainname(sc.DomainName))
}
if sc.HostsPath != "" {
setFctList = append(setFctList, libnetwork.OptionHostsPath(sc.HostsPath))
}
if sc.ResolvConfPath != "" {
setFctList = append(setFctList, libnetwork.OptionResolvConfPath(sc.ResolvConfPath))
}
if sc.UseDefaultSandbox {
setFctList = append(setFctList, libnetwork.OptionUseDefaultSandbox())
}
if sc.UseExternalKey {
setFctList = append(setFctList, libnetwork.OptionUseExternalKey())
}
if sc.DNS != nil {
for _, d := range sc.DNS {
setFctList = append(setFctList, libnetwork.OptionDNS(d))
}
}
if sc.ExtraHosts != nil {
for _, e := range sc.ExtraHosts {
setFctList = append(setFctList, libnetwork.OptionExtraHost(e.Name, e.Address))
}
}
return setFctList
}
func (ej *endpointJoin) parseOptions() []libnetwork.EndpointOption {
// priority will go here
return []libnetwork.EndpointOption{}
}
/******************
Process functions
*******************/
func processCreateDefaults(c libnetwork.NetworkController, nc *networkCreate) {
if nc.NetworkType == "" {
nc.NetworkType = c.Config().Daemon.DefaultDriver
}
if nc.NetworkType == BridgeNetworkDriver {
if nc.Options == nil {
nc.Options = make(map[string]interface{})
}
genericData, ok := nc.Options[netlabel.GenericData]
if !ok {
genericData = make(map[string]interface{})
}
gData := genericData.(map[string]interface{})
if _, ok := gData["BridgeName"]; !ok {
gData["BridgeName"] = nc.Name
}
nc.Options[netlabel.GenericData] = genericData
}
}
/***************************
NetworkController interface
****************************/
func procCreateNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var create networkCreate
err := json.Unmarshal(body, &create)
if err != nil {
return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
processCreateDefaults(c, &create)
nw, err := c.NewNetwork(create.NetworkType, create.Name, create.parseOptions()...)
if err != nil {
return "", convertNetworkError(err)
}
return nw.ID(), &createdResponse
}
func procGetNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
t, by := detectNetworkTarget(vars)
nw, errRsp := findNetwork(c, t, by)
if !errRsp.isOK() {
return nil, errRsp
}
return buildNetworkResource(nw), &successResponse
}
func procGetNetworks(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var list []*networkResource
// Look for query filters and validate
name, queryByName := vars[urlNwName]
shortID, queryByPid := vars[urlNwPID]
if queryByName && queryByPid {
return nil, &badQueryResponse
}
if queryByName {
if nw, errRsp := findNetwork(c, name, byName); errRsp.isOK() {
list = append(list, buildNetworkResource(nw))
}
} else if queryByPid {
// Return all the prefix-matching networks
l := func(nw libnetwork.Network) bool {
if strings.HasPrefix(nw.ID(), shortID) {
list = append(list, buildNetworkResource(nw))
}
return false
}
c.WalkNetworks(l)
} else {
for _, nw := range c.Networks() {
list = append(list, buildNetworkResource(nw))
}
}
return list, &successResponse
}
func procCreateSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var create sandboxCreate
err := json.Unmarshal(body, &create)
if err != nil {
return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
sb, err := c.NewSandbox(create.ContainerID, create.parseOptions()...)
if err != nil {
return "", convertNetworkError(err)
}
return sb.ID(), &createdResponse
}
/******************
Network interface
*******************/
func procCreateEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var ec endpointCreate
err := json.Unmarshal(body, &ec)
if err != nil {
return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
nwT, nwBy := detectNetworkTarget(vars)
n, errRsp := findNetwork(c, nwT, nwBy)
if !errRsp.isOK() {
return "", errRsp
}
var setFctList []libnetwork.EndpointOption
if ec.ExposedPorts != nil {
setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(ec.ExposedPorts))
}
if ec.PortMapping != nil {
setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(ec.PortMapping))
}
ep, err := n.CreateEndpoint(ec.Name, setFctList...)
if err != nil {
return "", convertNetworkError(err)
}
return ep.ID(), &createdResponse
}
func procGetEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
nwT, nwBy := detectNetworkTarget(vars)
epT, epBy := detectEndpointTarget(vars)
ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
return buildEndpointResource(ep), &successResponse
}
func procGetEndpoints(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
// Look for query filters and validate
name, queryByName := vars[urlEpName]
shortID, queryByPid := vars[urlEpPID]
if queryByName && queryByPid {
return nil, &badQueryResponse
}
nwT, nwBy := detectNetworkTarget(vars)
nw, errRsp := findNetwork(c, nwT, nwBy)
if !errRsp.isOK() {
return nil, errRsp
}
var list []*endpointResource
// If query parameter is specified, return a filtered collection
if queryByName {
if ep, errRsp := findEndpoint(c, nwT, name, nwBy, byName); errRsp.isOK() {
list = append(list, buildEndpointResource(ep))
}
} else if queryByPid {
// Return all the prefix-matching endpoints
l := func(ep libnetwork.Endpoint) bool {
if strings.HasPrefix(ep.ID(), shortID) {
list = append(list, buildEndpointResource(ep))
}
return false
}
nw.WalkEndpoints(l)
} else {
for _, ep := range nw.Endpoints() {
epr := buildEndpointResource(ep)
list = append(list, epr)
}
}
return list, &successResponse
}
func procDeleteNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
target, by := detectNetworkTarget(vars)
nw, errRsp := findNetwork(c, target, by)
if !errRsp.isOK() {
return nil, errRsp
}
err := nw.Delete()
if err != nil {
return nil, convertNetworkError(err)
}
return nil, &successResponse
}
/******************
Endpoint interface
*******************/
func procJoinEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var ej endpointJoin
err := json.Unmarshal(body, &ej)
if err != nil {
return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
nwT, nwBy := detectNetworkTarget(vars)
epT, epBy := detectEndpointTarget(vars)
ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
sb, errRsp := findSandbox(c, ej.SandboxID, byID)
if !errRsp.isOK() {
return nil, errRsp
}
err = ep.Join(sb)
if err != nil {
return nil, convertNetworkError(err)
}
return sb.Key(), &successResponse
}
func procLeaveEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
nwT, nwBy := detectNetworkTarget(vars)
epT, epBy := detectEndpointTarget(vars)
ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
sb, errRsp := findSandbox(c, vars[urlSbID], byID)
if !errRsp.isOK() {
return nil, errRsp
}
err := ep.Leave(sb)
if err != nil {
return nil, convertNetworkError(err)
}
return nil, &successResponse
}
func procDeleteEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
nwT, nwBy := detectNetworkTarget(vars)
epT, epBy := detectEndpointTarget(vars)
ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
err := ep.Delete()
if err != nil {
return nil, convertNetworkError(err)
}
return nil, &successResponse
}
/******************
Service interface
*******************/
func procGetServices(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
// Look for query filters and validate
nwName, filterByNwName := vars[urlNwName]
svName, queryBySvName := vars[urlEpName]
shortID, queryBySvPID := vars[urlEpPID]
if filterByNwName && queryBySvName || filterByNwName && queryBySvPID || queryBySvName && queryBySvPID {
return nil, &badQueryResponse
}
var list []*endpointResource
switch {
case filterByNwName:
// return all service present on the specified network
nw, errRsp := findNetwork(c, nwName, byName)
if !errRsp.isOK() {
return list, &successResponse
}
for _, ep := range nw.Endpoints() {
epr := buildEndpointResource(ep)
list = append(list, epr)
}
case queryBySvName:
// Look in each network for the service with the specified name
l := func(ep libnetwork.Endpoint) bool {
if ep.Name() == svName {
list = append(list, buildEndpointResource(ep))
return true
}
return false
}
for _, nw := range c.Networks() {
nw.WalkEndpoints(l)
}
case queryBySvPID:
// Return all the prefix-matching services
l := func(ep libnetwork.Endpoint) bool {
if strings.HasPrefix(ep.ID(), shortID) {
list = append(list, buildEndpointResource(ep))
}
return false
}
for _, nw := range c.Networks() {
nw.WalkEndpoints(l)
}
default:
for _, nw := range c.Networks() {
for _, ep := range nw.Endpoints() {
epr := buildEndpointResource(ep)
list = append(list, epr)
}
}
}
return list, &successResponse
}
func procGetService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
epT, epBy := detectEndpointTarget(vars)
sv, errRsp := findService(c, epT, epBy)
if !errRsp.isOK() {
return nil, endpointToService(errRsp)
}
return buildEndpointResource(sv), &successResponse
}
func procPublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var sp servicePublish
err := json.Unmarshal(body, &sp)
if err != nil {
return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
n, errRsp := findNetwork(c, sp.Network, byName)
if !errRsp.isOK() {
return "", errRsp
}
var setFctList []libnetwork.EndpointOption
if sp.ExposedPorts != nil {
setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(sp.ExposedPorts))
}
if sp.PortMapping != nil {
setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(sp.PortMapping))
}
ep, err := n.CreateEndpoint(sp.Name, setFctList...)
if err != nil {
return "", endpointToService(convertNetworkError(err))
}
return ep.ID(), &createdResponse
}
func procUnpublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
epT, epBy := detectEndpointTarget(vars)
sv, errRsp := findService(c, epT, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
err := sv.Delete()
if err != nil {
return nil, endpointToService(convertNetworkError(err))
}
return nil, &successResponse
}
func procAttachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var bk endpointJoin
err := json.Unmarshal(body, &bk)
if err != nil {
return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
}
epT, epBy := detectEndpointTarget(vars)
sv, errRsp := findService(c, epT, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
sb, errRsp := findSandbox(c, bk.SandboxID, byID)
if !errRsp.isOK() {
return nil, errRsp
}
err = sv.Join(sb)
if err != nil {
return nil, convertNetworkError(err)
}
return sb.Key(), &successResponse
}
func procDetachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
epT, epBy := detectEndpointTarget(vars)
sv, errRsp := findService(c, epT, epBy)
if !errRsp.isOK() {
return nil, errRsp
}
sb, errRsp := findSandbox(c, vars[urlSbID], byID)
if !errRsp.isOK() {
return nil, errRsp
}
err := sv.Leave(sb)
if err != nil {
return nil, convertNetworkError(err)
}
return nil, &successResponse
}
/******************
Sandbox interface
*******************/
func procGetSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
if epT, ok := vars[urlEpID]; ok {
sv, errRsp := findService(c, epT, byID)
if !errRsp.isOK() {
return nil, endpointToService(errRsp)
}
return buildSandboxResource(sv.Info().Sandbox()), &successResponse
}
sbT, by := detectSandboxTarget(vars)
sb, errRsp := findSandbox(c, sbT, by)
if !errRsp.isOK() {
return nil, errRsp
}
return buildSandboxResource(sb), &successResponse
}
type cndFnMkr func(string) cndFn
type cndFn func(libnetwork.Sandbox) bool
// list of (query type, condition function makers) couples
var cndMkrList = []struct {
identifier string
maker cndFnMkr
}{
{urlSbPID, func(id string) cndFn {
return func(sb libnetwork.Sandbox) bool { return strings.HasPrefix(sb.ID(), id) }
}},
{urlCnID, func(id string) cndFn {
return func(sb libnetwork.Sandbox) bool { return sb.ContainerID() == id }
}},
{urlCnPID, func(id string) cndFn {
return func(sb libnetwork.Sandbox) bool { return strings.HasPrefix(sb.ContainerID(), id) }
}},
}
func getQueryCondition(vars map[string]string) func(libnetwork.Sandbox) bool {
for _, im := range cndMkrList {
if val, ok := vars[im.identifier]; ok {
return im.maker(val)
}
}
return func(sb libnetwork.Sandbox) bool { return true }
}
func sandboxWalker(condition cndFn, list *[]*sandboxResource) libnetwork.SandboxWalker {
return func(sb libnetwork.Sandbox) bool {
if condition(sb) {
*list = append(*list, buildSandboxResource(sb))
}
return false
}
}
func procGetSandboxes(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
var list []*sandboxResource
cnd := getQueryCondition(vars)
c.WalkSandboxes(sandboxWalker(cnd, &list))
return list, &successResponse
}
func procDeleteSandbox(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
sbT, by := detectSandboxTarget(vars)
sb, errRsp := findSandbox(c, sbT, by)
if !errRsp.isOK() {
return nil, errRsp
}
err := sb.Delete()
if err != nil {
return nil, convertNetworkError(err)
}
return nil, &successResponse
}
/***********
Utilities
************/
const (
byID = iota
byName
)
func detectNetworkTarget(vars map[string]string) (string, int) {
if target, ok := vars[urlNwName]; ok {
return target, byName
}
if target, ok := vars[urlNwID]; ok {
return target, byID
}
// vars are populated from the URL, following cannot happen
panic("Missing URL variable parameter for network")
}
func detectSandboxTarget(vars map[string]string) (string, int) {
if target, ok := vars[urlSbID]; ok {
return target, byID
}
// vars are populated from the URL, following cannot happen
panic("Missing URL variable parameter for sandbox")
}
func detectEndpointTarget(vars map[string]string) (string, int) {
if target, ok := vars[urlEpName]; ok {
return target, byName
}
if target, ok := vars[urlEpID]; ok {
return target, byID
}
// vars are populated from the URL, following cannot happen
panic("Missing URL variable parameter for endpoint")
}
func findNetwork(c libnetwork.NetworkController, s string, by int) (libnetwork.Network, *responseStatus) {
var (
nw libnetwork.Network
err error
)
switch by {
case byID:
nw, err = c.NetworkByID(s)
case byName:
if s == "" {
s = c.Config().Daemon.DefaultNetwork
}
nw, err = c.NetworkByName(s)
default:
panic(fmt.Sprintf("unexpected selector for network search: %d", by))
}
if err != nil {
if _, ok := err.(types.NotFoundError); ok {
return nil, &responseStatus{Status: "Resource not found: Network", StatusCode: http.StatusNotFound}
}
return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
}
return nw, &successResponse
}
func findSandbox(c libnetwork.NetworkController, s string, by int) (libnetwork.Sandbox, *responseStatus) {
var (
sb libnetwork.Sandbox
err error
)
switch by {
case byID:
sb, err = c.SandboxByID(s)
default:
panic(fmt.Sprintf("unexpected selector for sandbox search: %d", by))
}
if err != nil {
if _, ok := err.(types.NotFoundError); ok {
return nil, &responseStatus{Status: "Resource not found: Sandbox", StatusCode: http.StatusNotFound}
}
return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
}
return sb, &successResponse
}
func findEndpoint(c libnetwork.NetworkController, ns, es string, nwBy, epBy int) (libnetwork.Endpoint, *responseStatus) {
nw, errRsp := findNetwork(c, ns, nwBy)
if !errRsp.isOK() {
return nil, errRsp
}
var (
err error
ep libnetwork.Endpoint
)
switch epBy {
case byID:
ep, err = nw.EndpointByID(es)
case byName:
ep, err = nw.EndpointByName(es)
default:
panic(fmt.Sprintf("unexpected selector for endpoint search: %d", epBy))
}
if err != nil {
if _, ok := err.(types.NotFoundError); ok {
return nil, &responseStatus{Status: "Resource not found: Endpoint", StatusCode: http.StatusNotFound}
}
return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
}
return ep, &successResponse
}
func findService(c libnetwork.NetworkController, svs string, svBy int) (libnetwork.Endpoint, *responseStatus) {
for _, nw := range c.Networks() {
var (
ep libnetwork.Endpoint
err error
)
switch svBy {
case byID:
ep, err = nw.EndpointByID(svs)
case byName:
ep, err = nw.EndpointByName(svs)
default:
panic(fmt.Sprintf("unexpected selector for service search: %d", svBy))
}
if err == nil {
return ep, &successResponse
} else if _, ok := err.(types.NotFoundError); !ok {
return nil, convertNetworkError(err)
}
}
return nil, &responseStatus{Status: "Service not found", StatusCode: http.StatusNotFound}
}
func endpointToService(rsp *responseStatus) *responseStatus {
rsp.Status = strings.Replace(rsp.Status, "endpoint", "service", -1)
return rsp
}
func convertNetworkError(err error) *responseStatus {
var code int
switch err.(type) {
case types.BadRequestError:
code = http.StatusBadRequest
case types.ForbiddenError:
code = http.StatusForbidden
case types.NotFoundError:
code = http.StatusNotFound
case types.TimeoutError:
code = http.StatusRequestTimeout
case types.NotImplementedError:
code = http.StatusNotImplemented
case types.NoServiceError:
code = http.StatusServiceUnavailable
case types.InternalError:
code = http.StatusInternalServerError
default:
code = http.StatusInternalServerError
}
return &responseStatus{Status: err.Error(), StatusCode: code}
}
func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
return json.NewEncoder(w).Encode(v)
}

View file

@ -1,80 +0,0 @@
package api
import "github.com/docker/libnetwork/types"
/***********
Resources
************/
// networkResource is the body of the "get network" http response message
type networkResource struct {
Name string `json:"name"`
ID string `json:"id"`
Type string `json:"type"`
Endpoints []*endpointResource `json:"endpoints"`
}
// endpointResource is the body of the "get endpoint" http response message
type endpointResource struct {
Name string `json:"name"`
ID string `json:"id"`
Network string `json:"network"`
}
// sandboxResource is the body of "get service backend" response message
type sandboxResource struct {
ID string `json:"id"`
Key string `json:"key"`
ContainerID string `json:"container_id"`
// will add more fields once labels change is in
}
/***********
Body types
************/
// networkCreate is the expected body of the "create network" http request message
type networkCreate struct {
Name string `json:"name"`
NetworkType string `json:"network_type"`
Options map[string]interface{} `json:"options"`
}
// endpointCreate represents the body of the "create endpoint" http request message
type endpointCreate struct {
Name string `json:"name"`
ExposedPorts []types.TransportPort `json:"exposed_ports"`
PortMapping []types.PortBinding `json:"port_mapping"`
}
// sandboxCreate is the expected body of the "create sandbox" http request message
type sandboxCreate struct {
ContainerID string `json:"container_id"`
HostName string `json:"host_name"`
DomainName string `json:"domain_name"`
HostsPath string `json:"hosts_path"`
ResolvConfPath string `json:"resolv_conf_path"`
DNS []string `json:"dns"`
ExtraHosts []extraHost `json:"extra_hosts"`
UseDefaultSandbox bool `json:"use_default_sandbox"`
UseExternalKey bool `json:"use_external_key"`
}
// endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages
type endpointJoin struct {
SandboxID string `json:"sandbox_id"`
}
// servicePublish represents the body of the "publish service" http request message
type servicePublish struct {
Name string `json:"name"`
Network string `json:"network_name"`
ExposedPorts []types.TransportPort `json:"exposed_ports"`
PortMapping []types.PortBinding `json:"port_mapping"`
}
// extraHost represents the extra host object
type extraHost struct {
Name string `json:"name"`
Address string `json:"address"`
}