network compatibility endpoints for API

add endpoints for networking compatibility with the API.

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2020-05-13 14:24:59 -05:00
parent a6ee8bf2af
commit c3af2faab2
5 changed files with 438 additions and 1 deletions

View File

@ -0,0 +1,301 @@
package compat
import (
"encoding/json"
"net"
"net/http"
"os"
"syscall"
"time"
"github.com/containernetworking/cni/libcni"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/containers/libpod/pkg/network"
"github.com/docker/docker/api/types"
dockerNetwork "github.com/docker/docker/api/types/network"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
type CompatInspectNetwork struct {
types.NetworkResource
}
func InspectNetwork(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
// FYI scope and version are currently unused but are described by the API
// Leaving this for if/when we have to enable these
//query := struct {
// scope string
// verbose bool
//}{
// // override any golang type defaults
//}
//decoder := r.Context().Value("decoder").(*schema.Decoder)
//if err := decoder.Decode(&query, r.URL.Query()); err != nil {
// utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
// return
//}
config, err := runtime.GetConfig()
if err != nil {
utils.InternalServerError(w, err)
return
}
name := utils.GetName(r)
_, err = network.InspectNetwork(config, name)
if err != nil {
// TODO our network package does not distinguish between not finding a
// specific network vs not being able to read it
utils.InternalServerError(w, err)
return
}
report, err := getNetworkResourceByName(name, runtime)
if err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(w, http.StatusOK, report)
}
func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.NetworkResource, error) {
var (
ipamConfigs []dockerNetwork.IPAMConfig
)
config, err := runtime.GetConfig()
if err != nil {
return nil, err
}
containerEndpoints := map[string]types.EndpointResource{}
// Get the network path so we can get created time
networkConfigPath, err := network.GetCNIConfigPathByName(config, name)
if err != nil {
return nil, err
}
f, err := os.Stat(networkConfigPath)
if err != nil {
return nil, err
}
stat := f.Sys().(*syscall.Stat_t)
cons, err := runtime.GetAllContainers()
if err != nil {
return nil, err
}
conf, err := libcni.ConfListFromFile(networkConfigPath)
if err != nil {
return nil, err
}
// No Bridge plugin means we bail
bridge, err := genericPluginsToBridge(conf.Plugins, network.DefaultNetworkDriver)
if err != nil {
return nil, err
}
for _, outer := range bridge.IPAM.Ranges {
for _, n := range outer {
ipamConfig := dockerNetwork.IPAMConfig{
Subnet: n.Subnet,
Gateway: n.Gateway,
}
ipamConfigs = append(ipamConfigs, ipamConfig)
}
}
for _, con := range cons {
data, err := con.Inspect(false)
if err != nil {
return nil, err
}
if netData, ok := data.NetworkSettings.Networks[name]; ok {
containerEndpoint := types.EndpointResource{
Name: netData.NetworkID,
EndpointID: netData.EndpointID,
MacAddress: netData.MacAddress,
IPv4Address: netData.IPAddress,
IPv6Address: netData.GlobalIPv6Address,
}
containerEndpoints[con.ID()] = containerEndpoint
}
}
report := types.NetworkResource{
Name: name,
ID: "",
Created: time.Unix(stat.Ctim.Sec, stat.Ctim.Nsec),
Scope: "",
Driver: network.DefaultNetworkDriver,
EnableIPv6: false,
IPAM: dockerNetwork.IPAM{
Driver: "default",
Options: nil,
Config: ipamConfigs,
},
Internal: false,
Attachable: false,
Ingress: false,
ConfigFrom: dockerNetwork.ConfigReference{},
ConfigOnly: false,
Containers: containerEndpoints,
Options: nil,
Labels: nil,
Peers: nil,
Services: nil,
}
return &report, nil
}
func genericPluginsToBridge(plugins []*libcni.NetworkConfig, pluginType string) (network.HostLocalBridge, error) {
var bridge network.HostLocalBridge
generic, err := findPluginByName(plugins, pluginType)
if err != nil {
return bridge, err
}
err = json.Unmarshal(generic, &bridge)
return bridge, err
}
func findPluginByName(plugins []*libcni.NetworkConfig, pluginType string) ([]byte, error) {
for _, p := range plugins {
if pluginType == p.Network.Type {
return p.Bytes, nil
}
}
return nil, errors.New("unable to find bridge plugin")
}
func ListNetworks(w http.ResponseWriter, r *http.Request) {
var (
reports []*types.NetworkResource
)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Filters map[string][]string `schema:"filters"`
}{
// override any golang type defaults
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
config, err := runtime.GetConfig()
if err != nil {
utils.InternalServerError(w, err)
return
}
// TODO remove when filters are implemented
if len(query.Filters) > 0 {
utils.InternalServerError(w, errors.New("filters for listing networks is not implemented"))
return
}
netNames, err := network.GetNetworkNamesFromFileSystem(config)
if err != nil {
utils.InternalServerError(w, err)
return
}
for _, name := range netNames {
report, err := getNetworkResourceByName(name, runtime)
if err != nil {
utils.InternalServerError(w, err)
}
reports = append(reports, report)
}
utils.WriteResponse(w, http.StatusOK, reports)
}
func CreateNetwork(w http.ResponseWriter, r *http.Request) {
var (
name string
networkCreate types.NetworkCreateRequest
)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
if err := json.NewDecoder(r.Body).Decode(&networkCreate); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
if len(networkCreate.Name) > 0 {
name = networkCreate.Name
}
// At present I think we should just suport the bridge driver
// and allow demand to make us consider more
if networkCreate.Driver != network.DefaultNetworkDriver {
utils.InternalServerError(w, errors.New("network create only supports the bridge driver"))
return
}
ncOptions := entities.NetworkCreateOptions{
Driver: network.DefaultNetworkDriver,
Internal: networkCreate.Internal,
}
if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil {
if len(networkCreate.IPAM.Config) > 1 {
utils.InternalServerError(w, errors.New("compat network create can only support one IPAM config"))
return
}
if len(networkCreate.IPAM.Config[0].Subnet) > 0 {
_, subnet, err := net.ParseCIDR(networkCreate.IPAM.Config[0].Subnet)
if err != nil {
utils.InternalServerError(w, err)
return
}
ncOptions.Subnet = *subnet
}
if len(networkCreate.IPAM.Config[0].Gateway) > 0 {
ncOptions.Gateway = net.ParseIP(networkCreate.IPAM.Config[0].Gateway)
}
if len(networkCreate.IPAM.Config[0].IPRange) > 0 {
_, IPRange, err := net.ParseCIDR(networkCreate.IPAM.Config[0].IPRange)
if err != nil {
utils.InternalServerError(w, err)
return
}
ncOptions.Range = *IPRange
}
}
ce := abi.ContainerEngine{Libpod: runtime}
_, err := ce.NetworkCreate(r.Context(), name, ncOptions)
if err != nil {
utils.InternalServerError(w, err)
}
report := types.NetworkCreate{
CheckDuplicate: networkCreate.CheckDuplicate,
Driver: networkCreate.Driver,
Scope: networkCreate.Scope,
EnableIPv6: networkCreate.EnableIPv6,
IPAM: networkCreate.IPAM,
Internal: networkCreate.Internal,
Attachable: networkCreate.Attachable,
Ingress: networkCreate.Ingress,
ConfigOnly: networkCreate.ConfigOnly,
ConfigFrom: networkCreate.ConfigFrom,
Options: networkCreate.Options,
Labels: networkCreate.Labels,
}
utils.WriteResponse(w, http.StatusOK, report)
}
func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
config, err := runtime.GetConfig()
if err != nil {
utils.InternalServerError(w, err)
return
}
name := utils.GetName(r)
exists, err := network.Exists(config, name)
if err != nil {
utils.InternalServerError(w, err)
return
}
if !exists {
utils.Error(w, "network not found", http.StatusNotFound, err)
return
}
if err := network.RemoveNetwork(config, name); err != nil {
utils.InternalServerError(w, err)
}
utils.WriteResponse(w, http.StatusNoContent, "")
}

View File

@ -3,6 +3,7 @@ package compat
import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/storage/pkg/archive"
"github.com/docker/docker/api/types"
)
// Create container
@ -35,3 +36,30 @@ type swagChangesResponse struct {
Changes []archive.Change
}
}
// Network inspect
// swagger:response CompatNetworkInspect
type swagCompatNetworkInspect struct {
// in:body
Body types.NetworkResource
}
// Network list
// swagger:response CompatNetworkList
type swagCompatNetworkList struct {
// in:body
Body []types.NetworkResource
}
// Network create
// swagger:model NetworkCreateRequest
type NetworkCreateRequest struct {
types.NetworkCreateRequest
}
// Network create
// swagger:response CompatNetworkCreate
type swagCompatNetworkCreateResponse struct {
// in:body
Body struct{ types.NetworkCreate }
}

View File

@ -3,11 +3,96 @@ package server
import (
"net/http"
"github.com/containers/libpod/pkg/api/handlers/compat"
"github.com/containers/libpod/pkg/api/handlers/libpod"
"github.com/gorilla/mux"
)
func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// swagger:operation DELETE /networks/{name} compat compatRemoveNetwork
// ---
// tags:
// - networks (compat)
// summary: Remove a network
// description: Remove a network
// parameters:
// - in: path
// name: name
// type: string
// required: true
// description: the name of the network
// produces:
// - application/json
// responses:
// 204:
// description: no error
// 404:
// $ref: "#/responses/NoSuchNetwork"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/networks/{name}"), s.APIHandler(compat.RemoveNetwork)).Methods(http.MethodDelete)
r.HandleFunc("/networks/{name}", s.APIHandler(compat.RemoveNetwork)).Methods(http.MethodDelete)
// swagger:operation GET /networks/{name}/json compat compatInspectNetwork
// ---
// tags:
// - networks (compat)
// summary: Inspect a network
// description: Display low level configuration network
// parameters:
// - in: path
// name: name
// type: string
// required: true
// description: the name of the network
// produces:
// - application/json
// responses:
// 200:
// $ref: "#/responses/CompatNetworkInspect"
// 404:
// $ref: "#/responses/NoSuchNetwork"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/networks/{name}/json"), s.APIHandler(compat.InspectNetwork)).Methods(http.MethodGet)
r.HandleFunc("/networks/{name}/json", s.APIHandler(compat.InspectNetwork)).Methods(http.MethodGet)
// swagger:operation GET /networks/json compat compatListNetwork
// ---
// tags:
// - networks (compat)
// summary: List networks
// description: Display summary of network configurations
// produces:
// - application/json
// responses:
// 200:
// $ref: "#/responses/CompatNetworkList"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/networks/json"), s.APIHandler(compat.ListNetworks)).Methods(http.MethodGet)
r.HandleFunc("/networks", s.APIHandler(compat.ListNetworks)).Methods(http.MethodGet)
// swagger:operation POST /networks/create compat compatCreateNetwork
// ---
// tags:
// - networks (compat)
// summary: Create network
// description: Create a network configuration
// produces:
// - application/json
// parameters:
// - in: body
// name: create
// description: attributes for creating a container
// schema:
// $ref: "#/definitions/NetworkCreateRequest"
// responses:
// 200:
// $ref: "#/responses/CompatNetworkCreate"
// 400:
// $ref: "#/responses/BadParamError"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/networks/create"), s.APIHandler(compat.CreateNetwork)).Methods(http.MethodPost)
r.HandleFunc("/networks/create", s.APIHandler(compat.CreateNetwork)).Methods(http.MethodPost)
// swagger:operation DELETE /libpod/networks/{name} libpod libpodRemoveNetwork
// ---
// tags:
@ -33,6 +118,11 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// $ref: "#/responses/NoSuchNetwork"
// 500:
// $ref: "#/responses/InternalError"
/*
Libpod
*/
r.HandleFunc(VersionedPath("/libpod/networks/{name}"), s.APIHandler(libpod.RemoveNetwork)).Methods(http.MethodDelete)
// swagger:operation GET /libpod/networks/{name}/json libpod libpodInspectNetwork
// ---

View File

@ -21,5 +21,7 @@ tags:
description: Actions related to exec for the compatibility endpoints
- name: images (compat)
description: Actions related to images for the compatibility endpoints
- name: networks (compat)
description: Actions related to compatibility networks
- name: system (compat)
description: Actions related to Podman and compatibility engines

View File

@ -13,8 +13,11 @@ import (
"github.com/sirupsen/logrus"
)
// DefaultNetworkDriver is the default network type used
var DefaultNetworkDriver string = "bridge"
// SupportedNetworkDrivers describes the list of supported drivers
var SupportedNetworkDrivers = []string{"bridge"}
var SupportedNetworkDrivers = []string{DefaultNetworkDriver}
// IsSupportedDriver checks if the user provided driver is supported
func IsSupportedDriver(driver string) error {
@ -191,3 +194,16 @@ func InspectNetwork(config *config.Config, name string) (map[string]interface{},
err = json.Unmarshal(b, &rawList)
return rawList, err
}
// Exists says whether a given network exists or not; it meant
// specifically for restful reponses so 404s can be used
func Exists(config *config.Config, name string) (bool, error) {
_, err := ReadRawCNIConfByName(config, name)
if err != nil {
if errors.Cause(err) == ErrNetworkNotFound {
return false, nil
}
return false, err
}
return true, nil
}