Files
podman/pkg/api/handlers/compat/networks.go
baude fe3faa517e prevent unpredictable results with network create|remove
due to a lack of "locking" on cni operations, we could get ourselves in trouble when doing rapid creation or removal of networks.  added a simple file lock to deal with the collision and because it is not considered a performent path, use of the file lock should be ok.  if proven otherwise in the future, some generic shared memory lock should be implemented for libpod and also used here.

moved pkog/network to libpod/network because libpod is now being pulled into the package and it has therefore lost its generic nature. this will make it easier to absorb into libpod as we try to make the network closer to core operations.

Fixes: #7807

Signed-off-by: baude <bbaude@redhat.com>
2020-10-07 10:03:21 -05:00

315 lines
8.9 KiB
Go

package compat
import (
"encoding/json"
"net"
"net/http"
"os"
"strings"
"syscall"
"time"
"github.com/containernetworking/cni/libcni"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi"
"github.com/docker/docker/api/types"
dockerNetwork "github.com/docker/docker/api/types/network"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
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 {
utils.NetworkNotFound(w, name, 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(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert
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) {
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
}
filterNames, nameFilterExists := query.Filters["name"]
// TODO remove when filters are implemented
if (!nameFilterExists && len(query.Filters) > 0) || len(query.Filters) > 1 {
utils.InternalServerError(w, errors.New("only the name filter for listing networks is implemented"))
return
}
netNames, err := network.GetNetworkNamesFromFileSystem(config)
if err != nil {
utils.InternalServerError(w, err)
return
}
// filter by name
if nameFilterExists {
names := []string{}
for _, name := range netNames {
for _, filter := range filterNames {
if strings.Contains(name, filter) {
names = append(names, name)
break
}
}
}
netNames = names
}
reports := make([]*types.NetworkResource, 0, len(netNames))
for _, name := range netNames {
report, err := getNetworkResourceByName(name, runtime)
if err != nil {
utils.InternalServerError(w, err)
return
}
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 support 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}
if _, err := ce.NetworkCreate(r.Context(), name, ncOptions); err != nil {
utils.InternalServerError(w, err)
return
}
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, define.ErrNoSuchNetwork)
return
}
if err := network.RemoveNetwork(config, name); err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(w, http.StatusNoContent, "")
}