Exemple #1
0
def test_inc_restore_failure_invalid_block(grpc_controller, grpc_replica1,
                                           grpc_replica2,
                                           grpc_controller_no_frontend,
                                           grpc_fixed_dir_replica1,
                                           grpc_fixed_dir_replica2,
                                           backup_targets):  # NOQA
    # This case is for vfs backup only
    for backup_target in backup_targets:
        if "vfs" in backup_target:
            break
    assert backup_target

    address = grpc_controller.address

    dev = get_dev(grpc_replica1, grpc_replica2, grpc_controller)

    zero_string = b'\x00'.decode('utf-8')

    length0 = 256
    snap0_data = random_string(length0)
    verify_data(dev, 0, snap0_data)
    verify_data(dev, BLOCK_SIZE, snap0_data)
    snap0 = cmd.snapshot_create(address)
    backup0 = create_backup(address, snap0, backup_target)["URL"]
    backup0_name = cmd.backup_inspect(address, backup0)['Name']

    # backup1: 32 random data + 32 zero data + 192 random data in 1st block
    length1 = 32
    offset1 = 32
    snap1_data = zero_string * length1
    verify_data(dev, offset1, snap1_data)
    snap1 = cmd.snapshot_create(address)
    backup1 = create_backup(address, snap1, backup_target)["URL"]

    # start dr volume (no frontend)
    dr_address = grpc_controller_no_frontend.address
    start_no_frontend_volume(grpc_controller_no_frontend,
                             grpc_fixed_dir_replica1, grpc_fixed_dir_replica2)

    cmd.backup_restore(dr_address, backup0)
    wait_for_restore_completion(dr_address, backup0)
    verify_no_frontend_data(0, snap0_data, grpc_controller_no_frontend)

    # mock inc restore error: invalid block
    delta_file = "volume-delta-" + backup0_name + ".img"
    command = ["find", VFS_DIR, "-type", "d", "-name", VOLUME_NAME]
    backup_volume_path = subprocess.check_output(command).strip()
    command = ["find", backup_volume_path, "-name", "*blk"]
    blocks = subprocess.check_output(command).split()
    assert len(blocks) != 0
    for blk in blocks:
        command = ["mv", blk, blk + ".tmp".encode('utf-8')]
        subprocess.check_output(command).strip()
    cmd.backup_restore(dr_address, backup1)
    # restore status should contain the error info
    failed_restore, finished_restore = 0, 0
    for i in range(RETRY_COUNTS):
        failed_restore, finished_restore = 0, 0
        rs = cmd.restore_status(dr_address)
        for status in rs.values():
            if status['backupURL'] != backup1:
                break
            if 'error' in status.keys():
                if status['error'] != "":
                    assert 'no such file or directory' in \
                           status['error']
                    failed_restore += 1
            if not status["isRestoring"]:
                finished_restore += 1
        if failed_restore == 2 and finished_restore == 2:
            break
        time.sleep(RETRY_INTERVAL)
    assert failed_restore == 2 and finished_restore == 2

    assert path.exists(FIXED_REPLICA_PATH1 + delta_file)
    assert path.exists(FIXED_REPLICA_PATH2 + delta_file)
    for blk in blocks:
        command = ["mv", blk + ".tmp".encode('utf-8'), blk]
        subprocess.check_output(command)

    cleanup_no_frontend_volume(grpc_controller_no_frontend,
                               grpc_fixed_dir_replica1,
                               grpc_fixed_dir_replica2)
    rm_backups(address, ENGINE_NAME, [backup0, backup1])
    cmd.sync_agent_server_reset(address)
    cmd.sync_agent_server_reset(dr_address)
    cleanup_controller(grpc_controller)
    cleanup_replica(grpc_replica1)
    cleanup_replica(grpc_replica2)
Exemple #2
0
def inc_restore_failure_cleanup_error_test(grpc_controller, grpc_replica1,
                                           grpc_replica2, grpc_dr_controller,
                                           grpc_dr_replica1, grpc_dr_replica2,
                                           backup_target):  # NOQA
    address = grpc_controller.address

    dev = get_dev(grpc_replica1, grpc_replica2, grpc_controller)

    zero_string = b'\x00'.decode('utf-8')

    length0 = 256
    snap0_data = random_string(length0)
    verify_data(dev, 0, snap0_data)
    verify_data(dev, BLOCK_SIZE, snap0_data)
    snap0 = cmd.snapshot_create(address)
    backup0 = create_backup(address, snap0, backup_target)["URL"]
    backup0_name = cmd.backup_inspect(address, backup0)['Name']

    # backup1: 32 random data + 32 zero data + 192 random data in 1st block
    length1 = 32
    offset1 = 32
    snap1_data = zero_string * length1
    verify_data(dev, offset1, snap1_data)
    snap1 = cmd.snapshot_create(address)
    backup1 = create_backup(address, snap1, backup_target)["URL"]

    # start dr volume (no frontend)
    dr_address = grpc_dr_controller.address
    start_no_frontend_volume(grpc_dr_controller, grpc_dr_replica1,
                             grpc_dr_replica2)

    cmd.backup_restore(dr_address, backup0)
    wait_for_restore_completion(dr_address, backup0)
    verify_no_frontend_data(0, snap0_data, grpc_dr_controller)

    # mock inc restore crash/error: cannot clean up the delta file path
    delta_file = "volume-delta-" + backup0_name + ".img"
    command = ["mkdir", "-p", FIXED_REPLICA_PATH1 + delta_file + "/dir"]
    subprocess.check_output(command).strip()
    command = ["mkdir", "-p", FIXED_REPLICA_PATH2 + delta_file + "/dir"]
    subprocess.check_output(command).strip()
    with pytest.raises(subprocess.CalledProcessError) as e:
        cmd.backup_restore(dr_address, backup1)
    assert "failed to clean up the existing file" in e.value.stdout
    command = ["rm", "-r", FIXED_REPLICA_PATH1 + delta_file]
    subprocess.check_output(command).strip()
    command = ["rm", "-r", FIXED_REPLICA_PATH2 + delta_file]
    subprocess.check_output(command).strip()

    # the restore status will be reverted/keep unchanged
    # if an error is triggered before the actual restore is performed
    rs = cmd.restore_status(dr_address)
    for status in rs.values():
        assert not status["isRestoring"]
        assert status['backupURL'] == backup0
        assert 'error' not in status
        assert status['progress'] == 100
        assert status['state'] == "complete"

    cleanup_no_frontend_volume(grpc_dr_controller, grpc_dr_replica1,
                               grpc_dr_replica2)
    rm_backups(address, ENGINE_NAME, [backup0, backup1])
    cmd.sync_agent_server_reset(address)
    cmd.sync_agent_server_reset(dr_address)
    cleanup_controller(grpc_controller)
    cleanup_replica(grpc_replica1)
    cleanup_replica(grpc_replica2)
Exemple #3
0
def restore_inc_test(grpc_controller, grpc_replica1, grpc_replica2,
                     grpc_dr_controller, grpc_dr_replica1, grpc_dr_replica2,
                     backup_target):  # NOQA
    address = grpc_controller.address

    dev = get_dev(grpc_replica1, grpc_replica2, grpc_controller)

    zero_string = b'\x00'.decode('utf-8')

    # backup0: 256 random data in 1st block
    length0 = 256
    snap0_data = random_string(length0)
    verify_data(dev, 0, snap0_data)
    verify_data(dev, BLOCK_SIZE, snap0_data)
    snap0 = cmd.snapshot_create(address)
    backup0 = create_backup(address, snap0, backup_target)["URL"]
    backup0_name = cmd.backup_inspect(address, backup0)['Name']

    # backup1: 32 random data + 32 zero data + 192 random data in 1st block
    length1 = 32
    offset1 = 32
    snap1_data = zero_string * length1
    verify_data(dev, offset1, snap1_data)
    snap1 = cmd.snapshot_create(address)
    backup1 = create_backup(address, snap1, backup_target)["URL"]
    backup1_name = cmd.backup_inspect(address, backup1)['Name']

    # backup2: 32 random data + 256 random data in 1st block,
    #          256 random data in 2nd block
    length2 = 256
    offset2 = 32
    snap2_data = random_string(length2)
    verify_data(dev, offset2, snap2_data)
    verify_data(dev, BLOCK_SIZE, snap2_data)
    snap2 = cmd.snapshot_create(address)
    backup2 = create_backup(address, snap2, backup_target)["URL"]
    backup2_name = cmd.backup_inspect(address, backup2)['Name']

    # backup3: 64 zero data + 192 random data in 1st block
    length3 = 64
    offset3 = 0
    verify_data(dev, offset3, zero_string * length3)
    verify_data(dev, length2, zero_string * offset2)
    verify_data(dev, BLOCK_SIZE, zero_string * length2)
    snap3 = cmd.snapshot_create(address)
    backup3 = create_backup(address, snap3, backup_target)["URL"]
    backup3_name = cmd.backup_inspect(address, backup3)['Name']

    # backup4: 256 random data in 1st block
    length4 = 256
    offset4 = 0
    snap4_data = random_string(length4)
    verify_data(dev, offset4, snap4_data)
    snap4 = cmd.snapshot_create(address)
    backup4 = create_backup(address, snap4, backup_target)["URL"]
    backup4_name = cmd.backup_inspect(address, backup4)['Name']

    # start no-frontend volume
    # start dr volume (no frontend)
    dr_address = grpc_dr_controller.address
    start_no_frontend_volume(grpc_dr_controller, grpc_dr_replica1,
                             grpc_dr_replica2)

    # mock restore crash/error:
    # By adding attribute `immutable`, Longhorn cannot create a file
    # for the restore. Then the following restore command will fail.
    command = ["chattr", "+i", FIXED_REPLICA_PATH1]
    subprocess.check_output(command).strip()
    command = ["chattr", "+i", FIXED_REPLICA_PATH2]
    subprocess.check_output(command).strip()
    with pytest.raises(subprocess.CalledProcessError) as e:
        cmd.backup_restore(dr_address, backup0)
    assert "operation not permitted" in e.value.stdout

    # the restore status will be reverted/keep unchanged
    # if an error is triggered before the actual restore is performed
    rs = cmd.restore_status(dr_address)
    for status in rs.values():
        assert not status["isRestoring"]
        assert not status['backupURL']
        assert 'error' not in status
        assert 'progress' not in status
        assert not status['state']

    command = ["chattr", "-i", FIXED_REPLICA_PATH1]
    subprocess.check_output(command).strip()
    command = ["chattr", "-i", FIXED_REPLICA_PATH2]
    subprocess.check_output(command).strip()
    cmd.sync_agent_server_reset(dr_address)

    cmd.backup_restore(dr_address, backup0)
    wait_for_restore_completion(dr_address, backup0)
    verify_no_frontend_data(0, snap0_data, grpc_dr_controller)

    data1 = \
        snap0_data[0:offset1] + snap1_data + \
        snap0_data[offset1+length1:]
    cmd.backup_restore(dr_address, backup1)
    wait_for_restore_completion(dr_address, backup1)
    verify_no_frontend_data(0, data1, grpc_dr_controller)
    delta_file1 = "volume-delta-" + backup0_name + ".img"
    assert not path.exists(FIXED_REPLICA_PATH1 + delta_file1)
    assert not path.exists(FIXED_REPLICA_PATH2 + delta_file1)
    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup1_name)

    data2 = \
        data1[0:offset2] + snap2_data + \
        zero_string * (BLOCK_SIZE - length2 - offset2) + snap2_data
    cmd.backup_restore(dr_address, backup2)
    wait_for_restore_completion(dr_address, backup2)
    verify_no_frontend_data(0, data2, grpc_dr_controller)
    delta_file2 = "volume-delta-" + backup1_name + ".img"
    assert not path.exists(FIXED_REPLICA_PATH1 + delta_file2)
    assert not path.exists(FIXED_REPLICA_PATH2 + delta_file2)
    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup2_name)

    # mock race condition: duplicate inc restore calls
    with pytest.raises(subprocess.CalledProcessError) as e:
        cmd.backup_restore(dr_address, backup2)
        wait_for_restore_completion(dr_address, backup2)
    assert "already restored backup" in e.value.stdout

    data3 = zero_string * length3 + data2[length3:length2]
    cmd.backup_restore(dr_address, backup3)
    wait_for_restore_completion(dr_address, backup3)
    verify_no_frontend_data(0, data3, grpc_dr_controller)
    delta_file3 = "volume-delta-" + backup3_name + ".img"
    assert not path.exists(FIXED_REPLICA_PATH1 + delta_file3)
    assert not path.exists(FIXED_REPLICA_PATH2 + delta_file3)
    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup3_name)

    # mock corner case: invalid last-restored backup
    rm_backups(address, ENGINE_NAME, [backup3])

    # This inc restore will fall back to full restore
    cmd.backup_restore(dr_address, backup4)
    wait_for_restore_completion(dr_address, backup4)
    verify_no_frontend_data(0, snap4_data, grpc_dr_controller)
    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup4_name)
    # check if the tmp files during this special full restore are cleaned up.
    if "vfs" in backup_target:
        command = ["find", VFS_DIR, "-type", "d", "-name", VOLUME_NAME]
        backup_volume_path = subprocess.check_output(command).strip()
        command = ["find", backup_volume_path, "-name", "*snap_tmp"]
        tmp_files = subprocess.check_output(command).split()
        assert len(tmp_files) == 0

    cleanup_no_frontend_volume(grpc_dr_controller, grpc_dr_replica1,
                               grpc_dr_replica2)

    rm_backups(address, ENGINE_NAME, [backup0, backup1, backup2, backup4])

    cmd.sync_agent_server_reset(address)
    cmd.sync_agent_server_reset(dr_address)
    cleanup_controller(grpc_controller)
    cleanup_replica(grpc_replica1)
    cleanup_replica(grpc_replica2)
Exemple #4
0
def restore_inc_test(
        grpc_engine_manager,  # NOQA
        grpc_controller,  # NOQA
        grpc_replica1,
        grpc_replica2,  # NOQA
        grpc_dr_controller,  # NOQA
        grpc_dr_replica1,
        grpc_dr_replica2,  # NOQA
        backup_target):  # NOQA
    address = grpc_controller.address

    dev = get_dev(grpc_replica1, grpc_replica2, grpc_controller)

    zero_string = b'\x00'.decode('utf-8')

    # backup0: 256 random data in 1st block
    length0 = 256
    snap0_data = random_string(length0)
    verify_data(dev, 0, snap0_data)
    verify_data(dev, BLOCK_SIZE, snap0_data)
    snap0 = cmd.snapshot_create(address)
    backup0 = create_backup(address, snap0, backup_target)["URL"]
    backup0_name = cmd.backup_inspect(address, backup0)['Name']

    # backup1: 32 random data + 32 zero data + 192 random data in 1st block
    length1 = 32
    offset1 = 32
    snap1_data = zero_string * length1
    verify_data(dev, offset1, snap1_data)
    snap1 = cmd.snapshot_create(address)
    backup1 = create_backup(address, snap1, backup_target)["URL"]
    backup1_name = cmd.backup_inspect(address, backup1)['Name']

    # backup2: 32 random data + 256 random data in 1st block,
    #          256 random data in 2nd block
    length2 = 256
    offset2 = 32
    snap2_data = random_string(length2)
    verify_data(dev, offset2, snap2_data)
    verify_data(dev, BLOCK_SIZE, snap2_data)
    snap2 = cmd.snapshot_create(address)
    backup2 = create_backup(address, snap2, backup_target)["URL"]
    backup2_name = cmd.backup_inspect(address, backup2)['Name']

    # backup3: 64 zero data + 192 random data in 1st block
    length3 = 64
    offset3 = 0
    verify_data(dev, offset3, zero_string * length3)
    verify_data(dev, length2, zero_string * offset2)
    verify_data(dev, BLOCK_SIZE, zero_string * length2)
    snap3 = cmd.snapshot_create(address)
    backup3 = create_backup(address, snap3, backup_target)["URL"]
    backup3_name = cmd.backup_inspect(address, backup3)['Name']

    # backup4: 256 random data in 1st block
    length4 = 256
    offset4 = 0
    snap4_data = random_string(length4)
    verify_data(dev, offset4, snap4_data)
    snap4 = cmd.snapshot_create(address)
    backup4 = create_backup(address, snap4, backup_target)["URL"]
    backup4_name = cmd.backup_inspect(address, backup4)['Name']

    # start no-frontend volume
    # start dr volume (no frontend)
    dr_address = grpc_dr_controller.address
    start_no_frontend_volume(grpc_engine_manager, grpc_dr_controller,
                             grpc_dr_replica1, grpc_dr_replica2)
    # mock restore crash/error:
    # By adding attribute `immutable`, Longhorn cannot create a file
    # for the restore. Then the following restore command will fail.
    command = ["chattr", "+i", FIXED_REPLICA_PATH1]
    subprocess.check_output(command).strip()
    command = ["chattr", "+i", FIXED_REPLICA_PATH2]
    subprocess.check_output(command).strip()
    with pytest.raises(subprocess.CalledProcessError) as e:
        cmd.backup_restore(dr_address, backup0)
    assert "operation not permitted" in e.value.stdout
    command = ["chattr", "-i", FIXED_REPLICA_PATH1]
    subprocess.check_output(command).strip()
    command = ["chattr", "-i", FIXED_REPLICA_PATH2]
    subprocess.check_output(command).strip()

    cmd.backup_restore(dr_address, backup0)
    wait_for_restore_completion(dr_address, backup0)
    verify_no_frontend_data(grpc_engine_manager, 0, snap0_data,
                            grpc_dr_controller)

    delta_file1 = "volume-delta-" + backup0_name + ".img"

    # mock inc restore crash/error: cannot clean up the delta file path
    command = ["mkdir", "-p", FIXED_REPLICA_PATH1 + delta_file1 + "/dir"]
    subprocess.check_output(command).strip()
    command = ["mkdir", "-p", FIXED_REPLICA_PATH2 + delta_file1 + "/dir"]
    subprocess.check_output(command).strip()
    with pytest.raises(subprocess.CalledProcessError):
        cmd.restore_inc(dr_address, backup1, backup0_name)
    command = ["rm", "-r", FIXED_REPLICA_PATH1 + delta_file1]
    subprocess.check_output(command).strip()
    command = ["rm", "-r", FIXED_REPLICA_PATH2 + delta_file1]
    subprocess.check_output(command).strip()

    if "vfs" in backup_target:
        # restore status should contain the error info
        failed_restore, finished_restore = 0, 0
        for i in range(RETRY_COUNTS):
            failed_restore, finished_restore = 0, 0
            rs = cmd.restore_status(dr_address)
            for status in rs.values():
                if status['backupURL'] != backup1:
                    break
                if 'error' in status.keys():
                    if status['error'] != "":
                        assert 'failed to clean up the existing file' in\
                               status['error']
                        failed_restore += 1
                if not status["isRestoring"]:
                    finished_restore += 1
            if failed_restore == 2 and finished_restore == 2:
                break
            time.sleep(RETRY_INTERVAL)
        assert failed_restore == 2 and finished_restore == 2

        # mock inc restore error: invalid block
        command = ["find", VFS_DIR, "-type", "d", "-name", VOLUME_NAME]
        backup_volume_path = subprocess.check_output(command).strip()
        command = ["find", backup_volume_path, "-name", "*blk"]
        blocks = subprocess.check_output(command).split()
        assert len(blocks) != 0
        for blk in blocks:
            command = ["mv", blk, blk + ".tmp".encode('utf-8')]
            subprocess.check_output(command).strip()
        cmd.restore_inc(dr_address, backup1, backup0_name)
        # restore status should contain the error info
        failed_restore, finished_restore = 0, 0
        for i in range(RETRY_COUNTS):
            failed_restore, finished_restore = 0, 0
            rs = cmd.restore_status(dr_address)
            for status in rs.values():
                if status['backupURL'] != backup1:
                    break
                if 'error' in status.keys():
                    if status['error'] != "":
                        assert 'no such file or directory' in \
                               status['error']
                        failed_restore += 1
                if not status["isRestoring"]:
                    finished_restore += 1
            if failed_restore == 2 and finished_restore == 2:
                break
            time.sleep(RETRY_INTERVAL)
        assert failed_restore == 2 and finished_restore == 2

        assert path.exists(FIXED_REPLICA_PATH1 + delta_file1)
        assert path.exists(FIXED_REPLICA_PATH2 + delta_file1)
        for blk in blocks:
            command = ["mv", blk + ".tmp".encode('utf-8'), blk]
            subprocess.check_output(command)

    data1 = \
        snap0_data[0:offset1] + snap1_data + \
        snap0_data[offset1+length1:]
    restore_incrementally(dr_address, backup1, backup0_name)

    verify_no_frontend_data(grpc_engine_manager, 0, data1, grpc_dr_controller)

    assert not path.exists(FIXED_REPLICA_PATH1 + delta_file1)
    assert not path.exists(FIXED_REPLICA_PATH2 + delta_file1)

    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup1_name)

    data2 = \
        data1[0:offset2] + snap2_data + \
        zero_string * (BLOCK_SIZE - length2 - offset2) + snap2_data
    restore_incrementally(dr_address, backup2, backup1_name)
    verify_no_frontend_data(grpc_engine_manager, 0, data2, grpc_dr_controller)

    delta_file2 = "volume-delta-" + backup1_name + ".img"
    assert not path.exists(FIXED_REPLICA_PATH1 + delta_file2)
    assert not path.exists(FIXED_REPLICA_PATH2 + delta_file2)

    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup2_name)

    # mock race condition
    with pytest.raises(subprocess.CalledProcessError) as e:
        restore_incrementally(dr_address, backup1, backup0_name)
    assert "doesn't match field LastRestored" in e.value.stdout

    data3 = zero_string * length3 + data2[length3:length2]
    restore_incrementally(dr_address, backup3, backup2_name)
    verify_no_frontend_data(grpc_engine_manager, 0, data3, grpc_dr_controller)

    delta_file3 = "volume-delta-" + backup3_name + ".img"
    assert not path.exists(FIXED_REPLICA_PATH1 + delta_file3)
    assert not path.exists(FIXED_REPLICA_PATH2 + delta_file3)
    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup3_name)

    # mock corner case: invalid last-restored backup
    rm_backups(address, ENGINE_NAME, [backup3])
    # actually it is full restoration
    restore_incrementally(dr_address, backup4, backup3_name)
    verify_no_frontend_data(grpc_engine_manager, 0, snap4_data,
                            grpc_dr_controller)
    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup4_name)

    if "vfs" in backup_target:
        command = ["find", VFS_DIR, "-type", "d", "-name", VOLUME_NAME]
        backup_volume_path = subprocess.check_output(command).strip()
        command = ["find", backup_volume_path, "-name", "*tempoary"]
        tmp_files = subprocess.check_output(command).split()
        assert len(tmp_files) == 0

    cleanup_no_frontend_volume(grpc_engine_manager, grpc_dr_controller,
                               grpc_dr_replica1, grpc_dr_replica2)

    rm_backups(address, ENGINE_NAME, [backup0, backup1, backup2, backup4])

    cmd.sync_agent_server_reset(address)
    cleanup_controller(grpc_controller)
    cleanup_replica(grpc_replica1)
    cleanup_replica(grpc_replica2)
Exemple #5
0
def backup_core(
        bin,
        engine_manager_client,  # NOQA
        grpc_controller_client,  # NOQA
        grpc_replica_client,  # NOQA
        grpc_replica_client2,  # NOQA
        backup_target):
    open_replica(grpc_replica_client)
    open_replica(grpc_replica_client2)

    r1_url = grpc_replica_client.url
    r2_url = grpc_replica_client2.url
    v = grpc_controller_client.volume_start(replicas=[
        r1_url,
        r2_url,
    ])
    assert v.replicaCount == 2

    backup_type = urlparse(backup_target).scheme

    # create & process backup1
    snapshot1 = cmd.snapshot_create(grpc_controller_client.address)
    output = grpc_replica_client.replica_get().chain[1]
    assert output == 'volume-snap-{}.img'.format(snapshot1)

    backup1 = cmd.backup_create(grpc_controller_client.address, snapshot1,
                                backup_target, {
                                    'name': 'backup1',
                                    'type': backup_type
                                })
    backup1_info = cmd.backup_inspect(grpc_controller_client.address, backup1)
    assert backup1_info["URL"] == backup1
    assert backup1_info["IsIncremental"] is False
    assert backup1_info["VolumeName"] == VOLUME_NAME
    assert backup1_info["VolumeSize"] == SIZE_STR
    assert backup1_info["SnapshotName"] == snapshot1
    assert len(backup1_info["Labels"]) == 2
    assert backup1_info["Labels"]["name"] == "backup1"
    assert backup1_info["Labels"]["type"] == backup_type

    # create & process backup2
    snapshot2 = cmd.snapshot_create(grpc_controller_client.address)
    output = grpc_replica_client.replica_get().chain[1]
    assert output == 'volume-snap-{}.img'.format(snapshot2)

    backup2 = cmd.backup_create(grpc_controller_client.address, snapshot2,
                                backup_target)

    backup2_info = cmd.backup_inspect(grpc_controller_client.address, backup2)
    assert backup2_info["URL"] == backup2
    assert backup2_info["IsIncremental"] is True
    assert backup2_info["VolumeName"] == VOLUME_NAME
    assert backup2_info["VolumeSize"] == SIZE_STR
    assert backup2_info["SnapshotName"] == snapshot2
    if backup2_info["Labels"] is not None:
        assert len(backup2_info["Labels"]) == 0

    # list all known backups for volume
    volume_info = cmd.backup_volume_list(
        grpc_controller_client.address,
        VOLUME_NAME,
        backup_target,
        include_backup_details=True)[VOLUME_NAME]
    assert volume_info["Name"] == VOLUME_NAME
    assert volume_info["Size"] == SIZE_STR
    backup_list = volume_info["Backups"]
    assert backup_list[backup1]["URL"] == backup1_info["URL"]
    assert backup_list[backup1]["SnapshotName"] == backup1_info["SnapshotName"]
    assert backup_list[backup1]["Size"] == backup1_info["Size"]
    assert backup_list[backup1]["Created"] == backup1_info["Created"]
    assert backup_list[backup1]["Messages"] is None
    assert backup_list[backup2]["URL"] == backup2_info["URL"]
    assert backup_list[backup2]["SnapshotName"] == backup2_info["SnapshotName"]
    assert backup_list[backup2]["Size"] == backup2_info["Size"]
    assert backup_list[backup2]["Created"] == backup2_info["Created"]
    assert backup_list[backup2]["Messages"] is None

    # test that corrupt backups are signaled during a list operation
    # https://github.com/longhorn/longhorn/issues/1212
    volume_dir = finddir(BACKUP_DIR, VOLUME_NAME)
    assert volume_dir
    assert os.path.exists(volume_dir)
    backup_dir = os.path.join(volume_dir, "backups")
    assert os.path.exists(backup_dir)
    backup_cfg_name = "backup_" + backup2_info["Name"] + ".cfg"
    assert backup_cfg_name
    backup_cfg_path = findfile(backup_dir, backup_cfg_name)
    assert os.path.exists(backup_cfg_path)
    backup_tmp_cfg_path = os.path.join(volume_dir, backup_cfg_name)
    os.rename(backup_cfg_path, backup_tmp_cfg_path)
    assert os.path.exists(backup_tmp_cfg_path)

    corrupt_backup = open(backup_cfg_path, "w")
    assert corrupt_backup
    assert corrupt_backup.write("{corrupt: definitely") > 0
    corrupt_backup.close()

    # request the new backup list
    volume_info = cmd.backup_volume_list(
        grpc_controller_client.address,
        VOLUME_NAME,
        backup_target,
        include_backup_details=True)[VOLUME_NAME]
    assert volume_info["Name"] == VOLUME_NAME
    backup_list = volume_info["Backups"]
    assert backup_list[backup1]["URL"] == backup1_info["URL"]
    assert backup_list[backup1]["Messages"] is None
    assert backup_list[backup2]["URL"] == backup2_info["URL"]
    assert MESSAGE_TYPE_ERROR in backup_list[backup2]["Messages"]

    # we still want to fail inspects, since they operate on urls
    # with no guarantee of backup existence
    with pytest.raises(subprocess.CalledProcessError):
        cmd.backup_inspect(grpc_controller_client.address, backup2)

    # switch back to valid cfg
    os.rename(backup_tmp_cfg_path, backup_cfg_path)
    assert cmd.backup_inspect(grpc_controller_client.address, backup2)

    # test that list returns a volume_info with an error message
    # for a missing volume.cfg instead of failing with an error
    # https://github.com/rancher/longhorn/issues/399
    volume_cfg_path = findfile(volume_dir, VOLUME_CONFIG_FILE)
    assert os.path.exists(volume_cfg_path)
    volume_tmp_cfg_path = volume_cfg_path.replace(VOLUME_CONFIG_FILE,
                                                  VOLUME_TMP_CONFIG_FILE)
    os.rename(volume_cfg_path, volume_tmp_cfg_path)
    assert os.path.exists(volume_tmp_cfg_path)

    volume_info = cmd.backup_volume_list(grpc_controller_client.address, "",
                                         backup_target)
    assert MESSAGE_TYPE_ERROR in volume_info[VOLUME_NAME]["Messages"]

    os.rename(volume_tmp_cfg_path, volume_cfg_path)
    assert os.path.exists(volume_cfg_path)

    volume_info = cmd.backup_volume_list(grpc_controller_client.address, "",
                                         backup_target)
    assert volume_info[VOLUME_NAME]["Messages"] is not None
    assert MESSAGE_TYPE_ERROR not in volume_info[VOLUME_NAME]["Messages"]

    # backup doesn't exists so it should error
    with pytest.raises(subprocess.CalledProcessError):
        url = backup_target + "?backup=backup-unk" + "&volume=" + VOLUME_NAME
        cmd.backup_inspect(grpc_controller_client.address, url)

    # this returns unsupported driver since `bad` is not a known scheme
    with pytest.raises(subprocess.CalledProcessError):
        cmd.backup_inspect(grpc_controller_client.address, "bad://xxx")

    reset_volume(grpc_controller_client, grpc_replica_client,
                 grpc_replica_client2)
    restore_with_frontend(grpc_controller_client.address, ENGINE_NAME, backup1)
    restore_with_frontend(grpc_controller_client.address, ENGINE_NAME, backup2)

    # remove backups + volume
    cmd.backup_rm(grpc_controller_client.address, backup1)
    cmd.backup_rm(grpc_controller_client.address, backup2)
    cmd.backup_volume_rm(grpc_controller_client.address, VOLUME_NAME,
                         backup_target)

    assert os.path.exists(BACKUP_DIR)
    assert not os.path.exists(volume_cfg_path)
def restore_inc_test(grpc_engine_manager,  # NOQA
                     grpc_controller,  # NOQA
                     grpc_replica1, grpc_replica2,  # NOQA
                     grpc_dr_controller,  # NOQA
                     grpc_dr_replica1, grpc_dr_replica2,  # NOQA
                     backup_target):  # NOQA
    address = grpc_controller.address

    dev = get_dev(grpc_replica1, grpc_replica2, grpc_controller)

    zero_string = b'\x00'.decode('utf-8')

    # backup0: 256 random data in 1st block
    length0 = 256
    snap0_data = random_string(length0)
    verify_data(dev, 0, snap0_data)
    verify_data(dev, BLOCK_SIZE, snap0_data)
    snap0 = cmd.snapshot_create(address)
    backup0 = create_backup(address, snap0, backup_target)["URL"]
    backup0_name = cmd.backup_inspect(address, backup0)['Name']

    # backup1: 32 random data + 32 zero data + 192 random data in 1st block
    length1 = 32
    offset1 = 32
    snap1_data = zero_string * length1
    verify_data(dev, offset1, snap1_data)
    snap1 = cmd.snapshot_create(address)
    backup1 = create_backup(address, snap1, backup_target)["URL"]
    backup1_name = cmd.backup_inspect(address, backup1)['Name']

    # backup2: 32 random data + 256 random data in 1st block,
    #          256 random data in 2nd block
    length2 = 256
    offset2 = 32
    snap2_data = random_string(length2)
    verify_data(dev, offset2, snap2_data)
    verify_data(dev, BLOCK_SIZE, snap2_data)
    snap2 = cmd.snapshot_create(address)
    backup2 = create_backup(address, snap2, backup_target)["URL"]
    backup2_name = cmd.backup_inspect(address, backup2)['Name']

    # backup3: 64 zero data + 192 random data in 1st block
    length3 = 64
    offset3 = 0
    verify_data(dev, offset3, zero_string * length3)
    verify_data(dev, length2, zero_string * offset2)
    verify_data(dev, BLOCK_SIZE, zero_string * length2)
    snap3 = cmd.snapshot_create(address)
    backup3 = create_backup(address, snap3, backup_target)["URL"]
    backup3_name = cmd.backup_inspect(address, backup3)['Name']

    # backup4: 256 random data in 1st block
    length4 = 256
    offset4 = 0
    snap4_data = random_string(length4)
    verify_data(dev, offset4, snap4_data)
    snap4 = cmd.snapshot_create(address)
    backup4 = create_backup(address, snap4, backup_target)["URL"]
    backup4_name = cmd.backup_inspect(address, backup4)['Name']

    # start no-frontend volume
    # start dr volume (no frontend)
    dr_address = grpc_dr_controller.address
    start_no_frontend_volume(grpc_engine_manager,
                             grpc_dr_controller,
                             grpc_dr_replica1, grpc_dr_replica2)

    cmd.backup_restore(dr_address, backup0)
    wait_for_restore_completion(dr_address, backup0)
    verify_no_frontend_data(grpc_engine_manager,
                            0, snap0_data, grpc_dr_controller)

    # mock restore crash/error
    delta_file1 = "volume-delta-" + backup0_name + ".img"
    if "vfs" in backup_target:
        command = ["find", VFS_DIR, "-type", "d", "-name", VOLUME_NAME]
        backup_volume_path = subprocess.check_output(command).strip()
        command = ["find", backup_volume_path, "-name", "*blk"]
        blocks = subprocess.check_output(command).split()
        assert len(blocks) != 0
        for blk in blocks:
            command = ["mv", blk, blk+".tmp".encode('utf-8')]
            subprocess.check_output(command).strip()
        # should fail
        is_failed = False
        cmd.restore_inc(dr_address, backup1, backup0_name)
        for i in range(RETRY_COUNTS):
            rs = cmd.restore_status(dr_address)
            for status in rs.values():
                if status['backupURL'] != backup1:
                    break
                if 'error' in status.keys():
                    if status['error'] != "":
                        assert 'no such file or directory' in \
                               status['error']
                        is_failed = True
            if is_failed:
                break
            time.sleep(RETRY_INTERVAL)
        assert is_failed

        assert path.exists(FIXED_REPLICA_PATH1 + delta_file1)
        assert path.exists(FIXED_REPLICA_PATH2 + delta_file1)
        for blk in blocks:
            command = ["mv", blk+".tmp".encode('utf-8'), blk]
            subprocess.check_output(command)

    data1 = \
        snap0_data[0:offset1] + snap1_data + \
        snap0_data[offset1+length1:]
    # race condition: last restoration has failed
    # but `isRestoring` hasn't been cleanup
    for i in range(RETRY_COUNTS):
        try:
            restore_incrementally(dr_address, backup1, backup0_name)
            break
        except subprocess.CalledProcessError as e:
            if "already in progress" not in e.output:
                time.sleep(RETRY_INTERVAL)
            else:
                raise e

    verify_no_frontend_data(grpc_engine_manager,
                            0, data1, grpc_dr_controller)

    assert not path.exists(FIXED_REPLICA_PATH1 + delta_file1)
    assert not path.exists(FIXED_REPLICA_PATH2 + delta_file1)

    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup1_name)

    data2 = \
        data1[0:offset2] + snap2_data + \
        zero_string * (BLOCK_SIZE - length2 - offset2) + snap2_data
    restore_incrementally(dr_address, backup2, backup1_name)
    verify_no_frontend_data(grpc_engine_manager,
                            0, data2, grpc_dr_controller)

    delta_file2 = "volume-delta-" + backup1_name + ".img"
    assert not path.exists(FIXED_REPLICA_PATH1 + delta_file2)
    assert not path.exists(FIXED_REPLICA_PATH2 + delta_file2)

    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup2_name)

    # mock race condition
    with pytest.raises(subprocess.CalledProcessError) as e:
        restore_incrementally(dr_address, backup1, backup0_name)
        assert "doesn't match lastRestored" in e

    data3 = zero_string * length3 + data2[length3:length2]
    restore_incrementally(dr_address, backup3, backup2_name)
    verify_no_frontend_data(grpc_engine_manager,
                            0, data3, grpc_dr_controller)

    delta_file3 = "volume-delta-" + backup3_name + ".img"
    assert not path.exists(FIXED_REPLICA_PATH1 + delta_file3)
    assert not path.exists(FIXED_REPLICA_PATH2 + delta_file3)
    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup3_name)

    # mock corner case: invalid last-restored backup
    rm_backups(address, ENGINE_NAME, [backup3])
    # actually it is full restoration
    restore_incrementally(dr_address, backup4, backup3_name)
    verify_no_frontend_data(grpc_engine_manager,
                            0, snap4_data, grpc_dr_controller)
    status = cmd.restore_status(dr_address)
    compare_last_restored_with_backup(status, backup4_name)

    if "vfs" in backup_target:
        command = ["find", VFS_DIR, "-type", "d", "-name", VOLUME_NAME]
        backup_volume_path = subprocess.check_output(command).strip()
        command = ["find", backup_volume_path, "-name", "*tempoary"]
        tmp_files = subprocess.check_output(command).split()
        assert len(tmp_files) == 0

    cleanup_no_frontend_volume(grpc_engine_manager,
                               grpc_dr_controller,
                               grpc_dr_replica1, grpc_dr_replica2)

    rm_backups(address, ENGINE_NAME, [backup0, backup1, backup2, backup4])

    cmd.sync_agent_server_reset(address)
    cleanup_controller(grpc_controller)
    cleanup_replica(grpc_replica1)
    cleanup_replica(grpc_replica2)