-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Verify manifest with remote layers #13001
Verify manifest with remote layers #13001
Conversation
cf089ab
to
aea2c6c
Compare
[test] |
test/end-to-end/core.sh
Outdated
os::cmd::expect_success "oc login -u user0 -p pass" | ||
os::cmd::expect_success "oc new-project project0" | ||
os::cmd::expect_success "oc new-build -D \$'FROM busybox:glibc\nRUN echo abc >/tmp/foo'" | ||
os::cmd::try_until_success "oc get build/busybox-1 -o go-template={{.metadata.name}} 2>&1 |grep busybox-1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
os::cmd::try_until_text "oc get build/busybox-1 -o 'jsonpath={.metadata.name}'" "busybox-1"
test/end-to-end/core.sh
Outdated
os::cmd::expect_success "oc new-project project0" | ||
os::cmd::expect_success "oc new-build -D \$'FROM busybox:glibc\nRUN echo abc >/tmp/foo'" | ||
os::cmd::try_until_success "oc get build/busybox-1 -o go-template={{.metadata.name}} 2>&1 |grep busybox-1" | ||
os::cmd::try_until_not_text "oc get build/busybox-1 -o go-template='{{.status.phase}}' 2>&1" "^(Pending|Running)\$" "$(( 10*TIME_MIN ))" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
os::cmd::try_until_not_text "oc get build/busybox-1 -o 'jsonpath={.status.phase}'" "(Pending|Running)" "$(( 10*TIME_MIN ))"
Although -- this seems a little weird, if we follow this up with expecting Complete
we should just do one wait
test/end-to-end/core.sh
Outdated
os::cmd::expect_success "oc new-build -D \$'FROM busybox:glibc\nRUN echo abc >/tmp/foo'" | ||
os::cmd::try_until_success "oc get build/busybox-1 -o go-template={{.metadata.name}} 2>&1 |grep busybox-1" | ||
os::cmd::try_until_not_text "oc get build/busybox-1 -o go-template='{{.status.phase}}' 2>&1" "^(Pending|Running)\$" "$(( 10*TIME_MIN ))" | ||
os::cmd::expect_success_and_text "oc get build/busybox-1 -o go-template='{{.status.phase}}'" '^Complete$' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
os::cmd::try_until_text "oc get build/busybox-1 -o jsonpath='{.status.phase}'" 'Complete' "$(( 10*TIME_MIN ))"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just after oc new-build ...
output of oc get builds
looks like this:
RUN: oc get builds
No resources found.
RUN: oc get builds
NAME TYPE FROM STATUS STARTED DURATION
busybox-1 Docker Dockerfile Pending
RUN: oc get builds
NAME TYPE FROM STATUS STARTED DURATION
busybox-1 Docker Dockerfile Pending
RUN: oc get builds
NAME TYPE FROM STATUS STARTED DURATION
busybox-1 Docker Dockerfile Running 1 second ago
RUN: oc get builds
NAME TYPE FROM STATUS STARTED DURATION
busybox-1 Docker Dockerfile Complete 2 seconds ago 1s
If I leave only the os::cmd::try_until_text "oc get build/busybox-1 -o jsonpath='{.status.phase}'" 'Complete' "$(( 10*TIME_MIN ))"
, in case of an error, the test will wait a very long time.
In my test first os::cmd::try_until_success
waits for the appearance of the build. Second os::cmd::try_until_not_text
waits for any completion of build. And the last os::cmd::expect_success_and_text
checks status of build.
/cc @mfojtik
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stevekuznetsov @legionus comment makes sense to me :-) we don't want to wait 10 minutes in case this test fails ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we almost always just take the long way -- see also the conformance timeout. It's more readable and expresses your intent better. I don't feel too strongly.
The changes lgtm, although the test error worries me, since it failed on pullthrough test, specifically and this PR modifies that part. Aside from that Steve's comments needs addressing. |
"github.com/openshift/origin/pkg/image/importer" | ||
) | ||
|
||
type BlobGetterService interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
godoc
distribution.BlobServer | ||
} | ||
|
||
type remoteBlobGetterService struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
godoc
test/end-to-end/core.sh
Outdated
os::cmd::expect_success 'oc rollout status dc/docker-registry' | ||
os::log::info "Registry configured to disble mirroring" | ||
|
||
# verify local image based on remote image can be pushed to local registry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for the comment since there's an info line just below. Though I like the text in the comment more.
test/end-to-end/core.sh
Outdated
os::log::info "Configure registry to disable mirroring" | ||
os::cmd::expect_success "oc project '${CLUSTER_ADMIN_CONTEXT}'" | ||
os::cmd::expect_success 'oc env -n default dc/docker-registry REGISTRY_MIDDLEWARE_REPOSITORY_OPENSHIFT_MIRRORPULLTHROUGH=false' | ||
os::cmd::expect_success 'oc env -n default dc/docker-registry REGISTRY_MIDDLEWARE_REPOSITORY_OPENSHIFT_PULLTHROUGH=true' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This holds true by default.
test/end-to-end/core.sh
Outdated
os::cmd::try_until_success "oc get build/busybox-1 -o go-template={{.metadata.name}} 2>&1 |grep busybox-1" | ||
os::cmd::try_until_not_text "oc get build/busybox-1 -o go-template='{{.status.phase}}' 2>&1" "^(Pending|Running)\$" "$(( 10*TIME_MIN ))" | ||
os::cmd::expect_success_and_text "oc get build/busybox-1 -o go-template='{{.status.phase}}'" '^Complete$' | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also ensure the istag exists?
@@ -123,130 +54,57 @@ func (r *pullthroughBlobStore) proxyStat(ctx context.Context, retriever importer | |||
// success response with no actual body content. | |||
// [1] https://docs.docker.com/registry/spec/api/#existing-layers | |||
func (pbs *pullthroughBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, req *http.Request, dgst digest.Digest) error { | |||
store, ok := pbs.digestToStore[dgst.String()] | |||
if !ok { | |||
if _, err := pbs.BlobStore.Stat(ctx, dgst); err == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need for the Stat
. It's the first thing the pbs.BlobStore.ServeBlob
does. Just handle the error case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@miminar I know, but we need to figure out is this blob local or not to start mirroring. I need to change mirroring code to make the changes that you suggest. I don't want to do this before the release. The changes will be very large.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@miminar yep. You're right.
if !found { | ||
err := fmt.Errorf("pullthroughBlobStore.ServeBlob: failed to retrieve remote getter from context") | ||
context.GetLogger(ctx).Error(err) | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't expose implementation details to clients (pullthroughBlobStore.ServeBlob:
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@miminar Otherwise it is very difficult to understand where the error occurs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with logging it. I'm not fine with returning it as an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just accidentally commented from my private account. Nevertheless, I completely agree with my alter-ego.
return desc, nil | ||
} | ||
|
||
// Second attemt: looking for the blob on a remote server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo
@@ -205,6 +205,13 @@ func newRepositoryWithClient( | |||
|
|||
// Manifests returns r, which implements distribution.ManifestService. | |||
func (r *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) { | |||
if r.pullthrough { | |||
ctx = WithRemoteBlobGetter(ctx, &remoteBlobGetterService{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain in a comment, why this is needed here?
@@ -243,6 +250,13 @@ func (r *repository) Blobs(ctx context.Context) distribution.BlobStore { | |||
repo := repository(*r) | |||
repo.ctx = ctx | |||
|
|||
if r.pullthrough { | |||
ctx = WithRemoteBlobGetter(ctx, &remoteBlobGetterService{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And again here.
07c126e
to
9874c50
Compare
@miminar all your comments addressed. |
if !found { | ||
err := fmt.Errorf("pullthroughBlobStore.ServeBlob: failed to retrieve remote getter from context") | ||
context.GetLogger(ctx).Error(err) | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this return ErrBlobUnknown
as well?
if r.pullthrough { | ||
// Add to the context the BlobGetterService that provide access to remote servers. | ||
// It will be used to validate manifest blobs. It only makes sense | ||
// if the pullthrough is enabled. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mention that it needs to be instantiated here in order to share the cache among different stat calls made on manifest's dependencies.
|
||
if r.pullthrough { | ||
// Add to the context the BlobGetterService that provide access to remote servers. | ||
// It will be used in pullthroughBlobStore. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mention that it needs to be instantiated here in order to share the cache for multiple stat calls made in descendant blob stores.
9874c50
to
eed05d4
Compare
return desc, err | ||
// only non-empty layers is wise to check for existence in the image stream. | ||
// schema v2 has no empty layers. | ||
if dgst.String() != digestSHA256GzippedEmptyTar.String() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about digest.DigestSha256EmptyTar?
if dgst.String() != digestSHA256GzippedEmptyTar.String() { | ||
// ensure it's referenced inside of corresponding image stream | ||
if !imageStreamHasBlob(repo, dgst) { | ||
return distribution.Descriptor{}, distribution.ErrBlobUnknown |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to fall-back to remote stat.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider:
- empty image stream
ns/foo
- remote manifest with layers
{A}
tagged asdocker.io/foo:v1
- remote manifest with layers
{B}
tagged asdocker.io/foo:v2
and following scenario:
oc import-image --from=docker.io/foo:v1 foo:v1 --confirm
docker pull docker.io/foo:v2
docker tag docker.io/foo:v2 "${integrated_registry_url}/ns/foo:v2"
docker push "${integrated_registry_url}/ns/foo:v2"
This will fail because HEAD
on B
in ns/foo
will succeed thanks to pullthrough. However, manifest verification will fail because B
is not tagged in the image stream and it's not an empty layer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@miminar It means that imageStreamHasBlob
should be called only for local blobs ?
It seems you have me confused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@legionus yes, that's what I meant.
test/end-to-end/core.sh
Outdated
os::cmd::expect_success "oc project '${CLUSTER_ADMIN_CONTEXT}'" | ||
os::cmd::expect_success 'oc env -n default dc/docker-registry REGISTRY_MIDDLEWARE_REPOSITORY_OPENSHIFT_MIRRORPULLTHROUGH=false' | ||
os::cmd::expect_success 'oc rollout status dc/docker-registry' | ||
os::log::info "Registry configured to disble mirroring" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo
test/end-to-end/core.sh
Outdated
os::cmd::expect_success 'oc rollout status dc/docker-registry' | ||
os::log::info "Registry configured to disble mirroring" | ||
|
||
os::log::info "Check the image based on a non-local base image." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/non-local/remote/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be more verbose? E.g.: Verify that an image based on a remote image can be pushed to the same image stream while pull-through enabled
test/end-to-end/core.sh
Outdated
os::log::info "Check the image based on a non-local base image." | ||
os::cmd::expect_success "oc login -u user0 -p pass" | ||
os::cmd::expect_success "oc new-project project0" | ||
os::cmd::expect_success "oc new-build -D \$'FROM busybox:glibc\nRUN echo abc >/tmp/foo'" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment that this actually creates a source repository busybox
for the source image and that the result will be pushed to the same repository.
b204966
to
ecc925b
Compare
either flake #13050 or this change is now causing that test to fail. |
ecc925b
to
e2f3983
Compare
@legionus there were multiple failures. some of them were 13047, but the networking one didn't appear to be related to that, no. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
[test] |
flake #12899 |
[test] |
1 similar comment
[test] |
@mfojtik \o/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have not addressed any of my comments on your tests. Please do so.
e2f3983
to
462bd16
Compare
@stevekuznetsov I believe that the proposed changes are wrong, but I made them. At least I know who to blame for the slow test. please review again. |
I was OK with the way you had the wait written. I said as much in the comments. It was the other comments re: go-template, etc that I want to see addressed. |
462bd16
to
82db14d
Compare
@stevekuznetsov Oh. I didn't understand you correctly. Now looks better ? |
flake #12923 re[test] |
Signed-off-by: Gladkov Alexey <agladkov@redhat.com>
82db14d
to
b4718ae
Compare
@mfojtik done |
Evaluated for origin test up to b4718ae |
continuous-integration/openshift-jenkins/test SUCCESS (https://ci.openshift.redhat.com/jenkins/job/test_pull_requests_origin_future/536/) (Base Commit: f1dd3c7) |
QA verified the fork_ami [merge] |
Evaluated for origin merge up to b4718ae |
continuous-integration/openshift-jenkins/merge SUCCESS (https://ci.openshift.redhat.com/jenkins/job/test_pull_requests_origin_future/571/) (Base Commit: 5357357) (Image: devenv-rhel7_5985) |
Problem
We pass all requests (including
HEAD
) to the remote service if the pullthrough is enabled. On the other hand when docker client pushes the manifest we check the presence of all these layers locally. The client checks a blob existance byHEAD
request before sending it to the server.If client image is based on the imported image (but not present in local registry) dockerregistry will say that it has all the layers from the base image. In this case docker client never send them to server, but manifest verification requires them locally. It means that the verification will always fail for remote layers.
Solution
Manifest verification must to take into account the possibility that the layers may not be local and check them on remote registry server before before give up.
We can't use
pullthroughBlobStore
because verification happens inManifestService
. So we need to move common code that gets the blobs from the remote server toBlobGetterService
and use it for pullthrough and for verification.@miminar PTAL