def test_expectations(self, group, has_v2list, expect_success): def get_manifest(request): media_types = request.headers.get('Accept', '').split(',') content_type = media_types[0] v2_content_type = 'application/vnd.docker.distribution.manifest.list.v2+json' if (not has_v2list) and v2_content_type in media_types: content_type = 'application/vnd.docker.distribution.manifest.v2+json' return (200, {'Content-Type': content_type}, '{}') url = re.compile(r'.*//crane.example.com/v2/.*/manifests/.*') responses.add_callback(responses.GET, url, callback=get_manifest) digests = {'digest': None} if group else {} workflow = self.workflow(postbuild_results={ PLUGIN_GROUP_MANIFESTS_KEY: digests, }) workflow.postbuild_plugins_conf = [{'name': 'pulp_sync'}] tasker = MockerTasker() plugin = PulpPullPlugin(tasker, workflow, timeout=0.2, retry_delay=0.1, expect_v2schema2=True) if expect_success: plugin.run() else: with pytest.raises(CraneTimeoutError): plugin.run()
def test_pull_push_vs_sync(self, push, sync): workflow = self.workflow(push=push, sync=sync) tasker = MockerTasker() test_id = 'sha256:(new)' getter = self.custom_get_v1 if sync: (flexmock(requests.Session) .should_receive('get') .replace_with(getter)) else: (flexmock(requests.Session) .should_receive('get') .never()) (flexmock(tasker) .should_call('pull_image') .with_args(self.EXPECTED_IMAGE, insecure=False) .and_return(self.EXPECTED_PULLSPEC) .ordered()) (flexmock(tasker) .should_receive('inspect_image') .with_args(self.EXPECTED_PULLSPEC) .and_return({'Id': test_id})) workflow.postbuild_plugins_conf = [] plugin = PulpPullPlugin(tasker, workflow) plugin.run() assert workflow.builder.image_id == test_id assert len(tasker.pulled_images) == 1
def test_unexpected_response(self): workflow = self.workflow() tasker = MockerTasker() unauthorized = requests.Response() flexmock(unauthorized, status_code=requests.codes.unauthorized) flexmock(requests.Session).should_receive('get').and_return(unauthorized) workflow.postbuild_plugins_conf = [] plugin = PulpPullPlugin(tasker, workflow) with pytest.raises(requests.exceptions.HTTPError): plugin.run()
def test_pull_first_time(self): workflow = self.workflow() tasker = MockerTasker() expected_pullspec = '%s/%s' % (self.CRANE_URI, self.TEST_UNIQUE_IMAGE) test_id = 'sha256:(new)' (flexmock(tasker).should_call('pull_image').and_return( expected_pullspec).once().ordered()) (flexmock(tasker).should_receive('inspect_image').with_args( expected_pullspec).and_return({ 'Id': test_id }).once()) plugin = PulpPullPlugin(tasker, workflow) # Plugin return value is the new ID assert plugin.run() == test_id assert len(tasker.pulled_images) == 1 pulled = tasker.pulled_images[0].to_str() assert pulled == expected_pullspec # Image ID is updated in workflow assert workflow.builder.image_id == test_id
def test_pull_timeout(self): workflow = self.workflow() tasker = MockerTasker() (flexmock(tasker).should_call('pull_image').and_return( self.EXPECTED_PULLSPEC).times(3)) (flexmock(tasker).should_receive('inspect_image').with_args( self.EXPECTED_PULLSPEC).and_raise( NotFound('message', flexmock(content=None))).times(3)) plugin = PulpPullPlugin(tasker, workflow, timeout=1, retry_delay=0.6) # Should raise a timeout exception with pytest.raises(CraneTimeoutError): plugin.run()
def test_pull_retry(self): workflow = self.workflow() tasker = MockerTasker() expected_pullspec = '%s/%s' % (self.CRANE_URI, self.TEST_UNIQUE_IMAGE) test_id = 'sha256:(new)' (flexmock(tasker).should_call('pull_image').and_return( expected_pullspec).times(3)) (flexmock(tasker).should_receive('inspect_image').with_args( expected_pullspec).and_raise( NotFound('message', flexmock(content=None))).and_raise( NotFound('message', flexmock(content=None))).and_return({ 'Id': test_id }).times(3)) plugin = PulpPullPlugin(tasker, workflow, timeout=1, retry_delay=0.6) # Plugin return value is the new ID assert plugin.run() == test_id assert len(tasker.pulled_images) == 3 for image in tasker.pulled_images: pulled = image.to_str() assert pulled == expected_pullspec # Image ID is updated in workflow assert workflow.builder.image_id == test_id
def test_expect_v2schema2list_only(self, caplog, expect_v2, platforms, platform_descriptors, manifest_list_only): def get_manifest(request): media_types = request.headers.get('Accept', '').split(',') content_type = media_types[0] return (200, {'Content-Type': content_type}, '{}') url = re.compile(r'.*//crane.example.com/v2/.*/manifests/.*') responses.add_callback(responses.GET, url, callback=get_manifest) digests = {'digest': None} workflow = self.workflow( platform_descriptors=platform_descriptors, postbuild_results={PLUGIN_GROUP_MANIFESTS_KEY: digests}, prebuild_results={PLUGIN_CHECK_AND_SET_PLATFORMS_KEY: set(platforms)}, ) workflow.postbuild_plugins_conf = [{'name': 'pulp_sync'}] tasker = MockerTasker() plugin = PulpPullPlugin(tasker, workflow, timeout=0.2, retry_delay=0.1, expect_v2schema2=expect_v2) media_types = plugin.run() expected_media_types = [MEDIA_TYPE_DOCKER_V2_MANIFEST_LIST] if not manifest_list_only: expected_media_types.append(MEDIA_TYPE_DOCKER_V2_SCHEMA1) expected_media_types.append(MEDIA_TYPE_DOCKER_V2_SCHEMA2) else: assert "Only V2 schema 2 manifest list is expected, " in caplog.text() assert set(media_types) == set(expected_media_types)
def test_pull_first_time(self, insecure): workflow = self.workflow() tasker = MockerTasker() test_id = 'sha256:(new)' (flexmock(tasker).should_call('pull_image').with_args( self.EXPECTED_IMAGE, insecure=insecure).and_return( self.EXPECTED_PULLSPEC).once().ordered()) (flexmock(tasker).should_receive('inspect_image').with_args( self.EXPECTED_PULLSPEC).and_return({ 'Id': test_id }).once()) plugin = PulpPullPlugin(tasker, workflow, insecure=insecure) # Plugin return value is the new ID assert plugin.run() == test_id assert len(tasker.pulled_images) == 1 pulled = tasker.pulled_images[0].to_str() assert pulled == self.EXPECTED_PULLSPEC # Image ID is updated in workflow assert workflow.builder.image_id == test_id
def test_expect_v2schema2list_only(self, caplog, expect_v2, platforms, platform_descriptors, manifest_list_only): def get_manifest(request): media_types = request.headers.get('Accept', '').split(',') content_type = media_types[0] return (200, {'Content-Type': content_type}, '{}') url = re.compile(r'.*//crane.example.com/v2/.*/manifests/.*') responses.add_callback(responses.GET, url, callback=get_manifest) digests = {'digest': None} workflow = self.workflow( platform_descriptors=platform_descriptors, postbuild_results={PLUGIN_GROUP_MANIFESTS_KEY: digests}, prebuild_results={PLUGIN_CHECK_AND_SET_PLATFORMS_KEY: set(platforms)}, ) workflow.postbuild_plugins_conf = [{'name': 'pulp_sync'}] tasker = MockerTasker() plugin = PulpPullPlugin(tasker, workflow, timeout=0.2, retry_delay=0.1, expect_v2schema2=expect_v2) media_types = plugin.run() expected_media_types = [MEDIA_TYPE_DOCKER_V2_MANIFEST_LIST] if not manifest_list_only: expected_media_types.append(MEDIA_TYPE_DOCKER_V2_SCHEMA1) expected_media_types.append(MEDIA_TYPE_DOCKER_V2_SCHEMA2) else: assert "Only V2 schema 2 manifest list is expected, " in caplog.text assert set(media_types) == set(expected_media_types)
def test_pull_timeout(self): workflow = self.workflow() tasker = MockerTasker() expected_pullspec = '%s/%s' % (self.CRANE_URI, self.TEST_UNIQUE_IMAGE) (flexmock(tasker).should_call('pull_image').and_return( expected_pullspec).times(3)) (flexmock(tasker).should_receive('inspect_image').with_args( expected_pullspec).and_raise( NotFound('message', flexmock(content=None))).times(3)) plugin = PulpPullPlugin(tasker, workflow, timeout=1, retry_delay=0.6) # Should raise a timeout exception with pytest.raises(CraneTimeoutError): plugin.run()
def test_forbidden_response(self): workflow = self.workflow() tasker = MockerTasker() forbidden = requests.Response() flexmock(forbidden, status_code=requests.codes.forbidden, request=requests.Request(url='https://crane.example.com')) expectation = flexmock(requests.Session).should_receive('get') expectation.and_return(forbidden) expectation.and_return(self.config_response_config_v1) expectation.and_return(self.config_response_config_v2) expectation.and_return(self.config_response_config_v2_list) # No OCI support in Pulp at the moment, will return a v1 response expectation.and_return(self.config_response_config_v1) expectation.and_return(self.config_response_config_v1) workflow.postbuild_plugins_conf = [] plugin = PulpPullPlugin(tasker, workflow, timeout=0.5, retry_delay=0.26, expect_v2schema2=True) plugin.run()
def test_forbidden_response(self): workflow = self.workflow() tasker = MockerTasker() forbidden = requests.Response() flexmock(forbidden, status_code=requests.codes.forbidden, request=requests.Request(url='https://crane.example.com')) expectation = flexmock(requests.Session).should_receive('get') expectation.and_return(forbidden) expectation.and_return(self.config_response_config_v1) expectation.and_return(self.config_response_config_v2) expectation.and_return(self.config_response_config_v2_list) # No OCI support in Pulp at the moment, will return a v1 response expectation.and_return(self.config_response_config_v1) expectation.and_return(self.config_response_config_v1) workflow.postbuild_plugins_conf = [] plugin = PulpPullPlugin(tasker, workflow, timeout=0.1, retry_delay=0.06, expect_v2schema2=True) plugin.run()
def test_plugin_type(self): # arrangement versions < 4 assert issubclass(PulpPullPlugin, PostBuildPlugin) # arrangement version >= 4 assert issubclass(PulpPullPlugin, ExitPlugin) # Verify the plugin does nothing when running as an exit # plugin for an already-failed build workflow = self.workflow(build_process_failed=True) tasker = MockerTasker() workflow.postbuild_plugins_conf = [] flexmock(requests.Session).should_receive('get').never() flexmock(tasker).should_receive('pull_image').never() flexmock(tasker).should_receive('inspect_image').never() plugin = PulpPullPlugin(tasker, workflow) media_types = plugin.run() assert len(media_types) == 0
def test_expect_v2_expect_v2list(self, caplog, has_v2, has_v2_list, expect_v2, platforms, manifest_list_only, has_v1, expect_success, manifest_list_warn, manifest2_warn): def get_manifest(request): media_types = request.headers.get('Accept', '').split(',') content_type = media_types[0] return (200, {'Content-Type': content_type}, '{}') url = re.compile(r'.*//crane.example.com/v2/.*/manifests/.*') responses.add_callback(responses.GET, url, callback=get_manifest) digests = {'digest': None} workflow = self.workflow( platform_descriptors=True, postbuild_results={PLUGIN_GROUP_MANIFESTS_KEY: digests}, prebuild_results={PLUGIN_CHECK_AND_SET_PLATFORMS_KEY: set(platforms)}, expectv2schema2=expect_v2, ) workflow.postbuild_plugins_conf = [{'name': 'pulp_sync'}] not_found = requests.Response() flexmock(not_found, status_code=requests.codes.not_found) expectation = flexmock(requests.Session).should_receive('get') # 1st retry don't find anything for _ in range(5): expectation = expectation.and_return(not_found) # (for v1, v2, list.v2, oci, and oci.index media types) before get_manifest_digests # v1 if has_v1: expectation.and_return(self.config_response_config_v1) else: expectation.and_return(not_found) # v2 if has_v2: expectation.and_return(self.config_response_config_v2) elif has_v1: expectation.and_return(self.config_response_config_v1) else: expectation.and_return(not_found) # v2_list if has_v2_list: expectation.and_return(self.config_response_config_v2_list) else: expectation.and_return(not_found) # oci if has_v1: expectation.and_return(self.config_response_config_v1) expectation.and_return(self.config_response_config_v1) else: expectation.and_return(not_found) expectation.and_return(not_found) tasker = MockerTasker() plugin = PulpPullPlugin(tasker, workflow, timeout=0.2, retry_delay=0.2, expect_v2schema2=expect_v2) media_types = None if manifest_list_only: if expect_success: media_types = plugin.run() else: with pytest.raises(Exception): plugin.run() if manifest_list_warn: assert "Expected schema 2 manifest list" in caplog.text else: if expect_success: media_types = plugin.run() else: with pytest.raises(Exception): plugin.run() if manifest_list_warn: assert "Expected schema 2 manifest list" in caplog.text elif manifest2_warn: assert "Expected schema 2 manifest" in caplog.text if media_types: expected_media_types = [MEDIA_TYPE_DOCKER_V2_MANIFEST_LIST] if not manifest_list_only: expected_media_types.append(MEDIA_TYPE_DOCKER_V2_SCHEMA1) expected_media_types.append(MEDIA_TYPE_DOCKER_V2_SCHEMA2) assert "V2 schema 2 digest found, leaving image ID unchanged" in caplog.text else: assert "Only V2 schema 2 manifest list is expected, " in caplog.text assert set(media_types) == set(expected_media_types)
def test_pull_retry(self, expect_v2schema2, v2, timeout, retry_delay, failures, expect_success, reactor_config): workflow = self.workflow(expect_v2schema2) tasker = MockerTasker() if v2: test_id = 'sha256:(old)' else: # Image ID is updated in workflow test_id = 'sha256:(new)' not_found = requests.Response() flexmock(not_found, status_code=requests.codes.not_found) expectation = flexmock(requests.Session).should_receive('get') # If pulp is returning a 404 for a manifest URL, we will get 5 requests # (for v1, v2, list.v2, oci, and oci.index media types) before get_manifest_digests # gives up, so we need to return 5 404's to equal one "failure". for _ in range(5 * failures): expectation = expectation.and_return(not_found) expectation.and_return(self.config_response_config_v1) if v2: expectation.and_return(self.config_response_config_v2) else: expectation.and_return(self.config_response_config_v1) expectation.and_return(self.config_response_config_v2_list) # No OCI support in Pulp at the moment, will return a v1 response expectation.and_return(self.config_response_config_v1) # A special case for retries - schema 2 manifest digest is expected, # but its never being sent - the test should fail on timeout if not v2 and expect_v2schema2: expect_success = False expectation = flexmock(tasker).should_call('pull_image') if v2: expectation.never() elif expect_success: expectation.and_return(self.EXPECTED_PULLSPEC).once() expectation = flexmock(tasker).should_receive('inspect_image') if v2: expectation.never() elif expect_success: (expectation .with_args(self.EXPECTED_PULLSPEC) .and_return({'Id': test_id}) .once()) workflow.postbuild_plugins_conf = [] if reactor_config: plugin = PulpPullPlugin(tasker, workflow, timeout=timeout, retry_delay=retry_delay) else: plugin = PulpPullPlugin(tasker, workflow, timeout=timeout, retry_delay=retry_delay, expect_v2schema2=expect_v2schema2) if not expect_success: with pytest.raises(Exception): plugin.run() return plugin.run() assert len(tasker.pulled_images) == 0 if v2 else 1 if not v2: img = tasker.pulled_images[0].to_str() assert img == self.EXPECTED_PULLSPEC assert workflow.builder.image_id == test_id
def test_pull_first_time(self, no_headers, broken_response, insecure, schema_version, pulp_plugin, expected_version): workflow = self.workflow() tasker = MockerTasker() test_id = 'sha256:(new)' if schema_version == 'v2': # for v2, we just return pre-existing ID test_id = 'sha256:(old)' if schema_version == 'v1': getter = self.custom_get_v1 elif schema_version == 'list.v2': getter = self.custom_get_v2_list elif no_headers: if broken_response: getter = self.custom_get_v2_broken else: getter = self.custom_get_v2_no_headers else: getter = self.custom_get_v2 (flexmock(requests.Session) .should_receive('get') .replace_with(getter)) if schema_version in ['v1', 'list.v2'] or broken_response: (flexmock(tasker) .should_call('pull_image') .with_args(self.EXPECTED_IMAGE, insecure=insecure) .and_return(self.EXPECTED_PULLSPEC) .once() .ordered()) (flexmock(tasker) .should_receive('inspect_image') .with_args(self.EXPECTED_PULLSPEC) .and_return({'Id': test_id}) .once()) else: (flexmock(tasker) .should_call('pull_image') .never()) (flexmock(tasker) .should_call('inspect_image') .never()) # Convert pulp_plugin into a JSON string and back into an object # to make really sure we get a different string object back. workflow.postbuild_plugins_conf = json.loads(json.dumps(pulp_plugin)) # Set the timeout parameters so that we retry exactly once, but quickly. # With the get_manifest_digests() API, the 'broken_response' case isn't # distinguishable from no manifest yet, so we retry until timout and then # fall through to pulp_pull. plugin = PulpPullPlugin(tasker, workflow, insecure=insecure, timeout=0.1, retry_delay=0.25) version = plugin.run() if not broken_response: assert version == expected_version if schema_version == 'v1': assert len(tasker.pulled_images) == 1 pulled = tasker.pulled_images[0].to_str() assert pulled == self.EXPECTED_PULLSPEC # Image ID is updated in workflow assert workflow.builder.image_id == test_id
def test_expect_v2_expect_v2list(self, caplog, has_v2, has_v2_list, expect_v2, platforms, manifest_list_only, has_v1, expect_success, manifest_list_warn, manifest2_warn): def get_manifest(request): media_types = request.headers.get('Accept', '').split(',') content_type = media_types[0] return (200, {'Content-Type': content_type}, '{}') url = re.compile(r'.*//crane.example.com/v2/.*/manifests/.*') responses.add_callback(responses.GET, url, callback=get_manifest) digests = {'digest': None} workflow = self.workflow( platform_descriptors=True, postbuild_results={PLUGIN_GROUP_MANIFESTS_KEY: digests}, prebuild_results={PLUGIN_CHECK_AND_SET_PLATFORMS_KEY: set(platforms)}, expectv2schema2=expect_v2, ) workflow.postbuild_plugins_conf = [{'name': 'pulp_sync'}] not_found = requests.Response() flexmock(not_found, status_code=requests.codes.not_found) expectation = flexmock(requests.Session).should_receive('get') # 1st retry don't find anything for _ in range(5): expectation = expectation.and_return(not_found) # (for v1, v2, list.v2, oci, and oci.index media types) before get_manifest_digests # v1 if has_v1: expectation.and_return(self.config_response_config_v1) else: expectation.and_return(not_found) # v2 if has_v2: expectation.and_return(self.config_response_config_v2) elif has_v1: expectation.and_return(self.config_response_config_v1) else: expectation.and_return(not_found) # v2_list if has_v2_list: expectation.and_return(self.config_response_config_v2_list) else: expectation.and_return(not_found) # oci if has_v1: expectation.and_return(self.config_response_config_v1) expectation.and_return(self.config_response_config_v1) else: expectation.and_return(not_found) expectation.and_return(not_found) tasker = MockerTasker() plugin = PulpPullPlugin(tasker, workflow, timeout=0.2, retry_delay=0.2, expect_v2schema2=expect_v2) media_types = None if manifest_list_only: if expect_success: media_types = plugin.run() else: with pytest.raises(Exception): plugin.run() if manifest_list_warn: assert "Expected schema 2 manifest list" in caplog.text() else: if expect_success: media_types = plugin.run() else: with pytest.raises(Exception): plugin.run() if manifest_list_warn: assert "Expected schema 2 manifest list" in caplog.text() elif manifest2_warn: assert "Expected schema 2 manifest" in caplog.text() if media_types: expected_media_types = [MEDIA_TYPE_DOCKER_V2_MANIFEST_LIST] if not manifest_list_only: expected_media_types.append(MEDIA_TYPE_DOCKER_V2_SCHEMA1) expected_media_types.append(MEDIA_TYPE_DOCKER_V2_SCHEMA2) assert "V2 schema 2 digest found, leaving image ID unchanged" in caplog.text() else: assert "Only V2 schema 2 manifest list is expected, " in caplog.text() assert set(media_types) == set(expected_media_types)
def test_pull_retry(self, expect_v2schema2, v2, timeout, retry_delay, failures, expect_success): workflow = self.workflow() tasker = MockerTasker() if v2: test_id = 'sha256:(old)' else: # Image ID is updated in workflow test_id = 'sha256:(new)' not_found = requests.Response() flexmock(not_found, status_code=requests.codes.not_found) expectation = flexmock(requests.Session).should_receive('get') for _ in range(failures): expectation = expectation.and_return(not_found) expectation.and_return(self.config_response_config_v1) if v2: expectation.and_return(self.config_response_config_v2) else: expectation.and_return(self.config_response_config_v1) expectation.and_return(self.config_response_config_v2_list) # A special case for retries - schema 2 manifest digest is expected, # but its never being sent - the test should fail on timeout if not v2 and expect_v2schema2: expect_success = False expectation = flexmock(tasker).should_call('pull_image') if v2: expectation.never() elif expect_success: expectation.and_return(self.EXPECTED_PULLSPEC).once() expectation = flexmock(tasker).should_receive('inspect_image') if v2: expectation.never() elif expect_success: (expectation .with_args(self.EXPECTED_PULLSPEC) .and_return({'Id': test_id}) .once()) workflow.postbuild_plugins_conf = [] plugin = PulpPullPlugin(tasker, workflow, timeout=timeout, retry_delay=retry_delay, expect_v2schema2=expect_v2schema2) if not expect_success: with pytest.raises(Exception): plugin.run() return # Plugin return value is the new ID and schema results, version = plugin.run() # Plugin return value is the new ID assert results == test_id assert len(tasker.pulled_images) == 0 if v2 else 1 if not v2: img = tasker.pulled_images[0].to_str() assert img == self.EXPECTED_PULLSPEC assert workflow.builder.image_id == test_id