diff options
-rw-r--r-- | libpod/network/netconflist.go | 84 | ||||
-rw-r--r-- | pkg/api/handlers/compat/networks.go | 18 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/networks.go | 17 | ||||
-rw-r--r-- | pkg/api/server/register_networks.go | 1 | ||||
-rw-r--r-- | pkg/domain/entities/network.go | 4 | ||||
-rw-r--r-- | pkg/domain/filters/containers.go | 11 | ||||
-rw-r--r-- | pkg/domain/infra/abi/network.go | 26 | ||||
-rw-r--r-- | pkg/util/filters.go | 25 | ||||
-rw-r--r-- | test/apiv2/35-networks.at | 28 |
9 files changed, 176 insertions, 38 deletions
diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go index a45a4109a..b358bc530 100644 --- a/libpod/network/netconflist.go +++ b/libpod/network/netconflist.go @@ -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 +} diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index dfb1d7fda..7e06cad66 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -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) diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go index 48cd37994..19c9ed658 100644 --- a/pkg/api/handlers/libpod/networks.go +++ b/pkg/api/handlers/libpod/networks.go @@ -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) diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go index c54de952f..a07c6a55e 100644 --- a/pkg/api/server/register_networks.go +++ b/pkg/api/server/register_networks.go @@ -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. diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go index f66a7f575..a89501664 100644 --- a/pkg/domain/entities/network.go +++ b/pkg/domain/entities/network.go @@ -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 +} diff --git a/pkg/domain/filters/containers.go b/pkg/domain/filters/containers.go index 98b8f7e88..02727e841 100644 --- a/pkg/domain/filters/containers.go +++ b/pkg/domain/filters/containers.go @@ -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()) - 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 diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index edde8ece6..1a833332c 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -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) } diff --git a/pkg/util/filters.go b/pkg/util/filters.go new file mode 100644 index 000000000..bf16f89e3 --- /dev/null +++ b/pkg/util/filters.go @@ -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 +} diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at index ce7ca628a..6c3a34ece 100644 --- a/test/apiv2/35-networks.at +++ b/test/apiv2/35-networks.at @@ -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 \ |