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-10-07 03:54:19 -07:00
parent aac5c44c10
commit 2ab94e11a2
29 changed files with 965 additions and 1379 deletions
+8 -7
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
+26 -11
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),
}
}
@@ -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)
}
+216
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
}
@@ -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) {
}
+5 -12
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
}
+10 -3
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)
}
}
+41
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"`
}