Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Reduce sorts done by ListImagesWithOptions #2338

Merged
merged 2 commits into from
Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 103 additions & 59 deletions api/v6/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,54 +26,16 @@ type Container struct {
NewFilteredImagesCount int `json:",omitempty"`
}

// NewContainer creates a Container given a list of images and the current image
func NewContainer(name string, images []image.Info, currentImage image.Info, tagPattern policy.Pattern, fields []string) (Container, error) {
sorted := update.SortImages(images, tagPattern)

// All images
imagesCount := len(sorted)
imagesErr := ""
if sorted == nil {
imagesErr = registry.ErrNoImageData.Error()
}
var newImages update.SortedImageInfos
for _, img := range sorted {
if tagPattern.Newer(&img, &currentImage) {
newImages = append(newImages, img)
}
}
newImagesCount := len(newImages)

// Filtered images (which respects sorting)
filteredImages := update.SortedImageInfos(update.FilterImages(sorted, tagPattern))
filteredImagesCount := len(filteredImages)
var newFilteredImages update.SortedImageInfos
for _, img := range filteredImages {
if tagPattern.Newer(&img, &currentImage) {
newFilteredImages = append(newFilteredImages, img)
}
}
newFilteredImagesCount := len(newFilteredImages)
latestFiltered, _ := filteredImages.Latest()

container := Container{
Name: name,
Current: currentImage,
LatestFiltered: latestFiltered,

Available: sorted,
AvailableError: imagesErr,
AvailableImagesCount: imagesCount,
NewAvailableImagesCount: newImagesCount,
FilteredImagesCount: filteredImagesCount,
NewFilteredImagesCount: newFilteredImagesCount,
}
return filterContainerFields(container, fields)
type imageSorter interface {
// SortedImages returns the known images, sorted according to the
// pattern given
SortedImages(policy.Pattern) update.SortedImageInfos
// Images returns the images in no defined order
Images() []image.Info
}

// filterContainerFields returns a new container with only the fields specified. If not fields are specified,
// a list of default fields is used.
func filterContainerFields(container Container, fields []string) (Container, error) {
// NewContainer creates a Container given a list of images and the current image
func NewContainer(name string, images imageSorter, currentImage image.Info, tagPattern policy.Pattern, fields []string) (Container, error) {
// Default fields
if len(fields) == 0 {
fields = []string{
Expand All @@ -90,29 +52,111 @@ func filterContainerFields(container Container, fields []string) (Container, err
}

var c Container

// The following machinery attempts to minimise the number of
// filters (`O(n)`) and sorts (`O(n log n)`), by memoising and
// sharing intermediate results.

var (
sortedImages update.SortedImageInfos
filteredImages []image.Info
sortedFilteredImages update.SortedImageInfos
)

getFilteredImages := func() []image.Info {
if filteredImages == nil {
filteredImages = update.FilterImages(images.Images(), tagPattern)
}
return filteredImages
}

getSortedFilteredImages := func() update.SortedImageInfos {
if sortedFilteredImages == nil {
sortedFilteredImages = update.SortImages(getFilteredImages(), tagPattern)
}
return sortedFilteredImages
}

getSortedImages := func() update.SortedImageInfos {
if sortedImages == nil {
sortedImages = images.SortedImages(tagPattern)
// now that we have the sorted images anyway, the fastest
// way to get sorted, filtered images will be to filter
// the already sorted images
getSortedFilteredImages = func() update.SortedImageInfos {
if sortedFilteredImages == nil {
sortedFilteredImages = update.FilterImages(sortedImages, tagPattern)
}
return sortedFilteredImages
}
getFilteredImages = func() []image.Info {
return []image.Info(getSortedFilteredImages())
}
}
return sortedImages
}

// do these after we've gone through all the field names, since
// they depend on what else is happening
assignFields := []func(){}

for _, field := range fields {
switch field {
// these first few rely only on the inputs
case "Name":
c.Name = container.Name
c.Name = name
case "Current":
c.Current = container.Current
case "LatestFiltered":
c.LatestFiltered = container.LatestFiltered
case "Available":
c.Available = container.Available
c.Current = currentImage
case "AvailableError":
c.AvailableError = container.AvailableError
if images == nil {
c.AvailableError = registry.ErrNoImageData.Error()
}
case "AvailableImagesCount":
c.AvailableImagesCount = container.AvailableImagesCount
c.AvailableImagesCount = len(images.Images())

// these required the sorted images, which we can get
// straight away
case "Available":
c.Available = getSortedImages()
case "NewAvailableImagesCount":
c.NewAvailableImagesCount = container.NewAvailableImagesCount
case "FilteredImagesCount":
c.FilteredImagesCount = container.FilteredImagesCount
case "NewFilteredImagesCount":
c.NewFilteredImagesCount = container.NewFilteredImagesCount
newImagesCount := 0
for _, img := range getSortedImages() {
if !tagPattern.Newer(&img, &currentImage) {
break
}
newImagesCount++
}
c.NewAvailableImagesCount = newImagesCount

// these depend on what else gets calculated, so do them afterwards
case "LatestFiltered": // needs sorted, filtered images
assignFields = append(assignFields, func() {
latest, _ := getSortedFilteredImages().Latest()
c.LatestFiltered = latest
})
case "FilteredImagesCount": // needs filtered tags
assignFields = append(assignFields, func() {
c.FilteredImagesCount = len(getFilteredImages())
})
case "NewFilteredImagesCount": // needs filtered images
assignFields = append(assignFields, func() {
newFilteredImagesCount := 0
for _, img := range getSortedFilteredImages() {
if !tagPattern.Newer(&img, &currentImage) {
break
}
newFilteredImagesCount++
}
c.NewFilteredImagesCount = newFilteredImagesCount
})
default:
return c, errors.Errorf("%s is an invalid field", field)
}
}

for _, fn := range assignFields {
fn()
}

return c, nil
}
113 changes: 54 additions & 59 deletions api/v6/container_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
package v6

import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"

"github.com/weaveworks/flux/image"
"github.com/weaveworks/flux/policy"
"github.com/weaveworks/flux/update"
)

type justSlice []image.Info

func (j justSlice) Images() []image.Info {
return []image.Info(j)
}

func (j justSlice) SortedImages(p policy.Pattern) update.SortedImageInfos {
return update.SortImages(j.Images(), p)
}

func TestNewContainer(t *testing.T) {

testImage := image.Info{ImageID: "test"}
Expand Down Expand Up @@ -71,81 +81,66 @@ func TestNewContainer(t *testing.T) {
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewContainer(tt.args.name, tt.args.images, tt.args.currentImage, tt.args.tagPattern, tt.args.fields)
assert.Equal(t, tt.wantErr, err != nil)
assert.Equal(t, tt.want, got)
})
}
}

func TestFilterContainerFields(t *testing.T) {
testContainer := Container{
Name: "test",
Current: image.Info{ImageID: "123"},
LatestFiltered: image.Info{ImageID: "123"},
Available: []image.Info{{ImageID: "123"}},
AvailableError: "test",
AvailableImagesCount: 1,
NewAvailableImagesCount: 2,
FilteredImagesCount: 3,
NewFilteredImagesCount: 4,
}

type args struct {
container Container
fields []string
}
tests := []struct {
name string
args args
want Container
wantErr bool
}{
{
name: "Default fields",
name: "Require only some calculations",
args: args{
container: testContainer,
name: "container-some",
images: []image.Info{currentSemver, newSemver, oldSemver, testImage},
currentImage: currentSemver,
tagPattern: policy.NewPattern("semver:*"),
fields: []string{"Name", "NewFilteredImagesCount"}, // but not, e.g., "FilteredImagesCount"
},
want: Container{
Name: "container-some",
NewFilteredImagesCount: 1,
},
want: testContainer,
wantErr: false,
},
{
name: "FilterImages",
name: "Fields in one order",
args: args{
container: testContainer,
fields: []string{"Name", "Available", "NewAvailableImagesCount", "NewFilteredImagesCount"},
name: "container-ordered1",
images: []image.Info{currentSemver, newSemver, oldSemver, testImage},
currentImage: currentSemver,
tagPattern: policy.NewPattern("semver:*"),
fields: []string{"Name",
"AvailableImagesCount", "Available", // these two both depend on the same intermediate result
"LatestFiltered", "FilteredImagesCount", // these two both depend on another intermediate result
},
},
want: Container{
Name: "test",
Available: []image.Info{{ImageID: "123"}},
NewAvailableImagesCount: 2,
NewFilteredImagesCount: 4,
Name: "container-ordered1",
Available: []image.Info{newSemver, currentSemver, oldSemver, testImage},
AvailableImagesCount: 4,
LatestFiltered: newSemver,
FilteredImagesCount: 3,
},
wantErr: false,
},
{
name: "Invalid field",
name: "Fields in another order",
args: args{
container: testContainer,
fields: []string{"Invalid"},
name: "container-ordered2",
images: []image.Info{currentSemver, newSemver, oldSemver, testImage},
currentImage: currentSemver,
tagPattern: policy.NewPattern("semver:*"),
fields: []string{"Name",
"Available", "AvailableImagesCount", // these two latter depend on the same intermediate result, as above
"FilteredImagesCount", "LatestFiltered", // as above, similarly
},
},
want: Container{
Name: "container-ordered2",
Available: []image.Info{newSemver, currentSemver, oldSemver, testImage},
AvailableImagesCount: 4,
LatestFiltered: newSemver,
FilteredImagesCount: 3,
},
want: Container{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := filterContainerFields(tt.args.container, tt.args.fields)
if (err != nil) != tt.wantErr {
t.Errorf("FilterContainerFields() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("FilterContainerFields() = %v, want %v", got, tt.want)
}
got, err := NewContainer(tt.args.name, justSlice(tt.args.images), tt.args.currentImage, tt.args.tagPattern, tt.args.fields)
assert.Equal(t, tt.wantErr, err != nil)
assert.Equal(t, tt.want, got)
})
}
}
Loading