summary: Check that the basic snapshots functionality is ok

details: |
    A snapshot is a copy of the user, system and configuration data stored
    by snapd for one or more snaps on your system. This data can be found
    in $HOME/snap/<snap-name> and /var/snap/<snap-name>.

    Snapshots are generated manually with the snap save command and
    automatically when a snap is removed (unless --purge is used with
    remove, in which case no snapshot is created).

    Having few snaps installed in the system, this test checks that
    `snap save` command shows the created snapshots for the snaps. Then it runs
    `snap check-snapshot` to check the snapshot created for the snaps are
    valid. Finally, it verifies that the snapshots can be exported and restored
    (the data is restored properly) and removed by running `snap forget` command.

environment:
    # tar invoked when creating snapshots triggers OOM
    SNAPD_NO_MEMORY_LIMIT: 1

prepare: |
    snap install test-snapd-sh
    snap install --edge test-snapd-just-edge
    snap install --edge test-snapd-tools

restore: |
    rm -f /var/snap/test-snapd-tools/common/huge

debug: |
    snap list || true
    snap info core || true
    snap saved || true

execute: |
    # use the snaps, so they create the dirs:
    test-snapd-sh.sh -c 'true'
    test-snapd-just-edge.snap-name >/dev/null
    # drop in canaries:
    for snap in test-snapd-sh test-snapd-just-edge; do
       echo "hello versioned $snap"  > ~/snap/$snap/current/canary.txt
       echo "hello common $snap" > ~/snap/$snap/common/canary.txt
    done
    # create snapshot, grab its ID
    SET_ID=$( snap save test-snapd-sh test-snapd-just-edge | cut -d\  -f1 | tail -n1 )

    # check it includes both snaps
    snap saved | MATCH test-snapd-sh
    snap saved | MATCH test-snapd-just-edge
    snap saved --id="$SET_ID" | grep test-snapd-sh
    snap saved --id="$SET_ID" | grep test-snapd-just-edge
    # and is valid
    snap check-snapshot "$SET_ID"

    # and is exportable
    snap export-snapshot "$SET_ID" "${SET_ID}_export.snapshot"
    test -e "${SET_ID}_export.snapshot"
    test "$( tar -tvf "${SET_ID}_export.snapshot" | wc -l )" = "4"
    # do it again and ensure it has the files have the same size
    snap export-snapshot "$SET_ID" "${SET_ID}_export-2.snapshot"
    test "$( tar -tvf "${SET_ID}_export-2.snapshot" | wc -l )" = "4"
    s1="$(stat -c%s "${SET_ID}_export.snapshot" )"
    s2="$(stat -c%s "${SET_ID}_export-2.snapshot" )"
    test "$s1" = "$s2"
    echo "and the same files are snapshoted"
    f1="$(tar -tf "${SET_ID}_export.snapshot" )"
    f2="$(tar -tf "${SET_ID}_export-2.snapshot" )"
    test "$f1" = "$f2"

    # remove the canaries
    rm ~/snap/*/{current,common}/canary.txt

    # restore one of them
    snap restore "$SET_ID" test-snapd-sh
    test -e ~/snap/test-snapd-sh/current/canary.txt
    test -e ~/snap/test-snapd-sh/common/canary.txt
    # it didn't restore the other one
    test ! -e ~/snap/test-snapd-just-edge/current/canary.txt
    test ! -e ~/snap/test-snapd-just-edge/common/canary.txt

    # restore the other
    snap restore "$SET_ID" test-snapd-just-edge

    # now check everything's as we expect
    for snap in test-snapd-sh test-snapd-just-edge; do
        test "$( cat ~/snap/$snap/current/canary.txt )" = "hello versioned $snap"
        test "$( cat ~/snap/$snap/common/canary.txt )" = "hello common $snap"
    done

    # check removal works
    snap forget "$SET_ID"
    snap saved --id="$SET_ID" | grep "No snapshots found"

    echo "Check import fails if snapshot contains invalid filenames"
    # trusty (14.04) relpath does not support --relative-to
    if ! os.query is-trusty; then
        fname=$(mktemp /tmp/1_XXXX)
        relpath=$(realpath  --relative-to="$(pwd)" "$fname")
        cp "${SET_ID}_export.snapshot" "${SET_ID}_export.snapshot.invalid"
        tar --update -f "${SET_ID}_export.snapshot.invalid" -P "$relpath"
        # remove the file so we can check that the file does not get created when
        # snapshot import fails
        rm "$fname"
        snap import-snapshot "${SET_ID}_export.snapshot.invalid" && exit 1
        test ! -e "$fname"
        rm "${SET_ID}_export.snapshot.invalid"
    fi

    echo "Check that import works"
    RESTORE_ID=$( snap import-snapshot "${SET_ID}_export.snapshot" | head -n1 | cut -f2 -d'#' )
    echo "And is valid"
    snap check-snapshot "$RESTORE_ID"

    NUM_SAVED=$(snap saved | wc -l)

    echo "Import again doesn't create a duplicated snapshot"
    IMPORTED_ID=$( snap import-snapshot "${SET_ID}_export-2.snapshot" | head -n1 | cut -f2 -d'#' )
    [[ "$RESTORE_ID" == "$IMPORTED_ID" ]]
    [[ "$NUM_SAVED" == $(snap saved | wc -l) ]]

    snap forget "$RESTORE_ID"
    echo "Import re-creates snapshots directory if misssing"
    # precondition check
    test -d /var/lib/snapd/snapshots
    rm -rf /var/lib/snapd/snapshots
    RESTORE_ID=$( snap import-snapshot "${SET_ID}_export.snapshot" | head -n1 | cut -f2 -d'#' )

    # remove the exports and import
    rm "${SET_ID}"_export*.snapshot
    snap forget "$RESTORE_ID"

    # check automatic snapshot can be disabled
    snap set core snapshots.automatic.retention=no
    snap remove test-snapd-sh > remove-snapshot-disabled.out
    MATCH 'test-snapd-sh removed$' < remove-snapshot-disabled.out
    if snap saved | MATCH "test-snapd-sh"; then
        echo "did not expect a snapshot for test-snapd-sh"
        exit 1
    fi

    # re-enable snapshots, check automatic snapshot is created on snap remove
    snap install test-snapd-sh
    snap set core snapshots.automatic.retention=30h
    snap remove test-snapd-sh > remove-snapshot-auto.out
    MATCH 'test-snapd-sh removed \(snap data snapshot saved\)$' < remove-snapshot-auto.out
    snap saved test-snapd-sh | MATCH "auto"
    SET_ID=$( snap saved test-snapd-sh | cut -d\  -f1 | tail -n1 )
    snap forget "$SET_ID"

    # removing with --purge doesn't create automatic snapshot
    snap set core snapshots.automatic.retention=30h
    snap install test-snapd-sh
    snap remove --purge test-snapd-sh > remove-purge.out
    MATCH 'test-snapd-sh removed$' < remove-purge.out
    if snap saved test-snapd-sh | MATCH "auto" ; then
        echo "automatic snapshot is not expected"
        exit 1
    fi

    # check validation of the automatic snapshot expiration range
    (snap set core snapshots.automatic.retention=1s 2>&1 || true) | MATCH "must be"

    # check that expiration can be read back
    snap get core snapshots.automatic.retention | MATCH "30h"
    snap get core -d snapshots.automatic | MATCH '"retention"'

    # check memory usage for larger snap snapshot data exporting
    mkdir -p /var/snap/test-snapd-tools/common/
    dd if=/dev/urandom of=/var/snap/test-snapd-tools/common/huge bs=1M count=100
    # create snapshot, grab its ID
    SET_ID=$( snap save test-snapd-tools | cut -d\  -f1 | tail -n1 )
    snap saved --id="$SET_ID" | grep test-snapd-tools
    snap check-snapshot "$SET_ID"
    # export the large snap and record memory usage
    "$TESTSTOOLS"/memory-observe-do -o memory-kb.txt snap export-snapshot \
        "$SET_ID" "${SET_ID}_export.snapshot"
    test -e "${SET_ID}_export.snapshot"
    # Ensure our snapshot export file is at least 50MB. The data we created
    # is 100MB of randomness so even with amazing compression we should never
    # have a smaller file
    test "$(stat -c%s "${SET_ID}_export.snapshot")" -gt 50000000
    # the io.Copy uses a 32k buffer, so extra memory usage should be limited.
    # The threshold in this test is set to about 40MB
    test "$(cat memory-kb.txt)" -lt 40000

    # check that snapshot set id from the filename has authority
    snap install test-snapd-sh
    snap save test-snapd-sh
    SNAPSHOT_FILE=$(ls /var/lib/snapd/snapshots/*test-snapd-sh*.zip)
    NEWSNAPSHOT_FILE=$(echo "$SNAPSHOT_FILE" | sed -e's/[1-9]\+_/123_/')
    # rename the snapshot file to force a new set id.
    mv "$SNAPSHOT_FILE" "$NEWSNAPSHOT_FILE"
    snap saved | MATCH "123 +test-snapd-sh"
    # make sure there is just one such snapshot
    [[ $(snap saved | grep -c test-snapd-sh) == "1" ]]
    snap saved --id=123 | MATCH "123 .+test-snapd-sh"
    snap restore 123 test-snapd-sh

    # snap configuration should work after restoring snapshots that didn't
    # have config (LP #1917870).
    "$TESTSTOOLS"/snaps-state install-local test-snap
    SET_ID=$( snap save test-snap | cut -d\  -f1 | tail -n1 )
    snap restore "$SET_ID"
    snap set test-snap foo=baz
    snap get test-snap foo | MATCH "baz"
