Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Network prune filters for http api (compat and libpod) #9710

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 66 additions & 18 deletions libpod/network/netconflist.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import (
"os"
"path/filepath"
"strings"
"syscall"
"time"

"github.com/containernetworking/cni/libcni"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/pkg/network"
"github.com/containers/podman/v3/pkg/util"
"github.com/pkg/errors"
Expand Down Expand Up @@ -222,24 +225,7 @@ func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]stri

case "label":
// matches all labels
labels := GetNetworkLabels(netconf)
outer:
for _, filterValue := range filterValues {
filterArray := strings.SplitN(filterValue, "=", 2)
filterKey := filterArray[0]
if len(filterArray) > 1 {
filterValue = filterArray[1]
} else {
filterValue = ""
}
for labelKey, labelValue := range labels {
if labelKey == filterKey && (filterValue == "" || labelValue == filterValue) {
result = true
continue outer
}
}
result = false
}
result = matchPruneLabelFilters(netconf, filterValues)

case "driver":
// matches only for the DefaultNetworkDriver
Expand Down Expand Up @@ -268,3 +254,65 @@ func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]stri
}
return result, nil
}

// IfPassesPruneFilter filters NetworkListReport and returns true if the prune filter match the given config
func IfPassesPruneFilter(config *config.Config, netconf *libcni.NetworkConfigList, f map[string][]string) (bool, error) {
for key, filterValues := range f {
switch strings.ToLower(key) {
case "label":
return matchPruneLabelFilters(netconf, filterValues), nil
case "until":
jmguzik marked this conversation as resolved.
Show resolved Hide resolved
until, err := util.ComputeUntilTimestamp(key, filterValues)
if err != nil {
return false, err
}
created, err := getCreatedTimestamp(config, netconf)
if err != nil {
return false, err
}
if created.Before(until) {
return true, nil
}
default:
return false, errors.Errorf("invalid filter %q", key)
}
}
return false, nil
}

func matchPruneLabelFilters(netconf *libcni.NetworkConfigList, filterValues []string) bool {
labels := GetNetworkLabels(netconf)
result := true
outer:
for _, filterValue := range filterValues {
filterArray := strings.SplitN(filterValue, "=", 2)
filterKey := filterArray[0]
if len(filterArray) > 1 {
filterValue = filterArray[1]
} else {
filterValue = ""
}
for labelKey, labelValue := range labels {
if labelKey == filterKey && (filterValue == "" || labelValue == filterValue) {
result = true
continue outer
}
}
result = false
}
return result
}

func getCreatedTimestamp(config *config.Config, netconf *libcni.NetworkConfigList) (*time.Time, error) {
networkConfigPath, err := GetCNIConfigPathByNameOrID(config, netconf.Name)
if err != nil {
return nil, err
}
f, err := os.Stat(networkConfigPath)
if err != nil {
return nil, err
}
stat := f.Sys().(*syscall.Stat_t)
jmguzik marked this conversation as resolved.
Show resolved Hide resolved
created := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)) // nolint: unconvert
return &created, nil
}
18 changes: 16 additions & 2 deletions pkg/api/handlers/compat/networks.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,10 +400,24 @@ func Disconnect(w http.ResponseWriter, r *http.Request) {

// Prune removes unused networks
func Prune(w http.ResponseWriter, r *http.Request) {
// TODO Filters are not implemented
runtime := r.Context().Value("runtime").(*libpod.Runtime)
filters, err := filtersFromRequest(r)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
filterMap := map[string][]string{}
for _, filter := range filters {
split := strings.SplitN(filter, "=", 2)
if len(split) > 1 {
filterMap[split[0]] = append(filterMap[split[0]], split[1])
}
}

ic := abi.ContainerEngine{Libpod: runtime}
pruneOptions := entities.NetworkPruneOptions{}
pruneOptions := entities.NetworkPruneOptions{
Filters: filterMap,
}
pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
Expand Down
17 changes: 15 additions & 2 deletions pkg/api/handlers/libpod/networks.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,23 @@ func ExistsNetwork(w http.ResponseWriter, r *http.Request) {

// Prune removes unused networks
func Prune(w http.ResponseWriter, r *http.Request) {
// TODO Filters are not implemented
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.StatusInternalServerError, err)
return
}

pruneOptions := entities.NetworkPruneOptions{
Filters: query.Filters,
}
ic := abi.ContainerEngine{Libpod: runtime}
pruneOptions := entities.NetworkPruneOptions{}
pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
Expand Down
1 change: 0 additions & 1 deletion pkg/api/server/register_networks.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,6 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// name: filters
// type: string
// description: |
// NOT IMPLEMENTED
// Filters to process on the prune list, encoded as JSON (a map[string][]string).
// Available filters:
// - until=<timestamp> Prune networks created before this timestamp. The <timestamp> can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the daemon machine’s time.
Expand Down
4 changes: 3 additions & 1 deletion pkg/domain/entities/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,6 @@ type NetworkPruneReport struct {

// NetworkPruneOptions describes options for pruning
// unused cni networks
type NetworkPruneOptions struct{}
type NetworkPruneOptions struct {
Filters map[string][]string
}
11 changes: 1 addition & 10 deletions pkg/domain/filters/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/network"
"github.com/containers/podman/v3/pkg/timetype"
"github.com/containers/podman/v3/pkg/util"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -186,18 +185,10 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
return false
}, nil
case "until":
if len(filterValues) != 1 {
return nil, errors.Errorf("specify exactly one timestamp for %s", filter)
}
ts, err := timetype.GetTimestamp(filterValues[0], time.Now())
if err != nil {
return nil, err
}
seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0)
until, err := util.ComputeUntilTimestamp(filter, filterValues)
if err != nil {
return nil, err
}
until := time.Unix(seconds, nanoseconds)
return func(c *libpod.Container) bool {
if !until.IsZero() && c.CreatedTime().After((until)) {
return true
Expand Down
26 changes: 23 additions & 3 deletions pkg/domain/infra/abi/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,17 +174,37 @@ func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.Ne
if err != nil {
return nil, err
}
networks, err := network.LoadCNIConfsFromDir(network.GetCNIConfDir(runtimeConfig))
if err != nil {
return nil, err
}

// Gather up all the non-default networks that the
// containers want
usedNetworks := make(map[string]bool)
networksToKeep := make(map[string]bool)
for _, c := range cons {
nets, _, err := c.Networks()
if err != nil {
return nil, err
}
for _, n := range nets {
usedNetworks[n] = true
networksToKeep[n] = true
}
}
if len(options.Filters) != 0 {
for _, n := range networks {
// This network will be kept anyway
if _, found := networksToKeep[n.Name]; found {
continue
}
ok, err := network.IfPassesPruneFilter(runtimeConfig, n, options.Filters)
if err != nil {
return nil, err
}
if !ok {
networksToKeep[n.Name] = true
}
}
}
return network.PruneNetworks(runtimeConfig, usedNetworks)
return network.PruneNetworks(runtimeConfig, networksToKeep)
}
25 changes: 25 additions & 0 deletions pkg/util/filters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package util

import (
"time"

"github.com/containers/podman/v3/pkg/timetype"
"github.com/pkg/errors"
)

// ComputeUntilTimestamp extracts unitil timestamp from filters
func ComputeUntilTimestamp(filter string, filterValues []string) (time.Time, error) {
invalid := time.Time{}
if len(filterValues) != 1 {
return invalid, errors.Errorf("specify exactly one timestamp for %s", filter)
}
ts, err := timetype.GetTimestamp(filterValues[0], time.Now())
if err != nil {
return invalid, err
}
seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0)
if err != nil {
return invalid, err
}
return time.Unix(seconds, nanoseconds), nil
}
28 changes: 27 additions & 1 deletion test/apiv2/35-networks.at
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ t POST libpod/networks/create?name=network2 \
200 \
.Filename~.*/network2\\.conflist

# --data '{"Subnet":{"IP":"10.10.133.0","Mask":[255,255,255,0]},"Labels":{"xyz":"val"}}'
t POST libpod/networks/create?name=network3 \
Subnet='{"IP":"10.10.133.0","Mask":[255,255,255,0]}' \
Labels='{"xyz":"val"}' \
200 \
.Filename~.*/network3\\.conflist

# --data '{"Subnet":{"IP":"10.10.134.0","Mask":[255,255,255,0]},"Labels":{"zaq":"val"}}'
t POST libpod/networks/create?name=network4 \
Subnet='{"IP":"10.10.134.0","Mask":[255,255,255,0]}' \
Labels='{"zaq":"val"}' \
200 \
.Filename~.*/network4\\.conflist

# test for empty mask
t POST libpod/networks/create Subnet='{"IP":"10.10.1.0","Mask":[]}' 500 \
.cause~'.*cannot be empty'
Expand All @@ -34,7 +48,7 @@ t GET networks 200
t GET networks?filters='{"name":["network1","network2"]}' 200 \
length=2
t GET networks?filters='{"name":["network"]}' 200 \
length=2
length=4
t GET networks?filters='{"label":["abc"]}' 200 \
length=1
# old docker filter type see #9526
Expand Down Expand Up @@ -62,6 +76,18 @@ t POST networks/create Name=net3\ IPAM='{"Config":[]}' 201
# network delete docker
t DELETE networks/net3 204

# Prune networks compat api - bad filter input
t POST networks/prune?filters='garb1age}' 500 \
.cause="invalid character 'g' looking for beginning of value"

# prune networks using filter - compat api
t POST networks/prune?filters='{"label":["xyz"]}' 200
t GET networks/json?filters='{"label":["xyz"]}' 404

# prune networks using filter - libpod api
t POST libpod/networks/prune?filters='{"label":["zaq=val"]}' 200
t GET libpod/networks/json?filters='{"label":["zaq=val"]}' 200 length=0

# clean the network
t DELETE libpod/networks/network1 200 \
.[0].Name~network1 \
Expand Down