async def test_setting_cancels_and_resyncs(reader: ReaderHelper,
                                           ui_server: UiServer, config: Config,
                                           server, session, drive: DriveSource,
                                           coord: Coordinator):
    # Create a blocking sync task
    coord._sync_wait.set()
    sync = asyncio.create_task(coord.sync(), name="Sync from saving settings")
    await coord._sync_start.wait()
    assert not sync.cancelled()
    assert not sync.done()

    # Change some config
    update = {
        "config": {
            "days_between_snapshots": 20,
            "drive_ipv4": ""
        },
        "snapshot_folder": "unused"
    }
    assert await reader.postjson("saveconfig", json=update) == {
        'message': 'Settings saved'
    }

    # verify the previous sync is done and another one is running
    assert sync.done()
    assert coord.isSyncing()
async def test_update_disable_drive(reader: ReaderHelper, server,
                                    coord: Coordinator, config: Config,
                                    drive_requests: DriveRequests):
    # Disable drive
    drive_requests.creds = None
    os.remove(config.get(Setting.CREDENTIALS_FILE_PATH))
    assert not coord.enabled()
    await coord.sync()
    assert len(coord.snapshots()) == 0

    # Disable Drive Upload
    update = {
        "config": {
            Setting.ENABLE_DRIVE_UPLOAD.value: False
        },
        "snapshot_folder": ""
    }
    assert await reader.postjson("saveconfig", json=update) == {
        'message': 'Settings saved',
        "reload_page": True
    }
    assert config.get(Setting.ENABLE_DRIVE_UPLOAD) is False

    # Verify the app is working fine.
    assert coord.enabled()
    await coord.waitForSyncToFinish()
    assert len(coord.snapshots()) == 1
async def test_check_size_sync(coord: Coordinator, source: HelperTestSource,
                               dest: HelperTestSource, time, fs: FsFaker,
                               global_info: GlobalInfo):
    skipForWindows()
    fs.setFreeBytes(0)
    await coord.sync()
    assert len(coord.snapshots()) == 0
    assert global_info._last_error is not None

    await coord.sync()
    assert len(coord.snapshots()) == 0
    assert global_info._last_error is not None

    # Verify it resets the global size skip check, but gets through once
    global_info.setSkipSpaceCheckOnce(True)
    await coord.sync()
    assert len(coord.snapshots()) == 1
    assert global_info._last_error is None
    assert not global_info.isSkipSpaceCheckOnce()

    # Next attempt to snapshot shoudl fail again.
    time.advance(days=7)
    await coord.sync()
    assert len(coord.snapshots()) == 1
    assert global_info._last_error is not None
async def test_update_ignore(reader: ReaderHelper, time: FakeTime,
                             coord: Coordinator, config: Config,
                             supervisor: SimulatedSupervisor, ha: HaSource,
                             drive: DriveSource):
    config.override(Setting.IGNORE_UPGRADE_SNAPSHOTS, True)
    config.override(Setting.DAYS_BETWEEN_SNAPSHOTS, 0)

    # make an ignored_snapshot
    slug = await supervisor.createSnapshot(
        {
            'name': "Ignore_me",
            'folders': ['homeassistant'],
            'addons': []
        },
        date=time.now())

    await coord.sync()
    assert len(await drive.get()) == 0
    assert len(await ha.get()) == 1
    assert len(coord.snapshots()) == 1

    # Disable Drive Upload
    update = {
        "ignore": False,
        "slug": slug,
    }
    await reader.postjson("ignore", json=update)
    await coord.waitForSyncToFinish()
    assert len(coord.snapshots()) == 1
    assert len(await drive.get()) == 1
    assert len(await ha.get()) == 1
Ejemplo n.º 5
0
async def test_sync(reader, ui_server, coord: Coordinator, time: FakeTime, session):
    assert len(coord.snapshots()) == 0
    status = await reader.getjson("sync")
    assert len(coord.snapshots()) == 1
    assert status == await reader.getjson("getstatus")
    time.advance(days=7)
    assert len((await reader.getjson("sync"))['snapshots']) == 2
async def test_working_through_upload(coord: Coordinator, global_info: GlobalInfo, dest):
    coord._sync_wait.clear()
    assert not coord.isWorkingThroughUpload()
    sync_task = asyncio.create_task(coord.sync())
    await coord._sync_start.wait()
    assert not coord.isWorkingThroughUpload()
    dest.working = True
    assert coord.isWorkingThroughUpload()
    coord._sync_wait.set()
    await asyncio.wait([sync_task])
    assert not coord.isWorkingThroughUpload()
async def test_disabled_at_install(coord: Coordinator, dest, time):
    """
    Verifies that at install time, if some snapshots are already present the
    addon doesn't try to sync over and over when drive is disabled.  This was
    a problem at one point.
    """
    dest.setEnabled(True)
    await coord.sync()
    assert len(coord.snapshots()) == 1

    dest.setEnabled(False)
    time.advance(days=5)
    assert coord.check()
    await coord.sync()
    assert not coord.check()
async def test_new_snapshot(coord: Coordinator, time: FakeTime, source, dest):
    await coord.startSnapshot(CreateOptions(time.now(), "Test Name"))
    snapshots = coord.snapshots()
    assert len(snapshots) == 1
    assert snapshots[0].name() == "Test Name"
    assert snapshots[0].getSource(source.name()) is not None
    assert snapshots[0].getSource(dest.name()) is None
async def test_sync(coord: Coordinator, global_info: GlobalInfo,
                    time: FakeTime):
    await coord.sync()
    assert global_info._syncs == 1
    assert global_info._successes == 1
    assert global_info._last_sync_start == time.now()
    assert len(coord.snapshots()) == 1
async def test_new_backup(coord: Coordinator, time: FakeTime, source, dest):
    await coord.startBackup(CreateOptions(time.now(), "Test Name"))
    backups = coord.backups()
    assert len(backups) == 1
    assert backups[0].name() == "Test Name"
    assert backups[0].getSource(source.name()) is not None
    assert backups[0].getSource(dest.name()) is None
Ejemplo n.º 11
0
async def test_delete(coord: Coordinator, snapshot, source, dest):
    assert snapshot.getSource(source.name()) is not None
    assert snapshot.getSource(dest.name()) is not None
    await coord.delete([source.name()], snapshot.slug())
    assert len(coord.snapshots()) == 1
    assert snapshot.getSource(source.name()) is None
    assert snapshot.getSource(dest.name()) is not None
    await coord.delete([dest.name()], snapshot.slug())
    assert snapshot.getSource(source.name()) is None
    assert snapshot.getSource(dest.name()) is None
    assert snapshot.isDeleted()
    assert len(coord.snapshots()) == 0

    await coord.sync()
    assert len(coord.snapshots()) == 1
    await coord.delete([source.name(), dest.name()], coord.snapshots()[0].slug())
    assert len(coord.snapshots()) == 0
async def test_only_source_configured(coord: Coordinator,
                                      dest: HelperTestSource, time,
                                      source: HelperTestSource):
    source.setEnabled(True)
    dest.setEnabled(False)
    dest.setNeedsConfiguration(False)
    await coord.sync()
    assert len(coord.snapshots()) == 1
async def test_delete(coord: Coordinator, backup, source, dest):
    assert backup.getSource(source.name()) is not None
    assert backup.getSource(dest.name()) is not None
    await coord.delete([source.name()], backup.slug())
    assert len(coord.backups()) == 1
    assert backup.getSource(source.name()) is None
    assert backup.getSource(dest.name()) is not None
    await coord.delete([dest.name()], backup.slug())
    assert backup.getSource(source.name()) is None
    assert backup.getSource(dest.name()) is None
    assert backup.isDeleted()
    assert len(coord.backups()) == 0

    await coord.sync()
    assert len(coord.backups()) == 1
    await coord.delete([source.name(), dest.name()], coord.backups()[0].slug())
    assert len(coord.backups()) == 0
async def test_freshness(coord: Coordinator, source: HelperTestSource,
                         dest: HelperTestSource, snapshot: Snapshot,
                         time: FakeTime):
    source.setMax(2)
    dest.setMax(2)
    await coord.sync()
    assert snapshot.getPurges() == {source.name(): False, dest.name(): False}

    source.setMax(1)
    dest.setMax(1)
    await coord.sync()
    assert snapshot.getPurges() == {source.name(): True, dest.name(): True}

    dest.setMax(0)
    await coord.sync()
    assert snapshot.getPurges() == {source.name(): True, dest.name(): False}

    source.setMax(0)
    await coord.sync()
    assert snapshot.getPurges() == {source.name(): False, dest.name(): False}

    source.setMax(2)
    dest.setMax(2)
    time.advance(days=7)
    await coord.sync()
    assert len(coord.snapshots()) == 2
    assert snapshot.getPurges() == {source.name(): True, dest.name(): True}
    assert coord.snapshots()[1].getPurges() == {
        source.name(): False,
        dest.name(): False
    }

    # should refresh on delete
    source.setMax(1)
    dest.setMax(1)
    await coord.delete([source.name()], snapshot.slug())
    assert coord.snapshots()[0].getPurges() == {dest.name(): True}
    assert coord.snapshots()[1].getPurges() == {
        source.name(): True,
        dest.name(): False
    }

    # should update on retain
    await coord.retain({dest.name(): True}, snapshot.slug())
    assert coord.snapshots()[0].getPurges() == {dest.name(): False}
    assert coord.snapshots()[1].getPurges() == {
        source.name(): True,
        dest.name(): True
    }

    # should update on upload
    await coord.uploadSnapshot(coord.snapshots()[0].slug())
    assert coord.snapshots()[0].getPurges() == {
        dest.name(): False,
        source.name(): True
    }
    assert coord.snapshots()[1].getPurges() == {
        source.name(): False,
        dest.name(): True
    }
async def test_max_sync_interval_next_sync_attempt(coord: Coordinator, model, source: HelperTestSource, dest: HelperTestSource, backup, time: FakeTime, simple_config: Config):
    """
    Next backup is after max sync interval is reached
    """
    simple_config.override(Setting.DAYS_BETWEEN_BACKUPS, 1)
    simple_config.override(Setting.MAX_SYNC_INTERVAL_SECONDS, 60 * 60)

    time.setTimeZone("Europe/Stockholm")
    simple_config.override(Setting.BACKUP_TIME_OF_DAY, "03:23")
    simple_config.override(Setting.DAYS_BETWEEN_BACKUPS, 1)

    source.setMax(10)
    source.insert("Fri", time.toUtc(time.local(2020, 3, 16, 3, 33)))
    time.setNow(time.local(2020, 3, 17, 1, 29))
    model.reinitialize()
    coord.reset()
    await coord.sync()
    assert coord.nextSyncAttempt() == time.local(2020, 3, 17, 2, 29)
    assert coord.nextBackupTime() > coord.nextSyncAttempt()
Ejemplo n.º 16
0
async def test_schedule_snapshot_next_sync_attempt(coord: Coordinator, model, source: HelperTestSource, dest: HelperTestSource, snapshot, time: FakeTime, simple_config: Config):
    """
    Next snapshot is before max sync interval is reached
    """
    simple_config.override(Setting.DAYS_BETWEEN_SNAPSHOTS, 1)
    simple_config.override(Setting.MAX_SYNC_INTERVAL_SECONDS, 60 * 60)

    time.setTimeZone("Europe/Stockholm")
    simple_config.override(Setting.SNAPSHOT_TIME_OF_DAY, "03:23")
    simple_config.override(Setting.DAYS_BETWEEN_SNAPSHOTS, 1)

    source.setMax(10)
    source.insert("Fri", time.toUtc(time.local(2020, 3, 16, 3, 33)))

    time.setNow(time.local(2020, 3, 17, 2, 29))
    model.reinitialize()
    coord.reset()
    await coord.sync()
    assert coord.nextSnapshotTime() == time.local(2020, 3, 17, 3, 23)
    assert coord.nextSnapshotTime() == coord.nextSyncAttempt()
async def test_backup_now(reader, ui_server, time: FakeTime,
                          snapshot: Snapshot, coord: Coordinator):
    assert len(coord.snapshots()) == 1
    assert (
        await
        reader.getjson("getstatus"))["snapshots"][0]["date"] == time.toLocal(
            time.now()).strftime("%c")

    time.advance(hours=1)
    assert await reader.getjson(
        "snapshot?custom_name=TestName&retain_drive=False&retain_ha=False"
    ) == {
        'message': "Requested snapshot 'TestName'"
    }
    status = await reader.getjson('getstatus')
    assert len(status["snapshots"]) == 2
    assert status["snapshots"][1]["date"] == time.toLocal(
        time.now()).strftime("%c")
    assert status["snapshots"][1]["name"] == "TestName"
    assert not status["snapshots"][1]["driveRetain"]
    assert not status["snapshots"][1]["haRetain"]

    time.advance(hours=1)
    assert await reader.getjson(
        "snapshot?custom_name=TestName2&retain_drive=True&retain_ha=False"
    ) == {
        'message': "Requested snapshot 'TestName2'"
    }
    await coord.sync()
    status = await reader.getjson('getstatus')
    assert len(status["snapshots"]) == 3
    assert not status["snapshots"][1]["driveRetain"]
    assert status["snapshots"][2]["date"] == time.toLocal(
        time.now()).strftime("%c")
    assert status["snapshots"][2]["name"] == "TestName2"
    assert not status["snapshots"][2]["haRetain"]
    assert status["snapshots"][2]["driveRetain"]

    time.advance(hours=1)
    assert await reader.getjson(
        "snapshot?custom_name=TestName3&retain_drive=False&retain_ha=True"
    ) == {
        'message': "Requested snapshot 'TestName3'"
    }
    await coord.sync()
    status = await reader.getjson('getstatus')
    assert len(status["snapshots"]) == 4
    assert not status["snapshots"][1]["driveRetain"]
    assert status["snapshots"][3]["date"] == time.toLocal(
        time.now()).strftime("%c")
    assert status["snapshots"][3]["name"] == "TestName3"
    assert status["snapshots"][3]["haRetain"]
    assert not status["snapshots"][3]["driveRetain"]
async def test_backup_now(reader, ui_server, time: FakeTime, backup: Backup,
                          coord: Coordinator):
    assert len(coord.backups()) == 1
    assert (await
            reader.getjson("getstatus"))["backups"][0]["date"] == time.toLocal(
                time.now()).strftime("%c")

    time.advance(hours=1)
    assert await reader.getjson(
        "backup?custom_name=TestName&retain_drive=False&retain_ha=False") == {
            'message': "Requested backup 'TestName'"
        }
    status = await reader.getjson('getstatus')
    assert len(status["backups"]) == 2
    assert status["backups"][1]["date"] == time.toLocal(
        time.now()).strftime("%c")
    assert status["backups"][1]["name"] == "TestName"
    assert status["backups"][1]['sources'][0]['retained'] is False
    assert len(status["backups"][1]['sources']) == 1

    time.advance(hours=1)
    assert await reader.getjson(
        "backup?custom_name=TestName2&retain_drive=True&retain_ha=False") == {
            'message': "Requested backup 'TestName2'"
        }
    await coord.sync()
    status = await reader.getjson('getstatus')
    assert len(status["backups"]) == 3
    assert status["backups"][2]["date"] == time.toLocal(
        time.now()).strftime("%c")
    assert status["backups"][2]["name"] == "TestName2"
    assert status["backups"][2]['sources'][0]['retained'] is False
    assert status["backups"][2]['sources'][1]['retained'] is True

    time.advance(hours=1)
    assert await reader.getjson(
        "backup?custom_name=TestName3&retain_drive=False&retain_ha=True") == {
            'message': "Requested backup 'TestName3'"
        }
    await coord.sync()
    status = await reader.getjson('getstatus')
    assert len(status["backups"]) == 4
    assert status["backups"][3]['sources'][0]['retained'] is True
    assert status["backups"][3]['sources'][1]['retained'] is False
    assert status["backups"][3]["date"] == time.toLocal(
        time.now()).strftime("%c")
    assert status["backups"][3]["name"] == "TestName3"
async def test_blocking(coord: Coordinator):
    # This just makes sure the wait thread is blocked while we do stuff
    event_start = asyncio.Event()
    event_end = asyncio.Event()
    asyncio.create_task(coord._withSoftLock(lambda: sleepHelper(event_start, event_end)))
    await event_start.wait()

    # Make sure PleaseWait gets called on these
    with raises(PleaseWait):
        await coord.delete(None, None)
    with raises(PleaseWait):
        await coord.sync()
    with raises(PleaseWait):
        await coord.uploadBackups(None)
    with raises(PleaseWait):
        await coord.startBackup(None)
    event_end.set()
async def test_alternate_timezone(coord: Coordinator, time: FakeTime, model: Model, dest, source, simple_config: Config):
    time.setTimeZone("Europe/Stockholm")
    simple_config.override(Setting.BACKUP_TIME_OF_DAY, "12:00")
    simple_config.override(Setting.DAYS_BETWEEN_BACKUPS, 1)

    source.setMax(10)
    source.insert("Fri", time.toUtc(time.local(2020, 3, 16, 18, 5)))
    time.setNow(time.local(2020, 3, 16, 18, 6))
    model.reinitialize()
    coord.reset()
    await coord.sync()
    assert not coord.check()
    assert coord.nextBackupTime() == time.local(2020, 3, 17, 12)

    time.setNow(time.local(2020, 3, 17, 11, 59))
    await coord.sync()
    assert not coord.check()
    time.setNow(time.local(2020, 3, 17, 12))
    assert coord.check()
async def test_backoff(coord: Coordinator, model, source: HelperTestSource,
                       dest: HelperTestSource, snapshot, time: FakeTime,
                       simple_config: Config):
    assert coord.check()
    simple_config.override(Setting.DAYS_BETWEEN_SNAPSHOTS, 1)
    simple_config.override(Setting.MAX_SYNC_INTERVAL_SECONDS, 60 * 60 * 6)

    assert coord.nextSyncAttempt() == time.now() + timedelta(hours=6)
    assert not coord.check()
    error = Exception("BOOM")
    old_sync = model.sync
    model.sync = lambda s: doRaise(error)
    await coord.sync()

    # first backoff should be 0 seconds
    assert coord.nextSyncAttempt() == time.now()
    assert coord.check()

    # backoff maxes out at 1 hr = 3600 seconds
    for seconds in [
            10, 20, 40, 80, 160, 320, 640, 1280, 2560, 3600, 3600, 3600
    ]:
        await coord.sync()
        assert coord.nextSyncAttempt() == time.now() + timedelta(
            seconds=seconds)
        assert not coord.check()
        assert not coord.check()
        assert not coord.check()

    # a good sync resets it back to 6 hours from now
    model.sync = old_sync
    await coord.sync()
    assert coord.nextSyncAttempt() == time.now() + timedelta(hours=6)
    assert not coord.check()

    # if the next snapshot is less that 6 hours from the last one, that that shoudl be when we sync
    simple_config.override(Setting.DAYS_BETWEEN_SNAPSHOTS, 1.0 / 24.0)
    assert coord.nextSyncAttempt() == time.now() + timedelta(hours=1)
    assert not coord.check()

    time.advance(hours=2)
    assert coord.nextSyncAttempt() == time.now() - timedelta(hours=1)
    assert coord.check()
async def test_cancel(coord: Coordinator, global_info: GlobalInfo):
    coord._sync_wait.clear()
    asyncio.create_task(coord.sync())
    await coord._sync_start.wait()
    await coord.cancel()
    assert isinstance(global_info._last_error, UserCancelledError)
def coord(model, time, simple_config, global_info, estimator):
    return Coordinator(model, time, simple_config, global_info, estimator)
async def test_enabled(coord: Coordinator, dest, time):
    dest.setEnabled(True)
    assert coord.enabled()
    dest.setEnabled(False)
    assert not coord.enabled()