mirror of
https://github.com/containers/podman.git
synced 2025-05-21 09:05:56 +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"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containernetworking/cni/libcni"
|
"github.com/containernetworking/cni/libcni"
|
||||||
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/podman/v3/pkg/network"
|
"github.com/containers/podman/v3/pkg/network"
|
||||||
"github.com/containers/podman/v3/pkg/util"
|
"github.com/containers/podman/v3/pkg/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -222,24 +225,7 @@ func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]stri
|
|||||||
|
|
||||||
case "label":
|
case "label":
|
||||||
// matches all labels
|
// matches all labels
|
||||||
labels := GetNetworkLabels(netconf)
|
result = matchPruneLabelFilters(netconf, filterValues)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
case "driver":
|
case "driver":
|
||||||
// matches only for the DefaultNetworkDriver
|
// matches only for the DefaultNetworkDriver
|
||||||
@ -268,3 +254,65 @@ func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]stri
|
|||||||
}
|
}
|
||||||
return result, nil
|
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
|
// Prune removes unused networks
|
||||||
func Prune(w http.ResponseWriter, r *http.Request) {
|
func Prune(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO Filters are not implemented
|
|
||||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
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}
|
ic := abi.ContainerEngine{Libpod: runtime}
|
||||||
pruneOptions := entities.NetworkPruneOptions{}
|
pruneOptions := entities.NetworkPruneOptions{
|
||||||
|
Filters: filterMap,
|
||||||
|
}
|
||||||
pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions)
|
pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
|
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
|
// Prune removes unused networks
|
||||||
func Prune(w http.ResponseWriter, r *http.Request) {
|
func Prune(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO Filters are not implemented
|
|
||||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
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}
|
ic := abi.ContainerEngine{Libpod: runtime}
|
||||||
pruneOptions := entities.NetworkPruneOptions{}
|
|
||||||
pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions)
|
pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
|
||||||
|
@ -172,7 +172,6 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
|
|||||||
// name: filters
|
// name: filters
|
||||||
// type: string
|
// type: string
|
||||||
// description: |
|
// description: |
|
||||||
// NOT IMPLEMENTED
|
|
||||||
// Filters to process on the prune list, encoded as JSON (a map[string][]string).
|
// Filters to process on the prune list, encoded as JSON (a map[string][]string).
|
||||||
// Available filters:
|
// 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.
|
// - 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
|
// NetworkPruneOptions describes options for pruning
|
||||||
// unused cni networks
|
// 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"
|
||||||
"github.com/containers/podman/v3/libpod/define"
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
"github.com/containers/podman/v3/pkg/network"
|
"github.com/containers/podman/v3/pkg/network"
|
||||||
"github.com/containers/podman/v3/pkg/timetype"
|
|
||||||
"github.com/containers/podman/v3/pkg/util"
|
"github.com/containers/podman/v3/pkg/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@ -186,18 +185,10 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
|
|||||||
return false
|
return false
|
||||||
}, nil
|
}, nil
|
||||||
case "until":
|
case "until":
|
||||||
if len(filterValues) != 1 {
|
until, err := util.ComputeUntilTimestamp(filter, filterValues)
|
||||||
return nil, errors.Errorf("specify exactly one timestamp for %s", filter)
|
|
||||||
}
|
|
||||||
ts, err := timetype.GetTimestamp(filterValues[0], time.Now())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
return func(c *libpod.Container) bool {
|
||||||
if !until.IsZero() && c.CreatedTime().After((until)) {
|
if !until.IsZero() && c.CreatedTime().After((until)) {
|
||||||
return true
|
return true
|
||||||
|
@ -174,17 +174,37 @@ func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.Ne
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Gather up all the non-default networks that the
|
||||||
// containers want
|
// containers want
|
||||||
usedNetworks := make(map[string]bool)
|
networksToKeep := make(map[string]bool)
|
||||||
for _, c := range cons {
|
for _, c := range cons {
|
||||||
nets, _, err := c.Networks()
|
nets, _, err := c.Networks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, n := range nets {
|
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 \
|
200 \
|
||||||
.Filename~.*/network2\\.conflist
|
.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
|
# test for empty mask
|
||||||
t POST libpod/networks/create Subnet='{"IP":"10.10.1.0","Mask":[]}' 500 \
|
t POST libpod/networks/create Subnet='{"IP":"10.10.1.0","Mask":[]}' 500 \
|
||||||
.cause~'.*cannot be empty'
|
.cause~'.*cannot be empty'
|
||||||
@ -38,7 +52,7 @@ t GET libpod/networks/network1/json 200 \
|
|||||||
t GET networks?filters='{"name":["network1","network2"]}' 200 \
|
t GET networks?filters='{"name":["network1","network2"]}' 200 \
|
||||||
length=2
|
length=2
|
||||||
t GET networks?filters='{"name":["network"]}' 200 \
|
t GET networks?filters='{"name":["network"]}' 200 \
|
||||||
length=2
|
length=4
|
||||||
t GET networks?filters='{"label":["abc"]}' 200 \
|
t GET networks?filters='{"label":["abc"]}' 200 \
|
||||||
length=1
|
length=1
|
||||||
# old docker filter type see #9526
|
# old docker filter type see #9526
|
||||||
@ -66,6 +80,18 @@ t POST networks/create Name=net3\ IPAM='{"Config":[]}' 201
|
|||||||
# network delete docker
|
# network delete docker
|
||||||
t DELETE networks/net3 204
|
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
|
# clean the network
|
||||||
t DELETE libpod/networks/network1 200 \
|
t DELETE libpod/networks/network1 200 \
|
||||||
.[0].Name~network1 \
|
.[0].Name~network1 \
|
||||||
|
Reference in New Issue
Block a user