Browse Source

Display summary of reclaimed ACI resources (CPU/mem) in `docker prune`

Signed-off-by: Guillaume Tardif <[email protected]>
Guillaume Tardif 5 years ago
parent
commit
50a2ae1100
6 changed files with 76 additions and 49 deletions
  1. 40 34
      aci/convert/convert.go
  2. 16 3
      aci/resources.go
  3. 2 2
      api/client/resources.go
  4. 7 1
      api/resources/api.go
  5. 7 4
      cli/cmd/prune.go
  6. 4 5
      tests/aci-e2e/e2e-aci_test.go

+ 40 - 34
aci/convert/convert.go

@@ -380,7 +380,7 @@ func (s serviceConfigAciHelper) getResourceRequestsLimits() (*containerinstance.
 		return s.Deploy != nil && s.Deploy.Resources.Reservations != nil && s.Deploy.Resources.Reservations.NanoCPUs != ""
 	}
 	if hasMemoryRequest() {
-		memRequest = bytesToGb(s.Deploy.Resources.Reservations.MemoryBytes)
+		memRequest = BytesToGB(float64(s.Deploy.Resources.Reservations.MemoryBytes))
 	}
 
 	if hasCPURequest() {
@@ -393,7 +393,7 @@ func (s serviceConfigAciHelper) getResourceRequestsLimits() (*containerinstance.
 	cpuLimit := cpuRequest
 	if s.Deploy != nil && s.Deploy.Resources.Limits != nil {
 		if s.Deploy.Resources.Limits.MemoryBytes != 0 {
-			memLimit = bytesToGb(s.Deploy.Resources.Limits.MemoryBytes)
+			memLimit = BytesToGB(float64(s.Deploy.Resources.Limits.MemoryBytes))
 			if !hasMemoryRequest() {
 				memRequest = memLimit
 			}
@@ -438,8 +438,9 @@ func getEnvVariables(composeEnv types.MappingWithEquals) *[]containerinstance.En
 	return &result
 }
 
-func bytesToGb(b types.UnitBytes) float64 {
-	f := float64(b) / 1024 / 1024 / 1024 // from bytes to gigabytes
+// BytesToGB convert bytes To GB
+func BytesToGB(b float64) float64 {
+	f := b / 1024 / 1024 / 1024 // from bytes to gigabytes
 	return math.Round(f*100) / 100
 }
 
@@ -472,29 +473,6 @@ func fqdn(group containerinstance.ContainerGroup, region string) string {
 
 // ContainerGroupToContainer composes a Container from an ACI container definition
 func ContainerGroupToContainer(containerID string, cg containerinstance.ContainerGroup, cc containerinstance.Container, region string) containers.Container {
-	memLimits := uint64(0)
-	memRequest := uint64(0)
-	cpuLimit := 0.
-	cpuReservation := 0.
-	if cc.Resources != nil {
-		if cc.Resources.Limits != nil {
-			if cc.Resources.Limits.MemoryInGB != nil {
-				memLimits = gbToBytes(*cc.Resources.Limits.MemoryInGB)
-			}
-			if cc.Resources.Limits.CPU != nil {
-				cpuLimit = *cc.Resources.Limits.CPU
-			}
-		}
-		if cc.Resources.Requests != nil {
-			if cc.Resources.Requests.MemoryInGB != nil {
-				memRequest = gbToBytes(*cc.Resources.Requests.MemoryInGB)
-			}
-			if cc.Resources.Requests.CPU != nil {
-				cpuReservation = *cc.Resources.Requests.CPU
-			}
-		}
-	}
-
 	command := ""
 	if cc.Command != nil {
 		command = strings.Join(*cc.Command, " ")
@@ -511,17 +489,11 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe
 		}
 	}
 
+	hostConfig := ToHostConfig(cc, cg)
 	config := &containers.RuntimeConfig{
 		FQDN: fqdn(cg, region),
 		Env:  envVars,
 	}
-	hostConfig := &containers.HostConfig{
-		CPULimit:          cpuLimit,
-		CPUReservation:    cpuReservation,
-		MemoryLimit:       memLimits,
-		MemoryReservation: memRequest,
-		RestartPolicy:     toContainerRestartPolicy(cg.RestartPolicy),
-	}
 	c := containers.Container{
 		ID:          containerID,
 		Status:      status,
@@ -540,6 +512,40 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe
 	return c
 }
 
+// ToHostConfig convert an ACI container to host config value
+func ToHostConfig(cc containerinstance.Container, cg containerinstance.ContainerGroup) *containers.HostConfig {
+	memLimits := uint64(0)
+	memRequest := uint64(0)
+	cpuLimit := 0.
+	cpuReservation := 0.
+	if cc.Resources != nil {
+		if cc.Resources.Limits != nil {
+			if cc.Resources.Limits.MemoryInGB != nil {
+				memLimits = gbToBytes(*cc.Resources.Limits.MemoryInGB)
+			}
+			if cc.Resources.Limits.CPU != nil {
+				cpuLimit = *cc.Resources.Limits.CPU
+			}
+		}
+		if cc.Resources.Requests != nil {
+			if cc.Resources.Requests.MemoryInGB != nil {
+				memRequest = gbToBytes(*cc.Resources.Requests.MemoryInGB)
+			}
+			if cc.Resources.Requests.CPU != nil {
+				cpuReservation = *cc.Resources.Requests.CPU
+			}
+		}
+	}
+	hostConfig := &containers.HostConfig{
+		CPULimit:          cpuLimit,
+		CPUReservation:    cpuReservation,
+		MemoryLimit:       memLimits,
+		MemoryReservation: memRequest,
+		RestartPolicy:     toContainerRestartPolicy(cg.RestartPolicy),
+	}
+	return hostConfig
+}
+
 // GetStatus returns status for the specified container
 func GetStatus(container containerinstance.Container, group containerinstance.ContainerGroup) string {
 	status := GetGroupStatus(group)

+ 16 - 3
aci/resources.go

@@ -18,6 +18,7 @@ package aci
 
 import (
 	"context"
+	"fmt"
 
 	"github.com/hashicorp/go-multierror"
 
@@ -30,18 +31,28 @@ type aciResourceService struct {
 	aciContext store.AciContext
 }
 
-func (cs *aciResourceService) Prune(ctx context.Context, request resources.PruneRequest) ([]string, error) {
+func (cs *aciResourceService) Prune(ctx context.Context, request resources.PruneRequest) (resources.PruneResult, error) {
 	res, err := getACIContainerGroups(ctx, cs.aciContext.SubscriptionID, cs.aciContext.ResourceGroup)
+	result := resources.PruneResult{}
 	if err != nil {
-		return nil, err
+		return result, err
 	}
 	multierr := &multierror.Error{}
 	deleted := []string{}
+	cpus := 0.
+	mem := 0.
+
 	for _, containerGroup := range res {
 		if !request.Force && convert.GetGroupStatus(containerGroup) == "Node "+convert.StatusRunning {
 			continue
 		}
 
+		for _, container := range *containerGroup.Containers {
+			hostConfig := convert.ToHostConfig(container, containerGroup)
+			cpus += hostConfig.CPUReservation
+			mem += convert.BytesToGB(float64(hostConfig.MemoryReservation))
+		}
+
 		if !request.DryRun {
 			_, err := deleteACIContainerGroup(ctx, cs.aciContext, *containerGroup.Name)
 			multierr = multierror.Append(multierr, err)
@@ -50,5 +61,7 @@ func (cs *aciResourceService) Prune(ctx context.Context, request resources.Prune
 			deleted = append(deleted, *containerGroup.Name)
 		}
 	}
-	return deleted, multierr.ErrorOrNil()
+	result.DeletedIDs = deleted
+	result.Summary = fmt.Sprintf("Total CPUs reclaimed: %.2f, total memory reclaimed: %.2f GB", cpus, mem)
+	return result, multierr.ErrorOrNil()
 }

+ 2 - 2
api/client/resources.go

@@ -27,6 +27,6 @@ type resourceService struct {
 }
 
 // Prune prune resources
-func (c *resourceService) Prune(ctx context.Context, request resources.PruneRequest) ([]string, error) {
-	return nil, errdefs.ErrNotImplemented
+func (c *resourceService) Prune(ctx context.Context, request resources.PruneRequest) (resources.PruneResult, error) {
+	return resources.PruneResult{}, errdefs.ErrNotImplemented
 }

+ 7 - 1
api/resources/api.go

@@ -26,8 +26,14 @@ type PruneRequest struct {
 	DryRun bool
 }
 
+// PruneResult info on what has been pruned
+type PruneResult struct {
+	DeletedIDs []string
+	Summary    string
+}
+
 // Service interacts with the underlying container backend
 type Service interface {
 	// Prune prune resources
-	Prune(ctx context.Context, request PruneRequest) ([]string, error)
+	Prune(ctx context.Context, request PruneRequest) (PruneResult, error)
 }

+ 7 - 4
cli/cmd/prune.go

@@ -56,14 +56,17 @@ func runPrune(ctx context.Context, opts pruneOpts) error {
 		return errors.Wrap(err, "cannot connect to backend")
 	}
 
-	ids, err := c.ResourceService().Prune(ctx, resources.PruneRequest{Force: opts.force, DryRun: opts.dryRun})
+	result, err := c.ResourceService().Prune(ctx, resources.PruneRequest{Force: opts.force, DryRun: opts.dryRun})
 	if opts.dryRun {
-		fmt.Println("resources that would be deleted:")
+		fmt.Println("Resources that would be deleted:")
 	} else {
-		fmt.Println("deleted resources:")
+		fmt.Println("Deleted resources:")
 	}
-	for _, id := range ids {
+	for _, id := range result.DeletedIDs {
 		fmt.Println(id)
 	}
+	if result.Summary != "" {
+		fmt.Println(result.Summary)
+	}
 	return err
 }

+ 4 - 5
tests/aci-e2e/e2e-aci_test.go

@@ -490,21 +490,20 @@ func TestContainerRunAttached(t *testing.T) {
 
 	t.Run("prune dry run", func(t *testing.T) {
 		res := c.RunDockerCmd("prune", "--dry-run")
-		fmt.Println("prune output:")
-		assert.Equal(t, "resources that would be deleted:\n", res.Stdout())
+		assert.Equal(t, "Resources that would be deleted:\nTotal CPUs reclaimed: 0.00, total memory reclaimed: 0.00 GB\n", res.Stdout())
 		res = c.RunDockerCmd("prune", "--dry-run", "--force")
-		assert.Equal(t, "resources that would be deleted:\n"+container+"\n", res.Stdout())
+		assert.Equal(t, "Resources that would be deleted:\n"+container+"\nTotal CPUs reclaimed: 0.10, total memory reclaimed: 0.10 GB\n", res.Stdout())
 	})
 
 	t.Run("prune", func(t *testing.T) {
 		res := c.RunDockerCmd("prune")
-		assert.Equal(t, "deleted resources:\n", res.Stdout())
+		assert.Equal(t, "Deleted resources:\nTotal CPUs reclaimed: 0.00, total memory reclaimed: 0.00 GB\n", res.Stdout())
 		res = c.RunDockerCmd("ps")
 		l := lines(res.Stdout())
 		assert.Equal(t, 2, len(l))
 
 		res = c.RunDockerCmd("prune", "--force")
-		assert.Equal(t, "deleted resources:\n"+container+"\n", res.Stdout())
+		assert.Equal(t, "Deleted resources:\n"+container+"\nTotal CPUs reclaimed: 0.10, total memory reclaimed: 0.10 GB\n", res.Stdout())
 
 		res = c.RunDockerCmd("ps", "--all")
 		l = lines(res.Stdout())