Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable referrers pagination #56

Merged
merged 8 commits into from
Jan 20, 2023
Merged

feat: enable referrers pagination #56

merged 8 commits into from
Jan 20, 2023

Conversation

wangxiaoxuan273
Copy link

@wangxiaoxuan273 wangxiaoxuan273 commented Jan 19, 2023

Signed-off-by: wangxiaoxuan273 [email protected]

Resolves #40

This pr enables the pagination feature of referrers API. The current implementation is slow as it calls generateReferrersList multiple times when pagination is needed.

Users can define page size by using the ?n query parameter. If not used, the default page size is set to 100. Pagination is automatically enabled when the number of returned referrers is greater than the page size (this is conformant to the distribution spec).

  • Test result with pageSize = 4. The test subject has 6 referrers and are returned with 2 pages.
    image

  • Test result with pageSize = 5 using the same test subject. 2 Pages are returned.
    image

Signed-off-by: wangxiaoxuan273 <[email protected]>
@codecov-commenter
Copy link

codecov-commenter commented Jan 20, 2023

Codecov Report

Merging #56 (40cd89f) into main (f27b092) will decrease coverage by 0.14%.
The diff coverage is 0.00%.

@@            Coverage Diff             @@
##             main      #56      +/-   ##
==========================================
- Coverage   56.27%   56.14%   -0.14%     
==========================================
  Files         108      108              
  Lines       11239    11266      +27     
==========================================
  Hits         6325     6325              
- Misses       4202     4229      +27     
  Partials      712      712              
Impacted Files Coverage Δ
registry/handlers/referrers.go 0.00% <0.00%> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

// query parameter. The default page size is 100.
pageSize, nParseError := strconv.Atoi(r.URL.Query().Get("n"))
if nParseError != nil || pageSize < minPageSize || pageSize > maxPageSize {
pageSize = maxPageSize

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to make the default size to 10, which is common.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed maxPageSize to 10.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should have three constants.

constant (
    pageSizeMin     = 1
    pageSizeDefault = 10
    pageSizeMax     = 100
)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the three constants.

@@ -80,6 +97,19 @@ func (h *referrersHandler) GetReferrers(w http.ResponseWriter, r *http.Request)
referrers = []v1.Descriptor{}
}

// only consider pagination if # of referrers is greater than page size
if len(referrers) > pageSize {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if statement is redundant.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleted.

registry/handlers/referrers.go Show resolved Hide resolved
}

// extract the page number info.
pageNumber, _ := strconv.Atoi(r.URL.Query().Get("p"))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pageNumber is not validated. What if it is negative?

Copy link
Author

@wangxiaoxuan273 wangxiaoxuan273 Jan 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the validation. Negative pageNumber would make startIndex invalid.

registry/handlers/referrers.go Outdated Show resolved Hide resolved
Comment on lines 233 to 235
if artifactType != "" {
url = fmt.Sprintf("%s&artifactType=%s", url, artifactType)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may consider to use url.Value since artifactType may contain special characters like /.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used url.Values here. Thanks for this good advice.

Signed-off-by: wangxiaoxuan273 <[email protected]>
// query parameter. The default page size is 100.
pageSize, nParseError := strconv.Atoi(r.URL.Query().Get("n"))
if nParseError != nil || pageSize < minPageSize || pageSize > maxPageSize {
pageSize = maxPageSize

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should have three constants.

constant (
    pageSizeMin     = 1
    pageSizeDefault = 10
    pageSizeMax     = 100
)

}

// extract the page number info.
pageNumber, pParseError := strconv.Atoi(r.URL.Query().Get("p"))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to name each error with a different name.

Suggested change
pageNumber, pParseError := strconv.Atoi(r.URL.Query().Get("p"))
pageNumber, err:= strconv.Atoi(r.URL.Query().Get("p"))

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed accordingly.

var annotations map[string]string
var artifactTypeFilter string
if artifactTypeFilter = r.URL.Query().Get("artifactType"); artifactTypeFilter != "" {
annotations = map[string]string{
v1.AnnotationReferrersFiltersApplied: "artifactType",
}
}

// extract the page size info. Users define page size by using the n
// query parameter. The default page size is 100.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment needs to be updated.

Suggested change
// query parameter. The default page size is 100.
// query parameter. The default page size is 10.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry. Updated.

Comment on lines 103 to 105
if referrers == nil || startIndex > len(referrers) {
referrers = []v1.Descriptor{}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When fall into this code path, the code from line 107 - 113 is still executed but should not.

Copy link
Author

@wangxiaoxuan273 wangxiaoxuan273 Jan 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used an else to enclose this part.

@@ -195,6 +227,20 @@ func readlink(ctx context.Context, path string, stDriver driver.StorageDriver) (
return digest.Parse(string(content))
}

func generateLinkHeader(repoName, subjectDigest, artifactType string, lastDigests []string, nPage int, p int) string {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lastDigests is never used.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to delete this from legacy code. Deleted.

Signed-off-by: wangxiaoxuan273 <[email protected]>
Signed-off-by: wangxiaoxuan273 <[email protected]>
func generateLinkHeader(repoName, subjectDigest, artifactType string, pageSize int, p int) string {
linkURL := fmt.Sprintf("/v2/%s/referrers/%s", repoName, subjectDigest)
v := url.Values{}
v.Add("p", fmt.Sprint(p))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: prefer strconv.Itoa to fmt.Sprint for performance.

Suggested change
v.Add("p", fmt.Sprint(p))
v.Add("p", strconv.Itoa(p))

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed.

v.Add("artifactType", artifactType)
}
if pageSize > 0 {
v.Add("n", fmt.Sprint(pageSize))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed.

Comment on lines 105 to 117
startIndex := pageNumber * pageSize

if referrers == nil || startIndex >= len(referrers) {
referrers = []v1.Descriptor{}
} else {
// if there's only 1 page of results left
if len(referrers)-startIndex <= pageSize {
referrers = referrers[startIndex:]
} else {
referrers = referrers[startIndex:(startIndex + pageSize)]
w.Header().Set("Link", generateLinkHeader(h.Repository.Named().Name(), h.Digest.String(), artifactTypeFilter, pageSize, pageNumber+1))
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: code style: readability

Suggested change
startIndex := pageNumber * pageSize
if referrers == nil || startIndex >= len(referrers) {
referrers = []v1.Descriptor{}
} else {
// if there's only 1 page of results left
if len(referrers)-startIndex <= pageSize {
referrers = referrers[startIndex:]
} else {
referrers = referrers[startIndex:(startIndex + pageSize)]
w.Header().Set("Link", generateLinkHeader(h.Repository.Named().Name(), h.Digest.String(), artifactTypeFilter, pageSize, pageNumber+1))
}
}
startIndex := pageNumber * pageSize
if referrers == nil || len(referrers) <= startIndex {
referrers = []v1.Descriptor{}
} else if len(referrers) <= startIndex+pageSize {
// only 1 page of results left
referrers = referrers[startIndex:]
} else {
referrers = referrers[startIndex:(startIndex + pageSize)]
w.Header().Set("Link", generateLinkHeader(h.Repository.Named().Name(), h.Digest.String(), artifactTypeFilter, pageSize, pageNumber+1))
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed.

Signed-off-by: wangxiaoxuan273 <[email protected]>
// only 1 page of results left
referrers = referrers[startIndex:]
} else {
referrers = referrers[startIndex:(startIndex + pageSize)]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
referrers = referrers[startIndex:(startIndex + pageSize)]
referrers = referrers[startIndex:startIndex + pageSize]

@@ -195,6 +229,19 @@ func readlink(ctx context.Context, path string, stDriver driver.StorageDriver) (
return digest.Parse(string(content))
}

func generateLinkHeader(repoName, subjectDigest, artifactType string, pageSize int, p int) string {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: code style: variable naming

Suggested change
func generateLinkHeader(repoName, subjectDigest, artifactType string, pageSize int, p int) string {
func generateLinkHeader(repoName, subjectDigest, artifactType string, pageSize int, pageNumber int) string {

Signed-off-by: wangxiaoxuan273 <[email protected]>
Copy link

@shizhMSFT shizhMSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@shizhMSFT shizhMSFT merged commit 5468963 into oras-project:main Jan 20, 2023
@wangxiaoxuan273 wangxiaoxuan273 deleted the pagination branch January 20, 2023 08:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enable pagination of referrers API
3 participants