Skip to content

Commit

Permalink
add network prune command
Browse files Browse the repository at this point in the history
Signed-off-by: Junjun Li <junjunli666@gmail.com>
  • Loading branch information
hellolijj committed Jun 7, 2019
1 parent c3e6408 commit 41f7b03
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 0 deletions.
41 changes: 41 additions & 0 deletions apis/server/network_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"

"github.com/alibaba/pouch/apis/types"
"github.com/alibaba/pouch/daemon/mgr"
networktypes "github.com/alibaba/pouch/network/types"
"github.com/alibaba/pouch/pkg/httputils"

Expand Down Expand Up @@ -112,6 +113,46 @@ func (s *Server) disconnectNetwork(ctx context.Context, rw http.ResponseWriter,
return s.ContainerMgr.Disconnect(ctx, network.Container, id, network.Force)
}

func (s *Server) pruneNetwork(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
var volumeResult []string

networkLists, err := s.NetworkMgr.List(ctx, map[string]string{})
if err != nil {
return err
}

toDeleteFlag := map[string]bool{}
toDeleteFlag["none"] = true
toDeleteFlag["host"] = true
toDeleteFlag["bridge"] = true

_, err = s.ContainerMgr.List(ctx, &mgr.ContainerListOption{
All: true,
FilterFunc: func(c *mgr.Container) bool {
if len(c.NetworkSettings.Networks) == 0 {
return false
}
for k := range c.NetworkSettings.Networks {
toDeleteFlag[k] = true
}
return true
},
})

if err != nil {
return err
}

for _, network := range networkLists {
if toDeleteFlag[network.Name] == false {
volumeResult = append(volumeResult, network.Name)
s.NetworkMgr.Remove(ctx, network.Name)
}
}

return EncodeResponse(rw, http.StatusOK, volumeResult)
}

func buildNetworkInspectResp(n *networktypes.Network) *types.NetworkInspectResp {
info := n.Network.Info()
network := &types.NetworkInspectResp{
Expand Down
1 change: 1 addition & 0 deletions apis/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func initRoute(s *Server) *mux.Router {
{Method: http.MethodDelete, Path: "/networks/{id:.*}", HandlerFunc: s.deleteNetwork},
{Method: http.MethodPost, Path: "/networks/{id:.*}/connect", HandlerFunc: s.connectToNetwork},
{Method: http.MethodPost, Path: "/networks/{id:.*}/disconnect", HandlerFunc: s.disconnectNetwork},
{Method: http.MethodPost, Path: "/networks/prune", HandlerFunc: s.pruneNetwork},

// metrics
{Method: http.MethodGet, Path: "/metrics", HandlerFunc: s.metrics},
Expand Down
24 changes: 24 additions & 0 deletions apis/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,30 @@ paths:
$ref: "#/definitions/NetworkDisconnect"
tags: ["Network"]

/networks/prune:
post:
summary: "Delete unused networks"
produces:
- "application/json"
operationId: "NetworkPrune"
responses:
200:
description: "No error"
schema:
type: "object"
title: "NetworkPruneResp"
properties:
NetworksDeleted:
description: "Networks that were deleted"
type: "array"
items:
type: "string"
500:
description: "Server error"
schema:
$ref: "#/responses/500ErrorResponse"
tags: ["Network"]

/commit:
post:
summary: "Create an image from a container"
Expand Down
76 changes: 76 additions & 0 deletions cli/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func (n *NetworkCommand) Init(c *Cli) {
c.AddCommand(n, &NetworkListCommand{})
c.AddCommand(n, &NetworkConnectCommand{})
c.AddCommand(n, &NetworkDisconnectCommand{})
c.AddCommand(n, &NetworkPruneCommand{})
}

// networkCreateDescription is used to describe network create command in detail and auto generate command doc.
Expand Down Expand Up @@ -524,3 +525,78 @@ func (nd *NetworkDisconnectCommand) networkDisconnectExample() string {
return `$ pouch network disconnect bridge test
container test is disconnected from network bridge successfully`
}

// networkPruneDescription is used to describe network prune command in detail and auto generate comand doc.
var NetworkPruneDescription = "Delete all unused networks"

// NetworkPruneCommand use to implement 'network prune' command
type NetworkPruneCommand struct {
baseCommand
force bool
}

// Init initializes 'network prune' command.
func (n *NetworkPruneCommand) Init(c *Cli) {
n.cli = c
n.cmd = &cobra.Command{
Use: "prune",
Short: "Prune networks",
Args: cobra.NoArgs,
Long: networkDisconnectDescription,
RunE: func(cmd *cobra.Command, args []string) error {
return n.runNetworkPrune(args)
},
Example: n.networkPruneExample(),
}
n.addFlags()
}

// addFlags adds flags for specific command.
func (n *NetworkPruneCommand) addFlags() {
// add flags
flagSet := n.cmd.Flags()
flagSet.BoolVarP(&n.force, "force", "f", false, "Do not prompt for confirmation")
}

// runNetworkPrune is use to delete unused networks.
func (n *NetworkPruneCommand) runNetworkPrune(args []string) error {
logrus.Debugf("prune the network")

ctx := context.Background()
apiClient := n.cli.Client()

if !n.force {
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]")
var input string
fmt.Scanf("%s", &input)
if input != "Y" && input != "y" {
return nil
}
}

networkResult, err := apiClient.NetworkPrune(ctx)
if err != nil {
return err
}

if len(networkResult) > 0 {
fmt.Println("Deleted Networks:")
for _, networkName := range networkResult {
fmt.Println(networkName)
}
}

return nil
}

// runNetworkPrune is use to delete unused networks.
func (n *NetworkPruneCommand) networkPruneExample() string {
return `$ pouch network prune
WARNING! This will remove all networks not used by at least one container.
Are you sure you want to continue? [y/N]
Deleted Networks:
n1
n2
`
}
1 change: 1 addition & 0 deletions client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,5 @@ type NetworkAPIClient interface {
NetworkList(ctx context.Context) ([]types.NetworkResource, error)
NetworkConnect(ctx context.Context, network string, req *types.NetworkConnect) error
NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error
NetworkPrune(ctx context.Context) ([]string, error)
}
20 changes: 20 additions & 0 deletions client/network_prune.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package client

import (
"context"
"net/url"
)

// NetworkPrune delete unused networks.
func (client *APIClient) NetworkPrune(ctx context.Context) ([]string, error) {
resp, err := client.post(ctx, "/networks/prune", url.Values{}, nil, nil)
if err != nil {
return nil, err
}

result := []string{}
err = decodeBody(&result, resp.Body)
ensureCloseReader(resp)

return result, err
}
66 changes: 66 additions & 0 deletions client/network_prune_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package client

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

func TestNetworkPruneError(t *testing.T) {
client := &APIClient{
HTTPCli: newMockClient(errorMockResponse(http.StatusInternalServerError, "Server error")),
}

_, err := client.NetworkPrune(context.Background())

if err == nil || err.Error() == "Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}

func TestNetworkPruneList(t *testing.T) {
expectedURL := "/networks/prune"

httpClient := newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL)
}

networksPruneJSON := []string{
"network0",
"network1",
"network2",
}

b, err := json.Marshal(networksPruneJSON)
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(b))),
}, nil
})

client := &APIClient{
HTTPCli: httpClient,
}
networkPruneResp, err := client.NetworkPrune(context.Background())
if err != nil {
t.Fatal(err)
}

if len(networkPruneResp) != 3 {
t.Fatalf("expected 3 networks, got %v", networkPruneResp)
}
assert.Equal(t, networkPruneResp[0], "network0")
assert.Equal(t, networkPruneResp[1], "network1")
assert.Equal(t, networkPruneResp[2], "network2")
}
9 changes: 9 additions & 0 deletions daemon/mgr/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ type NetworkMgr interface {

// GetNetworkStats returns the network stats of specific sandbox
GetNetworkStats(sandboxID string) (map[string]apitypes.NetworkStats, error)

// NetworkPrune is used to delete unused networks.
Prune(ctx context.Context) ([]string, error)
}

// NetworkManager is the default implement of interface NetworkMgr.
Expand Down Expand Up @@ -477,6 +480,12 @@ func (nm *NetworkManager) GetNetworkStats(sandboxID string) (map[string]apitypes
return stats, nil
}

// Prune is used to delete unused networks.
func (nm *NetworkManager) Prune(ctx context.Context) ([]string, error) {

return []string{"aaa", "bbb"}, nil
}

// Controller returns the network controller.
func (nm *NetworkManager) Controller() libnetwork.NetworkController {
return nm.controller
Expand Down
42 changes: 42 additions & 0 deletions test/api_network_prune_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"github.com/alibaba/pouch/test/command"
"github.com/alibaba/pouch/test/environment"
"github.com/alibaba/pouch/test/request"

"github.com/go-check/check"
"github.com/gotestyourself/gotestyourself/icmd"
)

// APINetworkPruneSuite is the test suite for network prune API.
type APINetworkPruneSuite struct{}

func init() {
check.Suite(&APINetworkPruneSuite{})
}

// SetUpTest does common setup in the beginning of each test.
func (suite *APINetworkPruneSuite) SetUpTest(c *check.C) {
SkipIfFalse(c, environment.IsLinux)
}

// TestNetworkPruneOk test if prune network is ok
func (suite *APINetworkPruneSuite) TestNetworkPruneOk(c *check.C) {
network1 := "TestPruneNetwork1"
command.PouchRun("network", "create", network1, "-d", "bridge", "--gateway", "192.168.1.1", "--subnet", "192.168.1.0/24").Assert(c, icmd.Success)

network2 := "TestPruneNetwork2"
command.PouchRun("network", "create", network2, "-d", "bridge", "--gateway", "192.168.2.1", "--subnet", "192.168.2.0/24").Assert(c, icmd.Success)

// prune the network.
path := "/networks/prune"
resp, err := request.Post(path)
c.Assert(err, check.IsNil)
CheckRespStatus(c, resp, 200)

var networkPruneResp []string
err = request.DecodeBody(&networkPruneResp, resp.Body)
c.Assert(err, check.IsNil)
c.Assert(len(networkPruneResp), check.Equals, 2)
}
39 changes: 39 additions & 0 deletions test/cli_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,3 +605,42 @@ func (suite *PouchNetworkSuite) TestNetworkConnectWithRestart(c *check.C) {

c.Assert(found, check.Equals, false)
}

// TestNetworkPrune is to verify the correctness of 'network prune' command.
func (suite *PouchNetworkSuite) TestNetworkPrune(c *check.C) {
network1 := "TestPruneNetwork1"
command.PouchRun("network", "create", network1, "-d", "bridge", "--gateway", "192.168.1.1", "--subnet", "192.168.1.0/24").Assert(c, icmd.Success)

network2 := "TestPruneNetwork2"
command.PouchRun("network", "create", network2, "-d", "bridge", "--gateway", "192.168.2.1", "--subnet", "192.168.2.0/24").Assert(c, icmd.Success)

network3 := "TestPruneNetwork3"
command.PouchRun("network", "create", network3, "-d", "bridge", "--gateway", "192.168.3.1", "--subnet", "192.168.3.0/24").Assert(c, icmd.Success)
defer func() {
command.PouchRun("network", "rm", network3).Assert(c, icmd.Success)
}()

// create container
containerName := "TestNetworkPruneContainer"
command.PouchRun("run", "-d", "--name", containerName, busyboxImage, "top").Assert(c, icmd.Success)
defer func() {
command.PouchRun("rm", "-f", containerName).Assert(c, icmd.Success)
}()

// connect a network
command.PouchRun("network", "connect", network3, containerName).Assert(c, icmd.Success)

// network prune
ret := command.PouchRun("network", "prune", "-f")
ret.Assert(c, icmd.Success)
out := ret.Combined()

found := false
for _, line := range strings.Split(out, "\n") {
if strings.Contains(line, "network1") || strings.Contains(line, "network2") {
found = true
break
}
}
c.Assert(found, check.Equals, false)
}

0 comments on commit 41f7b03

Please sign in to comment.