From 85e8fbf7f33717ef6a0d6cf9e2143b52c874c2de Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Mon, 16 Aug 2021 16:11:26 +0200 Subject: Wire network interface into libpod Make use of the new network interface in libpod. This commit contains several breaking changes: - podman network create only outputs the new network name and not file path. - podman network ls shows the network driver instead of the cni version and plugins. - podman network inspect outputs the new network struct and not the cni conflist. - The bindings and libpod api endpoints have been changed to use the new network structure. The container network status is stored in a new field in the state. The status should be received with the new `c.getNetworkStatus`. This will migrate the old status to the new format. Therefore old containers should contine to work correctly in all cases even when network connect/ disconnect is used. New features: - podman network reload keeps the ip and mac for more than one network. - podman container restore keeps the ip and mac for more than one network. - The network create compat endpoint can now use more than one ipam config. The man pages and the swagger doc are updated to reflect the latest changes. Signed-off-by: Paul Holzinger --- pkg/api/handlers/compat/networks.go | 264 ++++++++++++++---------------------- pkg/api/handlers/libpod/networks.go | 25 +--- pkg/api/handlers/libpod/swagger.go | 14 +- pkg/api/server/register_networks.go | 10 +- 4 files changed, 118 insertions(+), 195 deletions(-) (limited to 'pkg/api') diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index 847e6dcff..28727a22b 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -4,20 +4,15 @@ import ( "encoding/json" "net" "net/http" - "os" - "strings" - "syscall" - "time" - "github.com/containernetworking/cni/libcni" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/network" + nettypes "github.com/containers/podman/v3/libpod/network/types" + netutil "github.com/containers/podman/v3/libpod/network/util" "github.com/containers/podman/v3/pkg/api/handlers/utils" api "github.com/containers/podman/v3/pkg/api/types" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" - networkid "github.com/containers/podman/v3/pkg/network" "github.com/containers/podman/v3/pkg/util" "github.com/docker/docker/api/types" @@ -27,12 +22,6 @@ import ( "github.com/sirupsen/logrus" ) -type pluginInterface struct { - PluginType string `json:"type"` - IPAM network.IPAMConfig `json:"ipam"` - IsGW bool `json:"isGateway"` -} - func InspectNetwork(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) @@ -54,18 +43,13 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Invalid scope value. Can only be local.", http.StatusBadRequest, define.ErrInvalidArg) return } - config, err := runtime.GetConfig() - if err != nil { - utils.InternalServerError(w, err) - return - } name := utils.GetName(r) - _, err = network.InspectNetwork(config, name) + net, err := runtime.Network().NetworkInspect(name) if err != nil { utils.NetworkNotFound(w, name, err) return } - report, err := getNetworkResourceByNameOrID(name, runtime, nil) + report, err := convertLibpodNetworktoDockerNetwork(runtime, net) if err != nil { utils.InternalServerError(w, err) return @@ -73,65 +57,18 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, report) } -func getNetworkResourceByNameOrID(nameOrID string, runtime *libpod.Runtime, filters map[string][]string) (*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.GetCNIConfigPathByNameOrID(config, nameOrID) - if err != nil { - return nil, err - } - f, err := os.Stat(networkConfigPath) - if err != nil { - return nil, err - } - stat := f.Sys().(*syscall.Stat_t) +func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, network nettypes.Network) (*types.NetworkResource, error) { cons, err := runtime.GetAllContainers() if err != nil { return nil, err } - conf, err := libcni.ConfListFromFile(networkConfigPath) - if err != nil { - return nil, err - } - if len(filters) > 0 { - ok, err := network.IfPassesFilter(conf, filters) - if err != nil { - return nil, err - } - if !ok { - // do not return the config if we did not match the filter - return nil, nil - } - } - - plugin, err := getPlugin(conf.Plugins) - if err != nil { - return nil, err - } - - for _, outer := range plugin.IPAM.Ranges { - for _, n := range outer { - ipamConfig := dockerNetwork.IPAMConfig{ - Subnet: n.Subnet, - Gateway: n.Gateway, - } - ipamConfigs = append(ipamConfigs, ipamConfig) - } - } - + containerEndpoints := make(map[string]types.EndpointResource, len(cons)) for _, con := range cons { data, err := con.Inspect(false) if err != nil { return nil, err } - if netData, ok := data.NetworkSettings.Networks[conf.Name]; ok { + if netData, ok := data.NetworkSettings.Networks[network.Name]; ok { containerEndpoint := types.EndpointResource{ Name: netData.NetworkID, EndpointID: netData.EndpointID, @@ -142,60 +79,47 @@ func getNetworkResourceByNameOrID(nameOrID string, runtime *libpod.Runtime, filt containerEndpoints[con.ID()] = containerEndpoint } } - - labels := network.GetNetworkLabels(conf) - if labels == nil { - labels = map[string]string{} + ipamConfigs := make([]dockerNetwork.IPAMConfig, 0, len(network.Subnets)) + for _, sub := range network.Subnets { + ipamConfig := dockerNetwork.IPAMConfig{ + Subnet: sub.Subnet.String(), + Gateway: sub.Gateway.String(), + // TODO add range + } + ipamConfigs = append(ipamConfigs, ipamConfig) } - - isInternal := false - dockerDriver := plugin.PluginType - if plugin.PluginType == network.DefaultNetworkDriver { - isInternal = !plugin.IsGW - dockerDriver = "default" + ipamDriver := network.IPAMOptions["driver"] + if ipamDriver == nettypes.HostLocalIPAMDriver { + ipamDriver = "default" + } + ipam := dockerNetwork.IPAM{ + Driver: ipamDriver, + Options: network.IPAMOptions, + Config: ipamConfigs, } report := types.NetworkResource{ - Name: conf.Name, - ID: networkid.GetNetworkID(conf.Name), - Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert + Name: network.Name, + ID: network.ID, + Driver: network.Driver, + // TODO add Created: , + Internal: network.Internal, + EnableIPv6: network.IPv6Enabled, + Labels: network.Labels, + Options: network.Options, + IPAM: ipam, Scope: "local", - Driver: plugin.PluginType, - EnableIPv6: false, - IPAM: dockerNetwork.IPAM{ - Driver: dockerDriver, - Options: map[string]string{}, - Config: ipamConfigs, - }, - Internal: isInternal, Attachable: false, Ingress: false, ConfigFrom: dockerNetwork.ConfigReference{}, ConfigOnly: false, Containers: containerEndpoints, - Options: map[string]string{}, - Labels: labels, Peers: nil, Services: nil, } return &report, nil } -func getPlugin(plugins []*libcni.NetworkConfig) (pluginInterface, error) { - var plugin pluginInterface - - for _, p := range plugins { - for _, pluginType := range network.SupportedNetworkDrivers { - if pluginType == p.Network.Type { - err := json.Unmarshal(p.Bytes, &plugin) - return plugin, err - } - } - } - - return plugin, errors.New("unable to find supported plugin") -} - func ListNetworks(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) filterMap, err := util.PrepareFilters(r) @@ -204,37 +128,32 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { return } - config, err := runtime.GetConfig() - if err != nil { - utils.InternalServerError(w, err) - return + options := entities.NetworkListOptions{ + Filters: *filterMap, } - netNames, err := network.GetNetworkNamesFromFileSystem(config) + ic := abi.ContainerEngine{Libpod: runtime} + nets, err := ic.NetworkList(r.Context(), options) if err != nil { utils.InternalServerError(w, err) return } - - reports := []*types.NetworkResource{} - logrus.Debugf("netNames: %q", strings.Join(netNames, ", ")) - for _, name := range netNames { - report, err := getNetworkResourceByNameOrID(name, runtime, *filterMap) + reports := make([]*types.NetworkResource, 0, len(nets)) + for _, net := range nets { + report, err := convertLibpodNetworktoDockerNetwork(runtime, net) if err != nil { utils.InternalServerError(w, err) return } - if report != nil { - reports = append(reports, report) - } + reports = append(reports, report) } utils.WriteResponse(w, http.StatusOK, reports) } func CreateNetwork(w http.ResponseWriter, r *http.Request) { var ( - name string networkCreate types.NetworkCreateRequest + network nettypes.Network ) runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) if err := json.NewDecoder(r.Body).Decode(&networkCreate); err != nil { @@ -242,65 +161,80 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { return } - if len(networkCreate.Name) > 0 { - name = networkCreate.Name - } - if len(networkCreate.Driver) < 1 { - networkCreate.Driver = network.DefaultNetworkDriver + network.Name = networkCreate.Name + if networkCreate.Driver == "" { + networkCreate.Driver = nettypes.DefaultNetworkDriver } - // 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, - Labels: networkCreate.Labels, + network.Driver = networkCreate.Driver + network.Labels = networkCreate.Labels + network.Internal = networkCreate.Internal + network.IPv6Enabled = networkCreate.EnableIPv6 + + // FIXME use docker options and convert them to valid libpod options + // network.Options = networkCreate.Options + + // dns is only enabled for the bridge driver + if network.Driver == nettypes.BridgeNetworkDriver { + network.DNSEnabled = true } - if networkCreate.IPAM != nil && len(networkCreate.IPAM.Config) > 0 { - 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 + if networkCreate.IPAM != nil && len(networkCreate.IPAM.Config) > 0 { + for _, conf := range networkCreate.IPAM.Config { + s := nettypes.Subnet{} + if len(conf.Subnet) > 0 { + var err error + subnet, err := nettypes.ParseCIDR(conf.Subnet) + if err != nil { + utils.InternalServerError(w, errors.Wrap(err, "failed to parse subnet")) + return + } + s.Subnet = subnet } - 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 + if len(conf.Gateway) > 0 { + gw := net.ParseIP(conf.Gateway) + if gw == nil { + utils.InternalServerError(w, errors.Errorf("failed to parse gateway ip %s", conf.Gateway)) + return + } + s.Gateway = gw + } + if len(conf.IPRange) > 0 { + _, net, err := net.ParseCIDR(conf.IPRange) + if err != nil { + utils.InternalServerError(w, errors.Wrap(err, "failed to parse ip range")) + return + } + startIP, err := netutil.FirstIPInSubnet(net) + if err != nil { + utils.InternalServerError(w, errors.Wrap(err, "failed to get first ip in range")) + return + } + lastIP, err := netutil.LastIPInSubnet(net) + if err != nil { + utils.InternalServerError(w, errors.Wrap(err, "failed to get last ip in range")) + return + } + s.LeaseRange = &nettypes.LeaseRange{ + StartIP: startIP, + EndIP: lastIP, + } } - ncOptions.Range = *IPRange + network.Subnets = append(network.Subnets, s) } - } - ce := abi.ContainerEngine{Libpod: runtime} - if _, err := ce.NetworkCreate(r.Context(), name, ncOptions); err != nil { - utils.InternalServerError(w, err) - return + // FIXME can we use the IPAM driver and options? } - net, err := getNetworkResourceByNameOrID(name, runtime, nil) + network, err := runtime.Network().NetworkCreate(network) if err != nil { utils.InternalServerError(w, err) return } + body := struct { ID string `json:"Id"` Warning []string }{ - ID: net.ID, + ID: network.ID, } utils.WriteResponse(w, http.StatusCreated, body) } diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go index 122ab1d3d..fcd8e0231 100644 --- a/pkg/api/handlers/libpod/networks.go +++ b/pkg/api/handlers/libpod/networks.go @@ -6,7 +6,7 @@ import ( "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/network" + "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/pkg/api/handlers/utils" api "github.com/containers/podman/v3/pkg/api/types" "github.com/containers/podman/v3/pkg/domain/entities" @@ -18,27 +18,14 @@ import ( func CreateNetwork(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) - options := entities.NetworkCreateOptions{} - if err := json.NewDecoder(r.Body).Decode(&options); err != nil { - utils.Error(w, "unable to marshall input", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + network := types.Network{} + if err := json.NewDecoder(r.Body).Decode(&network); err != nil { + utils.Error(w, "unable to marshall input", http.StatusInternalServerError, errors.Wrap(err, "decode body")) return } - query := struct { - Name string `schema:"name"` - }{ - // override any golang type defaults - } - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) - return - } - if len(options.Driver) < 1 { - options.Driver = network.DefaultNetworkDriver - } + ic := abi.ContainerEngine{Libpod: runtime} - report, err := ic.NetworkCreate(r.Context(), query.Name, options) + report, err := ic.Libpod.Network().NetworkCreate(network) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go index 6116a7274..7ccfdd0f3 100644 --- a/pkg/api/handlers/libpod/swagger.go +++ b/pkg/api/handlers/libpod/swagger.go @@ -4,9 +4,9 @@ import ( "net/http" "os" - "github.com/containernetworking/cni/libcni" "github.com/containers/image/v5/manifest" "github.com/containers/podman/v3/libpod/define" + "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/pkg/errors" @@ -103,21 +103,27 @@ type swagNetworkRmReport struct { // swagger:response NetworkInspectReport type swagNetworkInspectReport struct { // in:body - Body libcni.NetworkConfigList + Body types.Network } // Network list // swagger:response NetworkListReport type swagNetworkListReport struct { // in:body - Body []entities.NetworkListReport + Body []types.Network +} + +// Network create +// swagger:model NetworkCreateLibpod +type swagNetworkCreateLibpod struct { + types.Network } // Network create // swagger:response NetworkCreateReport type swagNetworkCreateReport struct { // in:body - Body entities.NetworkCreateReport + Body types.Network } // Network prune diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go index cacf83a7f..641bce333 100644 --- a/pkg/api/server/register_networks.go +++ b/pkg/api/server/register_networks.go @@ -267,7 +267,7 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // - `id=[id]` Matches for full or partial ID. // - `driver=[driver]` Only bridge is supported. // - `label=[key]` or `label=[key=value]` Matches networks based on the presence of a label alone or a label and a value. - // - `plugin=[plugin]` Matches CNI plugins included in a network (e.g `bridge`,`portmap`,`firewall`,`tuning`,`dnsname`,`macvlan`) + // - `until=[timestamp]` Matches all networks that were create before the given timestamp. // produces: // - application/json // responses: @@ -306,19 +306,15 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // tags: // - networks // summary: Create network - // description: Create a new CNI network configuration + // description: Create a new network configuration // produces: // - application/json // parameters: - // - in: query - // name: name - // type: string - // description: optional name for new network // - in: body // name: create // description: attributes for creating a container // schema: - // $ref: "#/definitions/NetworkCreateOptions" + // $ref: "#/definitions/NetworkCreateLibpod" // responses: // 200: // $ref: "#/responses/NetworkCreateReport" -- cgit v1.2.3-54-g00ecf