diff --git a/aci/convert/convert.go b/aci/convert/convert.go index f67168b9..1119ae8b 100644 --- a/aci/convert/convert.go +++ b/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,6 +473,47 @@ 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 { + command := "" + if cc.Command != nil { + command = strings.Join(*cc.Command, " ") + } + + status := GetStatus(cc, cg) + platform := string(cg.OsType) + + var envVars map[string]string = nil + if cc.EnvironmentVariables != nil && len(*cc.EnvironmentVariables) != 0 { + envVars = map[string]string{} + for _, envVar := range *cc.EnvironmentVariables { + envVars[*envVar.Name] = *envVar.Value + } + } + + hostConfig := ToHostConfig(cc, cg) + config := &containers.RuntimeConfig{ + FQDN: fqdn(cg, region), + Env: envVars, + } + c := containers.Container{ + ID: containerID, + Status: status, + Image: to.String(cc.Image), + Command: command, + CPUTime: 0, + MemoryUsage: 0, + PidsCurrent: 0, + PidsLimit: 0, + Ports: ToPorts(cg.IPAddress, *cc.Ports), + Platform: platform, + Config: config, + HostConfig: hostConfig, + } + + 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. @@ -494,27 +536,6 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe } } } - - command := "" - if cc.Command != nil { - command = strings.Join(*cc.Command, " ") - } - - status := GetStatus(cc, cg) - platform := string(cg.OsType) - - var envVars map[string]string = nil - if cc.EnvironmentVariables != nil && len(*cc.EnvironmentVariables) != 0 { - envVars = map[string]string{} - for _, envVar := range *cc.EnvironmentVariables { - envVars[*envVar.Name] = *envVar.Value - } - } - - config := &containers.RuntimeConfig{ - FQDN: fqdn(cg, region), - Env: envVars, - } hostConfig := &containers.HostConfig{ CPULimit: cpuLimit, CPUReservation: cpuReservation, @@ -522,22 +543,7 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe MemoryReservation: memRequest, RestartPolicy: toContainerRestartPolicy(cg.RestartPolicy), } - c := containers.Container{ - ID: containerID, - Status: status, - Image: to.String(cc.Image), - Command: command, - CPUTime: 0, - MemoryUsage: 0, - PidsCurrent: 0, - PidsLimit: 0, - Ports: ToPorts(cg.IPAddress, *cc.Ports), - Platform: platform, - Config: config, - HostConfig: hostConfig, - } - - return c + return hostConfig } // GetStatus returns status for the specified container diff --git a/aci/resources.go b/aci/resources.go index e1beff20..d3619a60 100644 --- a/aci/resources.go +++ b/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() } diff --git a/api/client/resources.go b/api/client/resources.go index 74eb0873..cefb6c48 100644 --- a/api/client/resources.go +++ b/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 } diff --git a/api/resources/api.go b/api/resources/api.go index f3f6f184..729acf1a 100644 --- a/api/resources/api.go +++ b/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) } diff --git a/cli/cmd/prune.go b/cli/cmd/prune.go index b0a61b37..9fb23ff6 100644 --- a/cli/cmd/prune.go +++ b/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 } diff --git a/tests/aci-e2e/e2e-aci_test.go b/tests/aci-e2e/e2e-aci_test.go index d2a5d22c..ddb49105 100644 --- a/tests/aci-e2e/e2e-aci_test.go +++ b/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())