diff --git a/labs/GSP1312/HASHNODE.md b/labs/GSP1312/HASHNODE.md new file mode 100644 index 0000000..c6bdf44 --- /dev/null +++ b/labs/GSP1312/HASHNODE.md @@ -0,0 +1,174 @@ +# Protect your Google Cloud NetApp Volumes data with snapshots - GSP1312 + +## Overview + +A NetApp Volumes snapshot preserves a point-in-time view of a filesystem. This lab uses a preprovisioned NFS volume and Linux VM to create a manual snapshot, restore an individual file, configure recurring snapshots, and revert the entire volume. Reversion is destructive to changes made after the snapshot; use **only the temporary lab resources**. + +### What you'll do + +- Mount the supplied NFS volume on vm1. +- Create sample files and a snapshot named snapshot1. +- Restore a deleted file from the snapshot directory. +- Configure hourly, daily, weekly, and monthly snapshot retention. +- Revert the lab volume to snapshot1 and verify recovery. + +## Setup and requirements + +### Before you click the Start Lab button + +Open the [official NetApp snapshot lab](https://www.skills.google/focuses/124381?parent=catalog) in a private browser. Use **only the temporary credentials provided by the lab**; never paste a student password or personal project ID into public code. The lab provisions vm1 and volume1. Do not create duplicate resources or run the destructive revert on a production volume. + +**Version note:** The public Google Skills page has inconsistent legacy checkpoint labels and sometimes displays a placeholder lab code. The five Task headings and procedures are consistent with the official indexed GSP1312 page, but checkpoint counts/labels must be confirmed in the active lab panel. No grader score is claimed. + +### Activate Cloud Shell + +Open Cloud Shell and verify the project, VM zone, and NetApp volume location: + +```bash +gcloud config get-value project +gcloud compute instances list --filter='name=vm1' --format='table(name,zone,status)' +gcloud netapp volumes list --format='table(name,state,capacityGib)' +``` + +The volume's resource path includes its location. In the console, open NetApp Volumes → Volumes → volume1 → Mount instructions to obtain the unique NFS export IP and share path. Never invent an IP or region. + +## Task 1. Mount the volume + +Connect to vm1 using the Compute Engine SSH button or gcloud compute ssh with the detected zone. Install the NFS client utilities, create a mount point, and use the export path shown by your lab: + +```bash +sudo apt-get update -qq +sudo apt-get install -y nfs-common +sudo mkdir -p /share1 +sudo mount -t nfs -o rw,hard,rsize=65536,wsize=65536,vers=3,tcp ':/share1' /share1 +df -h /share1 +``` + +If the share is already mounted, do not mount it twice. The volume should initially show approximately 1 TiB. + +### Test completed task + +Click **Check my progress** for the mount objective when that checkpoint is available in your active lab. + +## Task 2. Create a manual snapshot + +On vm1, create two directories and two small files: + +```bash +sudo mkdir -p /share1/folder1 /share1/folder2 +printf 'important\n' | sudo tee /share1/file1.txt +printf 'top secret\n' | sudo tee /share1/file2.txt +sudo ls -la /share1 +``` + +In the NetApp Volumes console, select volume1 → Create snapshot, name it snapshot1, and wait until it is ready. Alternatively, use the verified CLI commands with the volume location from the earlier discovery: + +```bash +gcloud netapp volumes snapshots create snapshot1 --volume=volume1 --location='' +gcloud netapp volumes snapshots describe snapshot1 --volume=volume1 --location='' --format='value(state)' +``` + +Confirm the snapshot appears in the volume's Snapshots tab and reaches READY. Do not delete an existing snapshot of the same name merely to rerun the lab. + +## Task 3. Restore a file from a snapshot + +In volume1 → Edit → Snapshot configuration, ensure the snapshot directory is visible. The CLI equivalent is: + +```bash +gcloud netapp volumes update volume1 --location='' --snapshot-directory=true +``` + +On vm1, remove only the first sample file, inspect the snapshot directory, and restore it: + +```bash +sudo rm -f /share1/file1.txt +sudo ls -la /share1 +sudo ls -la /share1/.snapshot +sudo ls -la /share1/.snapshot/snapshot1 +sudo cp /share1/.snapshot/snapshot1/file1.txt /share1/file1.txt +sudo ls -la /share1 +``` + +The restored file should contain the original sample text. This is a file-level recovery and does not revert the whole volume. + +## Task 4. Schedule the snapshots of a volume + +Open volume1 → Edit → Snapshot configuration. Enable scheduled snapshots and enter the following settings: + +| Schedule | Retain | Time | +|---|---:|---| +| Hourly | 24 | Minute 0 | +| Daily | 7 | 00:00 | +| Weekly | 8 | Sunday 00:00 | +| Monthly | 3 | Day 1 at 00:00 | + +Save the volume. For CLI automation, the script uses the documented NetApp Volumes REST PATCH method with updateMask=snapshotPolicy, including enabled=true and all four schedules. It uses the current lab account's short-lived OAuth token and does not store credentials in the repository. + +```bash +gcloud netapp volumes describe volume1 --location='' --format='yaml(snapshotPolicy)' +``` + +Confirm the policy is enabled and the retention counts match the table. + +### Test completed task + +Click **Check my progress** for the scheduled snapshot/volume update objective when available. The public checkpoint panel has inconsistent labels; use the current lab UI as the source of truth. + +## Task 5. Restore the entire volume from a snapshot + +**Warning:** Reverting replaces the current volume contents with snapshot1's point-in-time state. This cannot be undone for changes made after that snapshot. Use only volume1 in the temporary lab. + +On vm1, delete the four sample items and verify they are absent: + +```bash +sudo rm -f /share1/file1.txt /share1/file2.txt +sudo rm -rf /share1/folder1 /share1/folder2 +sudo ls -la /share1 +``` + +In NetApp Volumes → volume1 → Snapshots, locate snapshot1 and select Revert. Review the warning and confirm by entering volume1. The CLI alternative is: + +```bash +gcloud netapp volumes revert volume1 --location='' --snapshot=snapshot1 +``` + +After the operation completes, inspect the mounted share: + +```bash +sudo ls -la /share1 +sudo test -f /share1/file1.txt +sudo test -f /share1/file2.txt +``` + +The original files and directories should return. Verify the relevant completion objective in the active lab panel; no grader result has been verified. + +--- + +## Solution of Lab + +### Quick + +After the Draft PR is merged into main, run exactly these two commands in an authorized lab Cloud Shell. The URL intentionally targets main and is unavailable until merge. + +```bash +curl -LO https://raw.githubusercontent.com/ePlus-DEV/storage/refs/heads/main/labs/GSP1312/lab.sh +source lab.sh +``` + +The script discovers the project, VM zone, volume location, and NFS export; creates snapshot1, restores a file, configures the four schedules, and reverts the temporary volume. It performs bounded retries and enables FORCE_COLOR through tee while respecting NO_COLOR. The revert step is intentionally destructive to the lab volume's post-snapshot changes. + +### Manual + +Chưa tìm thấy video YouTube hướng dẫn thủ công đã xác minh cho lab này. + +Follow all five manual Task sections above to complete the lab without a video. + +## Sources and verification + +- [Google Skills official NetApp snapshot lab](https://www.skills.google/focuses/124381?parent=catalog) +- [NetApp Volumes snapshot REST resource and states](https://docs.cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.volumes.snapshots) +- [NetApp Volumes snapshot policy schema](https://docs.cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.volumes) +- [NetApp Volumes PATCH API](https://docs.cloud.google.com/netapp/volumes/docs/reference/rest/v1/projects.locations.volumes/patch) +- [NetApp Volumes revert command](https://docs.cloud.google.com/sdk/gcloud/reference/netapp/volumes/revert) + +**Status:** static validated only; runtime/grader unverified. The public page's legacy checkpoint labels need active-lab confirmation. No unverified screenshots or YouTube links are embedded. This is a Hashnode-ready draft, not a published article. diff --git a/labs/GSP1312/lab.sh b/labs/GSP1312/lab.sh new file mode 100644 index 0000000..c190e37 --- /dev/null +++ b/labs/GSP1312/lab.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Copyright (c) 2026 ePlus.DEV | Unlicense +set -Eeuo pipefail +if [[ -v NO_COLOR ]]; then CYAN='' GREEN='' YELLOW='' RESET='' +elif [[ -t 1 || "${FORCE_COLOR:-0}" == 1 ]]; then + CYAN=$'\033[1;36m'; GREEN=$'\033[1;32m'; YELLOW=$'\033[1;33m'; RESET=$'\033[0m' +else CYAN='' GREEN='' YELLOW='' RESET=''; fi +log() { printf '%s%s%s\n' "$CYAN" "$*" "$RESET"; } +ok() { printf '%s%s%s\n' "$GREEN" "$*" "$RESET"; } +warn() { printf '%s%s%s\n' "$YELLOW" "$*" "$RESET"; } +trap 'warn "Failed at line $LINENO (exit $?)."' ERR +printf '%s\n' "${CYAN}ePlus.DEV – Starting Execution${RESET}" 'GSP1312 — NetApp snapshots (static validated only; runtime/grader unverified)' +for cmd in gcloud curl grep head; do command -v "$cmd" >/dev/null || { warn "Missing $cmd"; exit 1; }; done +PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project 2>/dev/null)}" +[[ -n "$PROJECT_ID" && "$PROJECT_ID" != '(unset)' ]] || { warn 'Choose an authorized lab project'; exit 1; } +VM=vm1 +VOLUME=volume1 +SNAPSHOT=snapshot1 +ZONE="${ZONE:-$(gcloud compute instances list --project="$PROJECT_ID" --filter="name=$VM" --format='value(zone.basename())' | head -n 1)}" +[[ -n "$ZONE" ]] || { warn 'Preprovisioned vm1 not found'; exit 1; } +VOLUME_PATH="$(gcloud netapp volumes list --project="$PROJECT_ID" --format='value(name)' | grep '/volumes/volume1$' | head -n 1 || true)" +[[ "$VOLUME_PATH" =~ /locations/([^/]+)/volumes/volume1$ ]] || { warn 'Preprovisioned volume1 not found'; exit 1; } +LOCATION="${BASH_REMATCH[1]}" +NFS_EXPORT="${NFS_EXPORT:-$(gcloud netapp volumes describe "$VOLUME" --project="$PROJECT_ID" --location="$LOCATION" --format='value(mountOptions[0].exportFull)' 2>/dev/null || true)}" +if [[ -z "$NFS_EXPORT" && -t 0 ]]; then read -r -p 'NFS export from volume1 Mount instructions (IP:/share): ' NFS_EXPORT; fi +[[ "$NFS_EXPORT" =~ ^[0-9.]+:/[^[:space:]]+$ ]] || { warn 'Set NFS_EXPORT to the verified lab volume export'; exit 1; } +remote() { + local script="$1" attempt + for attempt in 1 2 3; do + if gcloud compute ssh "$VM" --project="$PROJECT_ID" --zone="$ZONE" --quiet --command="bash -lc $(printf '%q' "$script")"; then return 0; fi + warn "SSH retry $attempt/3"; sleep 8 + done + return 1 +} +wait_snapshot() { + local attempt state + for attempt in {1..24}; do + state="$(gcloud netapp volumes snapshots describe "$SNAPSHOT" --volume="$VOLUME" --location="$LOCATION" --project="$PROJECT_ID" --format='value(state)' 2>/dev/null || true)" + [[ "$state" == READY ]] && return 0 + sleep 10 + done + warn "Snapshot not READY (last: $state)"; return 1 +} +log '== Task 1/5 — Mount volume1 on preprovisioned vm1 ==' +remote "set -e; command -v mount.nfs >/dev/null || { sudo apt-get update -qq; sudo apt-get install -y -qq nfs-common; }; sudo mkdir -p /share1; if ! mountpoint -q /share1; then sudo mount -t nfs -o rw,hard,rsize=65536,wsize=65536,vers=3,tcp '$NFS_EXPORT' /share1; fi; df -h /share1" +ok 'NFS mount verified' +log '== Task 2/5 — Create files and manual snapshot1 ==' +remote "set -e; sudo mkdir -p /share1/folder1 /share1/folder2; printf 'important\\n' | sudo tee /share1/file1.txt >/dev/null; printf 'top secret\\n' | sudo tee /share1/file2.txt >/dev/null; sudo ls -la /share1" +if ! gcloud netapp volumes snapshots describe "$SNAPSHOT" --volume="$VOLUME" --location="$LOCATION" --project="$PROJECT_ID" >/dev/null 2>&1; then + gcloud netapp volumes snapshots create "$SNAPSHOT" --volume="$VOLUME" --location="$LOCATION" --project="$PROJECT_ID" --quiet +fi +wait_snapshot +ok 'snapshot1 READY' +log '== Task 3/5 — Restore file1.txt from snapshot directory ==' +gcloud netapp volumes update "$VOLUME" --project="$PROJECT_ID" --location="$LOCATION" --snapshot-directory=true --quiet +remote "set -e; sudo rm -f /share1/file1.txt; sudo test -f /share1/.snapshot/snapshot1/file1.txt; sudo cp /share1/.snapshot/snapshot1/file1.txt /share1/file1.txt; sudo test -f /share1/file1.txt; sudo ls -la /share1" +ok 'file1.txt restored' +log '== Task 4/5 — Configure hourly/daily/weekly/monthly snapshots ==' +# The gcloud update command does not expose snapshotPolicy.enabled; patch only that policy. +POLICY_JSON='{"snapshotPolicy":{"enabled":true,"hourlySchedule":{"snapshotsToKeep":24,"minute":0},"dailySchedule":{"snapshotsToKeep":7,"hour":0,"minute":0},"weeklySchedule":{"snapshotsToKeep":8,"day":"Sunday","hour":0,"minute":0},"monthlySchedule":{"snapshotsToKeep":3,"daysOfMonth":"1","hour":0,"minute":0}}}' +TOKEN="$(gcloud auth print-access-token)" +[[ -n "$TOKEN" ]] || { warn 'No authorized lab access token'; exit 1; } +curl -fsS --retry 3 -X PATCH \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + "https://netapp.googleapis.com/v1/$VOLUME_PATH?updateMask=snapshotPolicy" \ + -d "$POLICY_JSON" >/dev/null +unset TOKEN +for attempt in {1..24}; do + enabled="$(gcloud netapp volumes describe "$VOLUME" --project="$PROJECT_ID" --location="$LOCATION" --format='value(snapshotPolicy.enabled)' 2>/dev/null || true)" + [[ "$enabled" == 'True' || "$enabled" == 'true' ]] && break + sleep 10 +done +[[ "$enabled" == 'True' || "$enabled" == 'true' ]] || { warn 'Snapshot policy not confirmed enabled'; exit 1; } +ok 'Scheduled snapshots enabled' +log '== Task 5/5 — Revert volume1 to snapshot1 ==' +warn 'Revert is destructive to changes made after snapshot1, as required by this temporary lab.' +remote 'sudo rm -f /share1/file1.txt /share1/file2.txt; sudo rm -rf /share1/folder1 /share1/folder2; sudo ls -la /share1' +gcloud netapp volumes revert "$VOLUME" --location="$LOCATION" --project="$PROJECT_ID" --snapshot="$SNAPSHOT" --quiet +remote 'set -e; for i in $(seq 1 24); do if sudo test -f /share1/file1.txt && sudo test -f /share1/file2.txt; then sudo ls -la /share1; exit 0; fi; sleep 10; done; exit 1' +ok 'Reverted files confirmed. Use all four official Check my progress buttons.' +warn 'static validated only; runtime/grader unverified'