Merge pull request #9236 from baude/networkprune

add network prune
This commit is contained in:
OpenShift Merge Robot
2021-02-08 09:50:50 -05:00
committed by GitHub
18 changed files with 447 additions and 22 deletions

View File

@ -0,0 +1,82 @@
package network
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var (
networkPruneDescription = `Prune unused networks`
networkPruneCommand = &cobra.Command{
Use: "prune [options]",
Short: "network prune",
Long: networkPruneDescription,
RunE: networkPrune,
Example: `podman network prune`,
Args: validate.NoArgs,
ValidArgsFunction: common.AutocompleteNetworks,
}
)
var (
networkPruneOptions entities.NetworkPruneOptions
force bool
)
func networkPruneFlags(flags *pflag.FlagSet) {
//TODO: Not implemented but for future reference
//flags.StringSliceVar(&networkPruneOptions.Filters,"filters", []string{}, "provide filter values (e.g. 'until=<timestamp>')")
flags.BoolVarP(&force, "force", "f", false, "do not prompt for confirmation")
}
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: networkPruneCommand,
Parent: networkCmd,
})
flags := networkPruneCommand.Flags()
networkPruneFlags(flags)
}
func networkPrune(cmd *cobra.Command, _ []string) error {
var (
errs utils.OutputErrors
)
if !force {
reader := bufio.NewReader(os.Stdin)
fmt.Println("WARNING! This will remove all networks not used by at least one container.")
fmt.Print("Are you sure you want to continue? [y/N] ")
answer, err := reader.ReadString('\n')
if err != nil {
return err
}
if strings.ToLower(answer)[0] != 'y' {
return nil
}
}
responses, err := registry.ContainerEngine().NetworkPrune(registry.Context(), networkPruneOptions)
if err != nil {
setExitCode(err)
return err
}
for _, r := range responses {
if r.Error == nil {
fmt.Println(r.Name)
} else {
setExitCode(r.Error)
errs = append(errs, r.Error)
}
}
return errs.PrintErrors()
}

View File

@ -0,0 +1,31 @@
% podman-network-prune(1)
## NAME
podman\-network\-prune - Remove all unused networks
## SYNOPSIS
**podman network prune** [*options*]
## DESCRIPTION
Remove all unused networks. An unused network is defined by a network which
has no containers connected or configured to connect to it. It will not remove
the so-called default network which goes by the name of *podman*.
## OPTIONS
#### **--force**, **-f**
Do not prompt for confirmation
## EXAMPLE
Prune networks
```
podman network prune
```
## SEE ALSO
podman(1), podman-network(1), podman-network-remove(1)
## HISTORY
February 2021, Originally compiled by Brent Baude <bbaude@redhat.com>

View File

@ -19,6 +19,7 @@ The network command manages CNI networks for Podman.
| exists | [podman-network-exists(1)](podman-network-exists.1.md) | Check if the given network exists |
| inspect | [podman-network-inspect(1)](podman-network-inspect.1.md) | Displays the raw CNI network configuration for one or more networks |
| ls | [podman-network-ls(1)](podman-network-ls.1.md) | Display a summary of CNI networks |
| prune | [podman-network-prune(1)](podman-network-prune.1.md) | Remove all unused networks |
| reload | [podman-network-reload(1)](podman-network-reload.1.md) | Reload network configuration for containers |
| rm | [podman-network-rm(1)](podman-network-rm.1.md) | Remove one or more CNI networks |

View File

@ -13,6 +13,8 @@ Network
:doc:`ls <markdown/podman-network-ls.1>` network list
:doc:`prune <markdown/podman-network-prune.1>` network prune
:doc:`reload <markdown/podman-network-reload.1>` network reload
:doc:`rm <markdown/podman-network-rm.1>` network rm

View File

@ -11,6 +11,7 @@ import (
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/podman/v2/pkg/util"
"github.com/pkg/errors"
@ -174,14 +175,9 @@ func ValidateUserNetworkIsAvailable(config *config.Config, userNet *net.IPNet) e
return nil
}
// RemoveNetwork removes a given network by name. If the network has container associated with it, that
// must be handled outside the context of this.
func RemoveNetwork(config *config.Config, name string) error {
l, err := acquireCNILock(config)
if err != nil {
return err
}
defer l.releaseCNILock()
// removeNetwork is removes a cni network without a lock and should only be called
// when a lock was otherwise acquired.
func removeNetwork(config *config.Config, name string) error {
cniPath, err := GetCNIConfigPathByNameOrID(config, name)
if err != nil {
return err
@ -213,6 +209,17 @@ func RemoveNetwork(config *config.Config, name string) error {
return nil
}
// RemoveNetwork removes a given network by name. If the network has container associated with it, that
// must be handled outside the context of this.
func RemoveNetwork(config *config.Config, name string) error {
l, err := acquireCNILock(config)
if err != nil {
return err
}
defer l.releaseCNILock()
return removeNetwork(config, name)
}
// InspectNetwork reads a CNI config and returns its configuration
func InspectNetwork(config *config.Config, name string) (map[string]interface{}, error) {
b, err := ReadRawCNIConfByName(config, name)
@ -243,3 +250,30 @@ func GetNetworkID(name string) string {
hash := sha256.Sum256([]byte(name))
return hex.EncodeToString(hash[:])
}
// PruneNetworks removes networks that are not being used and that is not the default
// network. To keep proper fencing for imports, you must provide the used networks
// to this function as a map. the key is meaningful in the map, the book is a no-op
func PruneNetworks(rtc *config.Config, usedNetworks map[string]bool) ([]*entities.NetworkPruneReport, error) {
var reports []*entities.NetworkPruneReport
lock, err := acquireCNILock(rtc)
if err != nil {
return nil, err
}
defer lock.releaseCNILock()
nets, err := GetNetworkNamesFromFileSystem(rtc)
if err != nil {
return nil, err
}
for _, n := range nets {
_, found := usedNetworks[n]
// Remove is not default network and not found in the used list
if n != rtc.Network.DefaultNetwork && !found {
reports = append(reports, &entities.NetworkPruneReport{
Name: n,
Error: removeNetwork(rtc, n),
})
}
}
return reports, nil
}

View File

@ -388,3 +388,25 @@ func Disconnect(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, "OK")
}
// Prune removes unused networks
func Prune(w http.ResponseWriter, r *http.Request) {
// TODO Filters are not implemented
runtime := r.Context().Value("runtime").(*libpod.Runtime)
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)
return
}
var prunedNetworks []string //nolint
for _, pr := range pruneReports {
if pr.Error != nil {
logrus.Error(pr.Error)
continue
}
prunedNetworks = append(prunedNetworks, pr.Name)
}
utils.WriteResponse(w, http.StatusOK, prunedNetworks)
}

View File

@ -77,3 +77,10 @@ type swagCompatNetworkDisconnectRequest struct {
// in:body
Body struct{ types.NetworkDisconnect }
}
// Network prune
// swagger:response NetworkPruneResponse
type swagCompatNetworkPruneResponse struct {
// in:body
Body []string
}

View File

@ -175,3 +175,17 @@ func ExistsNetwork(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusNoContent, "")
}
// Prune removes unused networks
func Prune(w http.ResponseWriter, r *http.Request) {
// TODO Filters are not implemented
runtime := r.Context().Value("runtime").(*libpod.Runtime)
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)
return
}
utils.WriteResponse(w, http.StatusOK, pruneReports)
}

View File

@ -9,19 +9,6 @@ import (
)
func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// swagger:operation POST /networks/prune compat compatPruneNetwork
// ---
// tags:
// - networks (compat)
// Summary: Delete unused networks
// description: Not supported
// produces:
// - application/json
// responses:
// 404:
// $ref: "#/responses/NoSuchNetwork"
r.HandleFunc(VersionedPath("/networks/prune"), compat.UnsupportedHandler).Methods(http.MethodPost)
r.HandleFunc("/networks/prune", compat.UnsupportedHandler).Methods(http.MethodPost)
// swagger:operation DELETE /networks/{name} compat compatRemoveNetwork
// ---
// tags:
@ -172,6 +159,35 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/networks/{name}/disconnect"), s.APIHandler(compat.Disconnect)).Methods(http.MethodPost)
r.HandleFunc("/networks/{name}/disconnect", s.APIHandler(compat.Disconnect)).Methods(http.MethodPost)
// swagger:operation POST /networks/prune compat compatPruneNetwork
// ---
// tags:
// - networks (compat)
// summary: Delete unused networks
// description: Remove CNI networks that do not have containers
// produces:
// - application/json
// parameters:
// - in: query
// 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.
// - label (label=<key>, label=<key>=<value>, label!=<key>, or label!=<key>=<value>) Prune networks with (or without, in case label!=... is used) the specified labels.
// responses:
// 200:
// description: OK
// schema:
// type: array
// items:
// type: string
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/networks/prune"), s.APIHandler(compat.Prune)).Methods(http.MethodPost)
r.HandleFunc("/networks/prune", s.APIHandler(compat.Prune)).Methods(http.MethodPost)
// swagger:operation DELETE /libpod/networks/{name} libpod libpodRemoveNetwork
// ---
@ -353,5 +369,29 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/networks/{name}/disconnect"), s.APIHandler(compat.Disconnect)).Methods(http.MethodPost)
// swagger:operation POST /libpod/networks/prune libpod libpodPruneNetwork
// ---
// tags:
// - networks
// summary: Delete unused networks
// description: Remove CNI networks that do not have containers
// produces:
// - application/json
// parameters:
// - in: query
// 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.
// - label (label=<key>, label=<key>=<value>, label!=<key>, or label!=<key>=<value>) Prune networks with (or without, in case label!=... is used) the specified labels.
// responses:
// 200:
// $ref: "#/responses/NetworkPruneResponse"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/networks/prune"), s.APIHandler(libpod.Prune)).Methods(http.MethodPost)
return nil
}

View File

@ -180,3 +180,21 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool,
}
return response.IsSuccess(), nil
}
// Prune removes unused CNI networks
func Prune(ctx context.Context, options *PruneOptions) ([]*entities.NetworkPruneReport, error) {
// TODO Filters is not implemented
var (
prunedNetworks []*entities.NetworkPruneReport
)
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/networks/prune", nil, nil)
if err != nil {
return nil, err
}
return prunedNetworks, response.Process(&prunedNetworks)
}

View File

@ -74,3 +74,9 @@ type ConnectOptions struct {
// if a network exists
type ExistsOptions struct {
}
//go:generate go run ../generator/generator.go PruneOptions
// PruneOptions are optional options for removing unused
// CNI networks
type PruneOptions struct {
}

View File

@ -0,0 +1,75 @@
package network
import (
"net/url"
"reflect"
"strings"
"github.com/containers/podman/v2/pkg/bindings/util"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
)
/*
This file is generated automatically by go generate. Do not edit.
*/
// Changed
func (o *PruneOptions) Changed(fieldName string) bool {
r := reflect.ValueOf(o)
value := reflect.Indirect(r).FieldByName(fieldName)
return !value.IsNil()
}
// ToParams
func (o *PruneOptions) ToParams() (url.Values, error) {
params := url.Values{}
if o == nil {
return params, nil
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
s := reflect.ValueOf(o)
if reflect.Ptr == s.Kind() {
s = s.Elem()
}
sType := s.Type()
for i := 0; i < s.NumField(); i++ {
fieldName := sType.Field(i).Name
if !o.Changed(fieldName) {
continue
}
fieldName = strings.ToLower(fieldName)
f := s.Field(i)
if reflect.Ptr == f.Kind() {
f = f.Elem()
}
switch {
case util.IsSimpleType(f):
params.Set(fieldName, util.SimpleTypeToParam(f))
case f.Kind() == reflect.Slice:
for i := 0; i < f.Len(); i++ {
elem := f.Index(i)
if util.IsSimpleType(elem) {
params.Add(fieldName, util.SimpleTypeToParam(elem))
} else {
return nil, errors.New("slices must contain only simple types")
}
}
case f.Kind() == reflect.Map:
lowerCaseKeys := make(map[string][]string)
iter := f.MapRange()
for iter.Next() {
lowerCaseKeys[iter.Key().Interface().(string)] = iter.Value().Interface().([]string)
}
s, err := json.MarshalToString(lowerCaseKeys)
if err != nil {
return nil, err
}
params.Set(fieldName, s)
}
}
return params, nil
}

View File

@ -63,6 +63,7 @@ type ContainerEngine interface {
NetworkExists(ctx context.Context, networkname string) (*BoolReport, error)
NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]NetworkInspectReport, []error, error)
NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error)
NetworkPrune(ctx context.Context, options NetworkPruneOptions) ([]*NetworkPruneReport, error)
NetworkReload(ctx context.Context, names []string, options NetworkReloadOptions) ([]*NetworkReloadReport, error)
NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error)
PlayKube(ctx context.Context, path string, opts PlayKubeOptions) (*PlayKubeReport, error)

View File

@ -80,3 +80,15 @@ type NetworkConnectOptions struct {
Aliases []string
Container string
}
// NetworkPruneReport containers the name of network and an error
// associated in its pruning (removal)
// swagger:model NetworkPruneReport
type NetworkPruneReport struct {
Name string
Error error
}
// NetworkPruneOptions describes options for pruning
// unused cni networks
type NetworkPruneOptions struct{}

View File

@ -155,3 +155,28 @@ func (ic *ContainerEngine) NetworkExists(ctx context.Context, networkname string
Value: exists,
}, nil
}
// Network prune removes unused cni networks
func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.NetworkPruneOptions) ([]*entities.NetworkPruneReport, error) {
runtimeConfig, err := ic.Libpod.GetConfig()
if err != nil {
return nil, err
}
cons, err := ic.Libpod.GetAllContainers()
if err != nil {
return nil, err
}
// Gather up all the non-default networks that the
// containers want
usedNetworks := 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
}
}
return network.PruneNetworks(runtimeConfig, usedNetworks)
}

View File

@ -89,3 +89,8 @@ func (ic *ContainerEngine) NetworkExists(ctx context.Context, networkname string
Value: exists,
}, nil
}
// Network prune removes unused cni networks
func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.NetworkPruneOptions) ([]*entities.NetworkPruneReport, error) {
return network.Prune(ic.ClientCtx, nil)
}

View File

@ -484,7 +484,7 @@ class TestApi(unittest.TestCase):
self.assertEqual(inspect.status_code, 404, inspect.content)
prune = requests.post(PODMAN_URL + "/v1.40/networks/prune")
self.assertEqual(prune.status_code, 404, prune.content)
self.assertEqual(prune.status_code, 200, prune.content)
def test_volumes_compat(self):
name = "Volume_" + "".join(random.choice(string.ascii_letters) for i in range(10))

View File

@ -540,4 +540,54 @@ var _ = Describe("Podman network", func() {
nc.WaitWithDefaultTimeout()
Expect(nc.ExitCode()).To(Equal(0))
})
It("podman network prune", func() {
// Create two networks
// Check they are there
// Run a container on one of them
// Network Prune
// Check that one has been pruned, other remains
net := "macvlan" + stringid.GenerateNonCryptoID()
net1 := net + "1"
net2 := net + "2"
nc := podmanTest.Podman([]string{"network", "create", net1})
nc.WaitWithDefaultTimeout()
defer podmanTest.removeCNINetwork(net1)
Expect(nc.ExitCode()).To(Equal(0))
nc2 := podmanTest.Podman([]string{"network", "create", net2})
nc2.WaitWithDefaultTimeout()
defer podmanTest.removeCNINetwork(net2)
Expect(nc2.ExitCode()).To(Equal(0))
list := podmanTest.Podman([]string{"network", "ls", "--format", "{{.Name}}"})
list.WaitWithDefaultTimeout()
Expect(list.ExitCode()).To(BeZero())
Expect(StringInSlice(net1, list.OutputToStringArray())).To(BeTrue())
Expect(StringInSlice(net2, list.OutputToStringArray())).To(BeTrue())
if !isRootless() {
Expect(StringInSlice("podman", list.OutputToStringArray())).To(BeTrue())
}
session := podmanTest.Podman([]string{"run", "-dt", "--net", net2, ALPINE, "top"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
prune := podmanTest.Podman([]string{"network", "prune", "-f"})
prune.WaitWithDefaultTimeout()
Expect(prune.ExitCode()).To(BeZero())
listAgain := podmanTest.Podman([]string{"network", "ls", "--format", "{{.Name}}"})
listAgain.WaitWithDefaultTimeout()
Expect(listAgain.ExitCode()).To(BeZero())
Expect(StringInSlice(net1, listAgain.OutputToStringArray())).To(BeFalse())
Expect(StringInSlice(net2, listAgain.OutputToStringArray())).To(BeTrue())
// Make sure default network 'podman' still exists
if !isRootless() {
Expect(StringInSlice("podman", list.OutputToStringArray())).To(BeTrue())
}
})
})