Execution guide to snapshot, verify, and restore an Oracle single-instance database on Google Cloud NetApp Volumes (GCNV) in ONTAP mode, using NetApp Consistency Groups. Standalone scripts live in scripts/ — only bash, curl, jq, gcloud, and SQL*Plus are required.
Use this sample scripts to test in non-production environment first for adoption.
- Scope: Oracle single-instance (CDB/PDB or non-CDB) on ASM (
+DATA/+FRA) over iSCSI or NVMe/TCP, Linux hosts. - Out of scope: RAC / Data Guard failover orchestration, GCNV DEFAULT-mode pools (CGs need ONTAP mode), RMAN/streaming backups.
Database
- ARCHIVELOG mode enabled —
SELECT log_mode FROM v$database;→ARCHIVELOG. - TR-4700 layout: datafiles + tempfiles only on
+DATA; control files, online redo, archive, flashback, SPFILE on+FRA. Both disk groups CG-backed. (Existing non-compliant DB? See Appendix A.) - SYSDBA via OS authentication on the host.
Host / OS
-
bash,curl,jqinstalled. -
gcloudinstalled and ADC configured (gcloud auth application-default login). - SQL*Plus on
PATHfor the SID (resolved from/etc/oratab).
GCNV / ONTAP
- Flex pool in ONTAP mode.
- You know
project,location(pool zone),pool, the SVM, and the volume names for+DATAand+FRA.
Copy the bundle to a dir oracle owns (e.g. /opt/oracle/toolkit, chown -R oracle:oinstall). Copy whole files (scp/rsync) — do not paste-edit on the host. One identity must satisfy all three privilege levels:
| Operation | Needs | Who |
|---|---|---|
Snapshot (30/32/35) |
SYSDBA + ONTAP token | Oracle owner / dba-group user with gcloud token |
Restore (50) / proof (91) |
Grid/ASM admin (crsctl/srvctl, OLR, ADR) + SYSDBA + ONTAP token |
Grid/Oracle software owner only |
Verify (20/40) |
ONTAP token only | any user with a valid token |
A
dba-group user can run SQL as SYSDBA but cannot manage Oracle Restart — a restore run as that user fails atcrsctl stop has(PROCL-00042/DIA-49802) aftershutdown abort, leaving a half-reverted DB.50/91callassert_storage_privilegesand abort before any change. Always run50/91asoracle.
Sanity check (as oracle):
sudo -iu oracle
gcloud auth print-access-token >/dev/null && echo "token OK" # ONTAP
sqlplus -s / as sysdba <<< 'select open_mode, log_mode from v$database;' # SYSDBA
/u01/app/26.0.0/grid/bin/crsctl check has # Grid/ASM (no PROCL-00042)| Oracle component | ASM disk group | ONTAP volume (example) | Class | In CG? |
|---|---|---|---|---|
| Datafiles + tempfiles ONLY | +DATA |
orasi_primary_data1 |
DATA | ✅ |
| Control files, online/archived redo, flashback, SPFILE | +FRA |
orasi_primary_fra1 |
LOG | ✅ |
Oracle binaries (/u01) |
n/a (XFS) | u01 |
— | ❌ |
Golden rule (TR-4700 isolation): datafiles live alone on the DATA volume; control/redo/archive/SPFILE on the LOG volume. This is what makes a datafile-only SnapRestore + roll-forward possible. The CG spans both volumes for whole-DB atomic snapshots. The scripts enforce this and abort on violation.
The snapshot model you choose dictates the restore scope you can use later.
| Model | Script | What it captures | Restore scope(s) | Use when |
|---|---|---|---|---|
| Whole-CG (app-consistent) | 30 |
one atomic CG snapshot (DATA + LOG) | whole |
full point-in-time: gold image, clone, DR |
| Volume-split (app-consistent) | 32 |
separate per-volume DATA & LOG snapshots | datafiles (roll-forward) · split (point-in-time) |
datafile corruption / logical error with intact logs |
| Crash-consistent | 35 |
one CG snapshot, no Oracle quiesce | whole |
non-prod clones / DR drills |
50-restore-from-snapshot.sh [name|latest] [scope]
| Scope | Reverts | Recovery | Data loss | From |
|---|---|---|---|---|
whole |
CG (DATA + LOG) | media recovery to snapshot point | back to snapshot | 30/35 |
datafiles |
DATA volume only | roll forward on current control/redo/archive | ~0 (normal conditions *) | 32 |
split |
DATA + LOG volume snaps | media recovery to snapshot point | back to snapshot | 32 |
* RPO under normal conditions is effectively zero.
datafilesreverts only the datafile volume and rolls forward using the current archive + online redo (still live on the intact LOG volume) to the last commit — no committed transaction lost, independent of snapshot frequency. Cadence only bounds RPO in the degraded case where the logs themselves are lost (then you fall back to the latest snapshot).Application-consistent vs crash-consistent: app-consistent (
BEGIN BACKUP→ snapshot →END BACKUP) requires ARCHIVELOG and is the default for any production-SLA image; crash-consistent skips the quiesce and is for non-prod/drills.Non-ASM (iSCSI/NVMe + filesystem): set
ORACLE_STORAGE=fsand list mount points inORACLE_DATA_PATHS/ORACLE_LOG_PATHS. Restore unmounts → SnapRestores →fsck→ remounts → recovers. Block + local FS only (no NFS).
Every ONTAP REST call is proxied through the Google control plane and authenticated with ADC — no ONTAP hostname/user/password:
https://netapp.googleapis.com/v1/projects/PROJECT/locations/LOCATION/storagePools/POOL/ontap/api/<PATH>
Authorization: Bearer $(gcloud auth print-access-token)
Proxy conventions used by the scripts: field selection uses ontap_fields= (not fields=); request/response bodies are wrapped in { "body": { ... } }, so list records live under .body.records.
one-time ┌──────────────────────────────────────────────────────────┐
│ 00-env.sh set GCP/SVM/CG identifiers (edit once) │
│ 10-ensure-cg.sh create the Consistency Group (DATA+FRA) │
└──────────────────────────────────────────────────────────┘
│
backup ┌──────────────────────────────────────────────────────────┐
CHOOSE │ 30 whole-CG (atomic) │ 32 volume-split │ 35 crash │
A MODEL └──────────────────────────────────────────────────────────┘
│
┌──────────────────────────────────────────────────────────┐
verify │ 20-verify-cg.sh / 40-list-snapshots.sh │
└──────────────────────────────────────────────────────────┘
│
recover ┌──────────────────────────────────────────────────────────┐
SCOPE = │ 50-restore-from-snapshot.sh [name|latest] [scope] │
MODEL │ 30/35 → whole 32 → datafiles | split │
└──────────────────────────────────────────────────────────┘
Edit the identifiers at the top of scripts/00-env.sh once; every other script sources it. This is the only file you normally edit.
# GCNV (ONTAP-mode) target
export GCNV_PROJECT_ID="..." # GCP project owning the pool
export GCNV_LOCATION="us-west1-a" # storage pool zone
export GCNV_STORAGE_POOL="..." # Flex pool (ONTAP mode)
export ONTAP_SVM="..." # SVM that owns the volumes
# Consistency Group + volume mapping (datafiles isolated on DATA)
export ONTAP_CG_NAME="cg_orcl_single"
ONTAP_DATA_VOLUMES=( "orasi_primary_data1" ) # datafiles ONLY
ONTAP_LOG_VOLUMES=( "orasi_primary_fra1" ) # control/redo/arch/spfile
# Oracle (used by quiesce / recover)
export ORACLE_SID="ORCL"
export DB_UNIQUE_NAME="ORCL"
export GRID_HOME="/u01/app/26.0.0/grid"
export ASM_DISKGROUPS="DATA FRA"Then confirm connectivity:
gcloud auth application-default login
chmod +x ./*.sh
source ./00-env.sh
ontap_get "/cluster?ontap_fields=name,version" | jq . # must return cluster JSONIdempotent — a no-op once the CG exists.
./10-ensure-consistency-group.shExpect: Consistency group 'cg_orcl_single' is ready (uuid=…) (or already exists).
Read-only health check (membership, SVM, latest snapshot). Add VERIFY_LAYOUT=true to also assert TR-4700 compliance.
./20-verify-consistency-group.sh
VERIFY_LAYOUT=true ./20-verify-consistency-group.sh # also checks datafile isolationExpect: Membership check : OK and (with the flag) Layout compliant: OK.
Pick the model that matches the recovery you want (see §2). The restore scope must match the snapshot you took — a 30 snapshot restores whole only; only a 32 snapshot unlocks datafiles/split.
Option A — Whole-CG (30): one atomic snapshot of DATA + FRA. trap-protected so the DB is never left in backup mode.
SNAP_ENV=prod SNAP_DB_NAME=ORCL SNAP_CHANGE_ID=chg1234 ./30-app-consistent-snapshot.sh
# → restore later with: ./50-restore-from-snapshot.sh latest wholeSequence: BEGIN BACKUP → CG snapshot (consistency_type=application) → END BACKUP → ARCHIVE LOG CURRENT + capture SCN.
Option B — Volume-split (32): two per-volume snapshots for the datafile-only roll-forward (zero RPO under normal conditions). Requires the TR-4700 layout.
SNAP_ENV=prod SNAP_DB_NAME=ORCL SNAP_CHANGE_ID=chg1234 ./32-volume-split-snapshot.sh
# → restore later with: ./50-restore-from-snapshot.sh latest datafiles (roll-forward, ~0 RPO)
# or: ./50-restore-from-snapshot.sh latest split (point-in-time)Sequence (END BACKUP precedes ARCHIVE LOG CURRENT, so the LOG snapshot is independently recoverable):
1. ALTER DATABASE BEGIN BACKUP 4. ALTER SYSTEM ARCHIVE LOG CURRENT
2. snapshot DATA volume(s) 5. BACKUP CONTROLFILE (binary+trace) → LOG volume
3. ALTER DATABASE END BACKUP 6. snapshot LOG volume(s)
Produces one <ENV>_<DB>_split_<timestamp>_<CHANGE_ID> name shared by both volumes (shown per-volume in 40, not as a CG snapshot).
Option C — Crash-consistent (35): non-prod / drills, no quiesce.
SNAP_ENV=test ./35-crash-consistent-snapshot.sh
# → restore later with: ./50-restore-from-snapshot.sh latest wholeNaming convention: <ENV>_<DB>_<type>_<YYYYMMDD_HHMMSS>_<CHANGE_ID> (e.g. prod_ORCL_appcons_20260619_142530_chg1234) — self-describing and audit-friendly. Keep the backup window to seconds (only the snapshot REST call sits between BEGIN/END BACKUP).
./40-list-snapshots.shShows CG (whole) snapshots and per-volume snapshots. Confirm DB state any time:
SELECT open_mode, log_mode FROM v$database; -- READ WRITE, ARCHIVELOG
SELECT COUNT(*) FROM v$backup WHERE status='ACTIVE'; -- 0 (never left in backup mode)Destructive — test in non-production first. Run as oracle. Match the scope to the snapshot (§2).
# Whole point-in-time (from a 30/35 snapshot):
./50-restore-from-snapshot.sh latest whole
./50-restore-from-snapshot.sh prod_ORCL_appcons_20260619_142530_chg1234 whole
# Datafile-only roll-forward (~0 RPO) from a 32 snapshot, with RMAN block check:
VALIDATE=true ./50-restore-from-snapshot.sh latest datafiles
# Volume-level point-in-time (both volumes) from a 32 snapshot:
./50-restore-from-snapshot.sh latest splitWhat it does: pre-flight guard (abort if a DB file is outside the CG/layout) → quiesce (shutdown abort → srvctl stop → crsctl stop has -f) → revert (CG restore_to, or volume SnapRestore) → restart storage (crsctl start has, wait for disk groups) → recover & open (MOUNT → END BACKUP → RECOVER AUTOMATIC DATABASE → OPEN + PDBs) → validate (v$recover_file=0, no backup-mode files, and with VALIDATE=true zero corrupt blocks via RMAN).
Success line:
SUCCESS: database restored (scope=…) from '<snapshot>', recovered, and OPEN.
Expected during a
datafilesroll-forward: because only the DATA volume is reverted,STARTUP MOUNT/END BACKUPreportORA-01208/ORA-01235for the reverted datafiles. This is normal —RECOVER AUTOMATIC DATABASErolls them forward to the current SCN and opens (no resetlogs).On SCNs: the printed
REFERENCE_SCNis for the audit trail only; the global SCN always advances past the snapshot during recovery, so a pre/post comparison is meaningless. The deterministic gates arev$recover_file=0, no backup-mode files, and (withVALIDATE=true) zero corrupt blocks.
⚠️ Validation / test harness — NOT an operational step. Production recovery uses50. Run91once on a throwaway/test DB to prove the datafile-only roll-forward works in your environment (re-run after a patch/upgrade/layout change). It must never run against production, and it adds no extra DB bounce — internally it just calls50 … datafilesand wraps it with assertions.
./91-prove-datafile-rpo.sh --yes # VALIDATE=true by defaultIt seeds a baseline row, takes a 32 snapshot, commits post-snapshot "RPO-window" rows, runs a datafiles restore, then asserts and ends in OVERALL: PASS:
- [RPO] every post-snapshot committed row survived (rolled forward);
- [CONSISTENCY] DB
OPEN,v$recover_file=0, zero corrupt blocks (RMAN); - [SELECTIVE] the LOG volume(s) were not reverted.
Cleanup afterward: DROP TABLE system.rpo_proof PURGE;
The split model requires datafiles isolated on +DATA. A default DBCA install multiplexes control/redo across +DATA++FRA and puts the SPFILE on +DATA — non-compliant. New databases built by this toolkit are already compliant.
Automated (recommended) — idempotent; moves redo online, control files + SPFILE in one srvctl bounce (no OPEN RESETLOGS), takes a safety CG snapshot, asserts compliance:
ansible-playbook -i inventories/single playbooks/19_relocate_db_files_to_fra.yml --check # dry run
ansible-playbook -i inventories/single playbooks/19_relocate_db_files_to_fra.yml -e relocate_confirm=trueManual equivalent (online where possible):
-- 1) Add a +FRA redo member per group, switch/checkpoint until INACTIVE, drop the +DATA member.
ALTER DATABASE ADD LOGFILE MEMBER '+FRA' TO GROUP 1; -- repeat per group
ALTER SYSTEM SWITCH LOGFILE; ALTER SYSTEM CHECKPOINT;
ALTER DATABASE DROP LOGFILE MEMBER '+DATA/<db>/onlinelog/...';
-- 2) Control file → +FRA (needs a bounce): set control_files to +FRA, copy via ASM, restart.
ALTER SYSTEM SET control_files='+FRA/<db>/controlfile/current.ctl' SCOPE=SPFILE;
-- 3) SPFILE → +FRA and re-point Oracle Restart:
CREATE SPFILE='+FRA/<db_unique>/spfile<SID>.ora' FROM SPFILE;
!srvctl modify database -d <db_unique> -spfile +FRA/<db_unique>/spfile<SID>.oraVerify any time: VERIFY_LAYOUT=true ./20-verify-consistency-group.sh. Snapshot/restore scripts run this automatically and abort on violation (override PREFLIGHT_SKIP=true at your own risk).
| Symptom / pitfall | Cause / fix |
|---|---|
ORA-01031/ORA-01017 at SYSDBA |
Not the Oracle owner / not in dba group. Run as oracle. |
PROCL-00042 / DIA-49802 on restore |
Restore run as a non-Grid user. Run 50/91 as the Grid/Oracle owner. |
ORA-01208/ORA-01235 during datafiles restore |
Expected — recovery rolls the reverted datafiles forward. |
ORA-00204/stale ASM after revert |
Storage reverted under a live ASM. The script stops HAS first — don't skip it. |
DB file outside +DATA/+FRA |
Pre-flight aborts. Relocate (Appendix A) so all DB state is CG-backed. |
| Snapshots at slightly different times | Always go through the CG (30) or same-named volume set (32) — never ad-hoc per-volume. |
| Interactive auth prompt in scheduled runs | Pre-stage ADC via a service account with the NetApp Volumes admin role. |
| Not in ARCHIVELOG | Required for app-consistent recovery. Enable before relying on these snapshots. |
| Script | Purpose |
|---|---|
00-env.sh |
Shared config + ONTAP REST / SQL helpers (edit once) |
10-ensure-consistency-group.sh |
Create the CG over +DATA++FRA (idempotent) |
20-verify-consistency-group.sh |
Verify CG membership + latest snapshot (VERIFY_LAYOUT=true adds layout check) |
30-app-consistent-snapshot.sh |
Whole-CG application-consistent snapshot |
32-volume-split-snapshot.sh |
Volume-split 6-step snapshot (DATA + LOG separately) |
35-crash-consistent-snapshot.sh |
Crash-consistent snapshot (non-prod / drills) |
40-list-snapshots.sh |
List CG and per-volume snapshots |
50-restore-from-snapshot.sh |
Restore + recover + validate (whole/datafiles/split); VALIDATE=true adds RMAN block check |
91-prove-datafile-rpo.sh |
Test/validation only — proof harness for datafile-only RPO (PASS/FAIL). Production recovery uses 50. |
As with any automation that interacts with database and storage infrastructure, validate these scripts in a non-production environment and ensure they align with your organization's backup, recovery, and operational requirements before production use. This product is licensed under the Apache 2 license. This is not an officially supported NetApp project