mirror of
https://github.com/containers/podman.git
synced 2025-05-21 00:56:36 +08:00
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:
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
25
pkg/util/filters.go
Normal 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
|
||||
}
|
@ -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 \
|
||||
|
Reference in New Issue
Block a user