Skip to content

Commit

Permalink
Merge pull request #9710 from jmguzik/network-prune-filters-http-api
Browse files Browse the repository at this point in the history
Network prune filters for http api (compat and libpod)
  • Loading branch information
openshift-merge-robot authored Mar 18, 2021
2 parents 77b3a2d + 8ea02d0 commit 629183b
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 38 deletions.
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":
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
}
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 @@ -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
Expand Down Expand Up @@ -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 \
Expand Down

0 comments on commit 629183b

Please sign in to comment.