Merge pull request #9710 from jmguzik/network-prune-filters-http-api

Network prune filters for http api (compat and libpod)
This commit is contained in:
OpenShift Merge Robot
2021-03-18 06:32:29 -07:00
committed by GitHub
9 changed files with 176 additions and 38 deletions

View File

@ -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"
@ -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
@ -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":
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)
created := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)) // nolint: unconvert
return &created, nil
}

View File

@ -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)

View File

@ -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)

View File

@ -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 machines time.

View File

@ -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
}

View File

@ -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"
)
@ -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())
until, err := util.ComputeUntilTimestamp(filter, filterValues)
if err != nil {
return nil, err
}
seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0)
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

View File

@ -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
}
}
return network.PruneNetworks(runtimeConfig, usedNetworks)
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, networksToKeep)
}

25
pkg/util/filters.go Normal file
View File

@ -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
}

View File

@ -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'
@ -38,7 +52,7 @@ t GET libpod/networks/network1/json 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
@ -66,6 +80,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 \