Getting there

This commit is contained in:
2025-11-25 10:51:26 -05:00
parent 60fa02c181
commit 5867e7029f
7 changed files with 108 additions and 21 deletions

0
clone_oracle_database.py Normal file → Executable file
View File

0
introspect_schema.py Normal file → Executable file
View File

12
list_oracle_databases.py Normal file → Executable file
View File

@@ -20,6 +20,7 @@ def list_oracle_databases():
nodes {
dbUniqueName
id
isRelic
cluster {
id
name
@@ -34,13 +35,9 @@ def list_oracle_databases():
}
"""
# Variables: exclude relics and replicated databases
# Variables: exclude replicated databases only
variables = {
"filter": [
{
"texts": ["false"],
"field": "IS_RELIC"
},
{
"texts": ["false"],
"field": "IS_REPLICATED"
@@ -71,11 +68,12 @@ def list_oracle_databases():
db['dbUniqueName'],
db['id'],
cluster_name,
host_name
host_name,
'Yes' if db['isRelic'] else 'No'
])
# Print tabulated output
headers = ['Database Name', 'ID', 'Cluster', 'Host']
headers = ['Database Name', 'ID', 'Cluster', 'Host', 'Relic']
print(tabulate(table_data, headers=headers, tablefmt='grid'))
if __name__ == "__main__":

View File

@@ -120,7 +120,7 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description='List Oracle Live Mounts')
parser.add_argument('--name', help='Filter by name (mounted database name)')
parser.add_argument('--cluster-uuid', help='Filter by cluster UUID')
parser.add_argument('--source-db-id', help='Filter by source database ID')
parser.add_argument('--source-db-id', help='Filter by source database ID (optional, defaults to local database)')
parser.add_argument('--org-id', help='Filter by organization ID')
parser.add_argument('--sort-field', choices=['NAME', 'CREATION_DATE', 'STATUS'], help='Sort field')
parser.add_argument('--sort-order', choices=['ASC', 'DESC'], default='ASC', help='Sort order')
@@ -128,6 +128,12 @@ if __name__ == "__main__":
args = parser.parse_args()
if not args.source_db_id and not (args.name or args.cluster_uuid or args.org_id):
auth = RSCAuth()
gql = RSCGraphQL(auth)
args.source_db_id = gql.get_local_database_id()
print(f"INFO: No filters specified, using local database ID: {args.source_db_id}")
try:
list_oracle_live_mounts(
name=args.name,

19
list_db_snapshots.py → list_oracle_snapshots.py Normal file → Executable file
View File

@@ -86,7 +86,7 @@ def find_database_by_name_or_id(identifier):
def format_timestamp(timestamp):
"""Format ISO timestamp to readable format"""
try:
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
dt = datetime.strptime(timestamp.replace('Z', '+0000'), '%Y-%m-%dT%H:%M:%S.%f%z')
return dt.strftime('%Y-%m-%d %H:%M:%S UTC')
except:
return timestamp
@@ -217,7 +217,7 @@ def list_snapshots(identifier):
format_timestamp(snap['date']),
'On-Demand' if snap['isOnDemandSnapshot'] else 'Policy',
snap['slaDomain']['name'] if snap['slaDomain'] else 'None',
'Local + Replica' if snap['snapshotRetentionInfo']['replicationInfos'] else 'Local only'
'Local + Replica' if snap['snapshotRetentionInfo']['replicationInfos'] else 'Local only'
])
headers = ['Snapshot ID', 'Date', 'Type', 'SLA Domain', 'Location Status']
@@ -245,14 +245,23 @@ def list_snapshots(identifier):
sys.exit(1)
def main():
if len(sys.argv) != 2:
print("Usage: python list_db_snapshots.py <database_name_or_id>")
if len(sys.argv) == 2:
identifier = sys.argv[1]
elif len(sys.argv) == 1:
# No argument, find local database
auth = RSCAuth()
gql = RSCGraphQL(auth)
identifier = gql.get_local_database_id()
print(f"INFO: Using local database ID: {identifier}")
else:
print("Usage: python list_db_snapshots.py [<database_name_or_id>]")
print("If no database is specified, uses the local database.")
print("Examples:")
print(" python list_db_snapshots.py SCLONE")
print(" python list_db_snapshots.py 2cb7e201-9da0-53f2-8c69-8fc21f82e0d2")
print(" python list_db_snapshots.py")
sys.exit(1)
identifier = sys.argv[1]
list_snapshots(identifier)
if __name__ == "__main__":

25
mount_oracle_filesonly.py Normal file → Executable file
View File

@@ -5,6 +5,7 @@ import sys
import os
import argparse
import time
import socket
from datetime import datetime
from rsc import RSCAuth, RSCGraphQL
@@ -58,7 +59,6 @@ def find_database_by_name_or_id(identifier):
variables = {
"filter": [
{"texts": [identifier], "field": "NAME_EXACT_MATCH"},
{"texts": ["false"], "field": "IS_RELIC"},
{"texts": ["false"], "field": "IS_REPLICATED"}
]
}
@@ -160,7 +160,7 @@ def get_latest_pit(db_id):
print(f"INFO: Latest PIT (ISO8601): {latest_endtime}")
# Convert to datetime and then to milliseconds since epoch
dt = datetime.fromisoformat(latest_endtime.replace('Z', '+00:00'))
dt = datetime.strptime(latest_endtime.replace('Z', '+0000'), '%Y-%m-%dT%H:%M:%S.%f%z')
unixtime_ms = int(dt.timestamp() * 1000)
print(f"INFO: Latest PIT unixtime (ms): {unixtime_ms}")
@@ -304,26 +304,39 @@ def main():
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python mount_oracle_filesonly.py --mountpath /tmp/mount
python mount_oracle_filesonly.py --mountpath /tmp/mount SHED
python mount_oracle_filesonly.py --targethost target-host --mountpath /tmp/mount SHED
python mount_oracle_filesonly.py --targethost target-host --mountpath /tmp/mount --timestamp "2025-11-25 12:00:00" SHED
"""
)
parser.add_argument("--targethost", required=True,
help="Target host where the files will be mounted")
parser.add_argument("--targethost", required=False,
help="Target host where the files will be mounted (defaults to local hostname)")
parser.add_argument("--mountpath", required=True,
help="Target mount path for the files")
parser.add_argument("--timestamp",
help="Optional timestamp for the recovery point in format 'YYYY-MM-DD HH:MM:SS'")
parser.add_argument("srcdb",
help="Source database name or RSC database ID")
parser.add_argument("srcdb", nargs='?',
help="Source database name or RSC database ID (optional, defaults to local database)")
args = parser.parse_args()
if not args.targethost:
args.targethost = socket.gethostname()
print(f"INFO: No target host specified, defaulting to local hostname: {args.targethost}")
try:
# Find the source database
if args.srcdb:
print(f"INFO: Finding source database: {args.srcdb}")
db = find_database_by_name_or_id(args.srcdb)
else:
print("INFO: No source database specified, finding local database")
auth = RSCAuth()
gql = RSCGraphQL(auth)
db_id = gql.get_local_database_id()
db = find_database_by_name_or_id(db_id)
print(f"INFO: Found database: {db['dbUniqueName']} (ID: {db['id']})")
print(f"INFO: Cluster: {db['cluster']['name']} (ID: {db['cluster']['id']})")

61
rsc.py
View File

@@ -2,6 +2,7 @@ import json
import os
import time
import requests
import socket
class RSCAuth:
def __init__(self, config_file='rsc.json'):
@@ -102,6 +103,66 @@ class RSCGraphQL:
return data
def get_local_database_id(self):
"""Get the ID of the local database on this host, preferring one protected by SLA"""
hostname = socket.gethostname()
query = """
query OracleDatabases($filter: [Filter!]) {
oracleDatabases(filter: $filter) {
nodes {
id
dbUniqueName
isRelic
effectiveSlaDomain {
id
name
}
cluster {
id
name
}
logicalPath {
fid
name
objectType
}
}
}
}
"""
variables = {
"filter": [
{"texts": ["false"], "field": "IS_REPLICATED"}
]
}
response = self.query(query, variables)
all_dbs = response['data']['oracleDatabases']['nodes']
# Filter databases on this host
dbs = [db for db in all_dbs if db['logicalPath'] and db['logicalPath'][0]['name'] == hostname]
if not dbs:
raise ValueError(f"No databases found on host {hostname}")
# Filter databases with SLA protection
protected_dbs = [db for db in dbs if db.get('effectiveSlaDomain')]
if protected_dbs:
if len(protected_dbs) == 1:
return protected_dbs[0]['id']
else:
# Multiple protected, use the first one with a warning
print(f"WARN: Multiple protected databases on {hostname}, using {protected_dbs[0]['dbUniqueName']}")
return protected_dbs[0]['id']
else:
if len(dbs) == 1:
return dbs[0]['id']
else:
raise ValueError(f"Multiple databases on {hostname}, none protected by SLA")
def introspect_schema(self):
"""Introspect the GraphQL schema to get type information"""
introspection_query = """