Beispiel #1
0
def add_bundles():
    """
    Submit a request to add operator bundles to an index image.

    :rtype: flask.Response
    :raise ValidationError: if required parameters are not supplied
    """
    payload = flask.request.get_json()
    if not isinstance(payload, dict):
        raise ValidationError('The input data must be a JSON object')

    request = RequestAdd.from_json(payload)
    db.session.add(request)
    db.session.commit()
    messaging.send_message_for_state_change(request, new_batch_msg=True)

    overwrite_from_index = _should_force_overwrite() or payload.get(
        'overwrite_from_index')
    celery_queue = _get_user_queue(serial=overwrite_from_index)
    args = _get_add_args(payload, request.id, overwrite_from_index,
                         celery_queue)
    safe_args = _get_safe_args(args, payload)
    error_callback = failed_request_callback.s(request.id)

    try:
        handle_add_request.apply_async(args=args,
                                       link_error=error_callback,
                                       argsrepr=repr(safe_args),
                                       queue=celery_queue)
    except kombu.exceptions.OperationalError:
        handle_broker_error(request)

    flask.current_app.logger.debug('Successfully scheduled request %d',
                                   request.id)
    return flask.jsonify(request.to_json()), 201
Beispiel #2
0
def test_create_empty_index_image_request(app, auth_env, client, db):
    total_requests = 20
    empty_index_revision = 'e16a8cd2e028'
    # flask_login.current_user is used in RequestAdd.from_json and RequestRm.from_json,
    # which requires a request context
    with app.test_request_context(environ_base=auth_env):
        # Generate some data to verify migration
        data = {
            'from_index': 'quay.io/namespace/index_image:latest',
            'binary_image': 'quay.io/namespace/binary_image:latest',
        }
        request = RequestCreateEmptyIndex.from_json(data)
        db.session.add(request)

        for i in range(total_requests):
            request_class = random.choice(
                (RequestAdd, RequestRm, RequestCreateEmptyIndex))
            if request_class == RequestAdd:
                data = {
                    'binary_image': 'quay.io/namespace/binary_image:latest',
                    'bundles': [f'quay.io/namespace/bundle:{i}'],
                    'from_index': f'quay.io/namespace/repo:{i}',
                }
                request = RequestAdd.from_json(data)
            elif request_class == RequestRm:
                data = {
                    'binary_image': 'quay.io/namespace/binary_image:latest',
                    'operators': [f'operator-{i}'],
                    'from_index': f'quay.io/namespace/repo:{i}',
                }
                request = RequestRm.from_json(data)
            elif request_class == RequestCreateEmptyIndex:
                data = {
                    'from_index': f'quay.io/namespace/index_image:{i}',
                    'binary_image': 'quay.io/namespace/binary_image:latest',
                }
                request = RequestCreateEmptyIndex.from_json(data)

            if i % 5 == 0:
                # Simulate failed request
                request.add_state('failed', 'Failed due to an unknown error')
            db.session.add(request)
        db.session.commit()

    expected_rv_json = client.get(
        f'/api/v1/builds?per_page={total_requests}&verbose=true').json
    flask_migrate.downgrade(revision=empty_index_revision)
    flask_migrate.upgrade()

    actual_rv_json = client.get(
        f'/api/v1/builds?per_page={total_requests}&verbose=true').json
    assert expected_rv_json == actual_rv_json
Beispiel #3
0
def add_bundles():
    """
    Submit a request to add operator bundles to an index image.

    :rtype: flask.Response
    :raise ValidationError: if required parameters are not supplied
    """
    payload = flask.request.get_json()
    if not isinstance(payload, dict):
        raise ValidationError('The input data must be a JSON object')

    request = RequestAdd.from_json(payload)
    db.session.add(request)
    db.session.commit()
    messaging.send_message_for_state_change(request, new_batch_msg=True)

    overwrite_from_index = _should_force_overwrite() or payload.get(
        'overwrite_from_index')
    celery_queue = _get_user_queue(serial=overwrite_from_index)
    args = [
        payload['bundles'],
        payload['binary_image'],
        request.id,
        payload.get('from_index'),
        payload.get('add_arches'),
        payload.get('cnr_token'),
        payload.get('organization'),
        payload.get('force_backport'),
        overwrite_from_index,
        payload.get('overwrite_from_index_token'),
        flask.current_app.config['IIB_GREENWAVE_CONFIG'].get(celery_queue),
    ]
    safe_args = copy.copy(args)
    if payload.get('cnr_token'):
        safe_args[safe_args.index(payload['cnr_token'])] = '*****'
    if payload.get('overwrite_from_index_token'):
        safe_args[safe_args.index(
            payload['overwrite_from_index_token'])] = '*****'

    error_callback = failed_request_callback.s(request.id)

    try:
        handle_add_request.apply_async(args=args,
                                       link_error=error_callback,
                                       argsrepr=repr(safe_args),
                                       queue=celery_queue)
    except kombu.exceptions.OperationalError:
        handle_broker_error(request)

    flask.current_app.logger.debug('Successfully scheduled request %d',
                                   request.id)
    return flask.jsonify(request.to_json()), 201
Beispiel #4
0
def test_abort_when_downgrading_from_regenerate_bundle_request(
        app, auth_env, client, db):
    """Verify downgrade is prevented if "regenerate-bundle" requests exist."""
    total_requests = 20
    # flask_login.current_user is used in Request*.from_json which requires a request context
    with app.test_request_context(environ_base=auth_env):
        # Always add a RequestRegenerateBundle to ensure sufficient test data is available
        data = {'from_bundle_image': 'quay.io/namespace/bundle-image:latest'}
        request = RequestRegenerateBundle.from_json(data)
        db.session.add(request)

        # One request was already added, let's add the remaining ones
        for i in range(total_requests - 1):
            request_class = random.choice(
                (RequestAdd, RequestRm, RequestRegenerateBundle))
            if request_class == RequestAdd:
                data = {
                    'binary_image': 'quay.io/namespace/binary_image:latest',
                    'bundles': [f'quay.io/namespace/bundle:{i}'],
                    'from_index': f'quay.io/namespace/repo:{i}',
                }
                request = RequestAdd.from_json(data)

            elif request_class == RequestRm:
                data = {
                    'binary_image': 'quay.io/namespace/binary_image:latest',
                    'operators': [f'operator-{i}'],
                    'from_index': f'quay.io/namespace/repo:{i}',
                }
                request = RequestRm.from_json(data)
            else:
                data = {
                    'from_bundle_image':
                    'quay.io/namespace/bundle-image:latest'
                }
                request = RequestRegenerateBundle.from_json(data)
                db.session.add(request)
            db.session.add(request)

        db.session.commit()

    # flask_migrate raises a SystemExit exception regardless of what's raised from the
    # downgrade function. This exception doesn't hold a reference to the RuntimeError
    # we expect from the downgrade function in the migration script. The best we can
    # do is catch the SystemExit exception.
    with pytest.raises(SystemExit):
        flask_migrate.downgrade(revision=INITIAL_DB_REVISION)
Beispiel #5
0
def test_get_builds(app, auth_env, client, db):
    total_requests = 50
    # flask_login.current_user is used in RequestAdd.from_json, which requires a request context
    with app.test_request_context(environ_base=auth_env):
        for i in range(total_requests):
            data = {
                'binary_image': 'quay.io/namespace/binary_image:latest',
                'bundles': [f'quay.io/namespace/bundle:{i}'],
                'from_index': f'quay.io/namespace/repo:{i}',
            }
            request = RequestAdd.from_json(data)
            if i % 5 == 0:
                request.add_state('failed', 'Failed due to an unknown error')
            db.session.add(request)
        db.session.commit()

    rv_json = client.get('/api/v1/builds?page=2').json
    # Verify the order_by is correct
    assert rv_json['items'][0][
        'id'] == total_requests - app.config['IIB_MAX_PER_PAGE']
    assert len(rv_json['items']) == app.config['IIB_MAX_PER_PAGE']
    # This key is only present the verbose=true
    assert 'state_history' not in rv_json['items'][0]
    assert rv_json['meta']['page'] == 2
    assert rv_json['meta']['pages'] == 3
    assert rv_json['meta']['per_page'] == app.config['IIB_MAX_PER_PAGE']
    assert rv_json['meta']['total'] == total_requests

    rv_json = client.get('/api/v1/builds?state=failed&per_page=5').json
    total_failed_requests = total_requests // 5
    assert len(rv_json['items']) == 5
    assert 'state=failed' in rv_json['meta']['next']
    assert rv_json['meta']['page'] == 1
    assert rv_json['meta']['pages'] == 2
    assert rv_json['meta']['per_page'] == 5
    assert rv_json['meta']['total'] == total_failed_requests

    rv_json = client.get('/api/v1/builds?batch=3').json
    assert len(rv_json['items']) == 1
    assert 'batch=3' in rv_json['meta']['first']
    assert rv_json['meta']['total'] == 1

    rv_json = client.get('/api/v1/builds?verbose=true&per_page=1').json
    # This key is only present the verbose=true
    assert 'state_history' in rv_json['items'][0]
Beispiel #6
0
def test_migrate_to_polymorphic_requests(app, auth_env, client, db):
    total_requests = 20
    # flask_login.current_user is used in RequestAdd.from_json and RequestRm.from_json,
    # which requires a request context
    with app.test_request_context(environ_base=auth_env):
        # Generate some data to verify migration
        for i in range(total_requests):
            if random.choice((True, False)):
                data = {
                    'binary_image': 'quay.io/namespace/binary_image:latest',
                    'bundles': [f'quay.io/namespace/bundle:{i}'],
                    'from_index': f'quay.io/namespace/repo:{i}',
                }
                request = RequestAdd.from_json(data)
            else:
                data = {
                    'binary_image': 'quay.io/namespace/binary_image:latest',
                    'operators': [f'operator-{i}'],
                    'from_index': f'quay.io/namespace/repo:{i}',
                }
                request = RequestRm.from_json(data)
            if i % 5 == 0:
                # Simulate failed request
                request.add_state('failed', 'Failed due to an unknown error')
            db.session.add(request)
        db.session.commit()

    expected_rv_json = client.get(
        f'/api/v1/builds?per_page={total_requests}&verbose=true').json

    flask_migrate.downgrade(revision=INITIAL_DB_REVISION)
    flask_migrate.upgrade()

    actual_rv_json = client.get(
        f'/api/v1/builds?per_page={total_requests}&verbose=true').json
    assert expected_rv_json == actual_rv_json
Beispiel #7
0
def add_rm_batch():
    """
    Submit a batch of requests to add or remove operators from an index image.

    Note: Any duplicate bundle will be removed from payload when adding operators.

    :rtype: flask.Response
    :raise ValidationError: if required parameters are not supplied
    """
    payload = flask.request.get_json()
    Batch.validate_batch_request_params(payload)

    batch = Batch(annotations=payload.get('annotations'))
    db.session.add(batch)

    requests = []
    # Iterate through all the build requests and verify that the requests are valid before
    # committing them and scheduling the tasks
    for build_request in payload['build_requests']:
        try:
            if build_request.get('operators'):
                # Check for the validity of a RM request
                request = RequestRm.from_json(build_request, batch)
            elif build_request.get('bundles'):
                build_request_uniq = copy.deepcopy(build_request)
                build_request_uniq['bundles'] = _get_unique_bundles(build_request_uniq['bundles'])
                # Check for the validity of an Add request
                request = RequestAdd.from_json(build_request_uniq, batch)
            else:
                raise ValidationError('Build request is not a valid Add/Rm request.')
        except ValidationError as e:
            raise ValidationError(
                f'{str(e).rstrip(".")}. This occurred on the build request in '
                f'index {payload["build_requests"].index(build_request)}.'
            )
        db.session.add(request)
        requests.append(request)

    db.session.commit()
    messaging.send_messages_for_new_batch_of_requests(requests)

    request_jsons = []
    # This list will be used for the log message below and avoids the need of having to iterate
    # through the list of requests another time
    processed_request_ids = []
    for build_request, request in zip(payload['build_requests'], requests):
        request_jsons.append(request.to_json())

        overwrite_from_index = build_request.get('overwrite_from_index')
        celery_queue = _get_user_queue(serial=overwrite_from_index)
        if isinstance(request, RequestAdd):
            args = _get_add_args(build_request, request, overwrite_from_index, celery_queue)
        elif isinstance(request, RequestRm):
            args = _get_rm_args(build_request, request, overwrite_from_index)

        safe_args = _get_safe_args(args, build_request)

        error_callback = failed_request_callback.s(request.id)
        try:
            if isinstance(request, RequestAdd):
                handle_add_request.apply_async(
                    args=args,
                    link_error=error_callback,
                    argsrepr=repr(safe_args),
                    queue=celery_queue,
                )
            else:
                handle_rm_request.apply_async(
                    args=args,
                    link_error=error_callback,
                    argsrepr=repr(safe_args),
                    queue=celery_queue,
                )
        except kombu.exceptions.OperationalError:
            unprocessed_requests = [r for r in requests if str(r.id) not in processed_request_ids]
            handle_broker_batch_error(unprocessed_requests)

        processed_request_ids.append(str(request.id))

    flask.current_app.logger.debug(
        'Successfully scheduled the batch %d with requests: %s',
        batch.id,
        ', '.join(processed_request_ids),
    )
    return flask.jsonify(request_jsons), 201
Beispiel #8
0
def test_get_build(app, auth_env, client, db):
    # flask_login.current_user is used in RequestAdd.from_json, which requires a request context
    with app.test_request_context(environ_base=auth_env):
        data = {
            'binary_image': 'quay.io/namespace/binary_image:latest',
            'bundles': [f'quay.io/namespace/bundle:1.0-3'],
            'from_index': f'quay.io/namespace/repo:latest',
        }
        request = RequestAdd.from_json(data)
        request.binary_image_resolved = Image.get_or_create(
            'quay.io/namespace/binary_image@sha256:abcdef')
        request.from_index_resolved = Image.get_or_create(
            'quay.io/namespace/from_index@sha256:defghi')
        request.index_image = Image.get_or_create(
            'quay.io/namespace/index@sha256:fghijk')
        request.add_architecture('amd64')
        request.add_architecture('s390x')
        request.add_state('complete', 'Completed successfully')
        db.session.add(request)
        db.session.commit()

    rv = client.get('/api/v1/builds/1').json
    for state in rv['state_history']:
        # Set this to a stable timestamp so the tests are dependent on it
        state['updated'] = '2020-02-12T17:03:00Z'
    rv['updated'] = '2020-02-12T17:03:00Z'

    expected = {
        'arches': ['amd64', 's390x'],
        'batch':
        1,
        'batch_annotations':
        None,
        'binary_image':
        'quay.io/namespace/binary_image:latest',
        'binary_image_resolved':
        'quay.io/namespace/binary_image@sha256:abcdef',
        'bundle_mapping': {},
        'bundles': ['quay.io/namespace/bundle:1.0-3'],
        'from_index':
        'quay.io/namespace/repo:latest',
        'from_index_resolved':
        'quay.io/namespace/from_index@sha256:defghi',
        'id':
        1,
        'index_image':
        'quay.io/namespace/index@sha256:fghijk',
        'organization':
        None,
        'removed_operators': [],
        'request_type':
        'add',
        'state':
        'complete',
        'state_history': [
            {
                'state': 'complete',
                'state_reason': 'Completed successfully',
                'updated': '2020-02-12T17:03:00Z',
            },
            {
                'state': 'in_progress',
                'state_reason': 'The request was initiated',
                'updated': '2020-02-12T17:03:00Z',
            },
        ],
        'state_reason':
        'Completed successfully',
        'updated':
        '2020-02-12T17:03:00Z',
        'user':
        '******',
    }
    assert rv == expected