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

View File

@@ -120,7 +120,7 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description='List Oracle Live Mounts') parser = argparse.ArgumentParser(description='List Oracle Live Mounts')
parser.add_argument('--name', help='Filter by name (mounted database name)') parser.add_argument('--name', help='Filter by name (mounted database name)')
parser.add_argument('--cluster-uuid', help='Filter by cluster UUID') 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('--org-id', help='Filter by organization ID')
parser.add_argument('--sort-field', choices=['NAME', 'CREATION_DATE', 'STATUS'], help='Sort field') 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') parser.add_argument('--sort-order', choices=['ASC', 'DESC'], default='ASC', help='Sort order')
@@ -128,6 +128,12 @@ if __name__ == "__main__":
args = parser.parse_args() 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: try:
list_oracle_live_mounts( list_oracle_live_mounts(
name=args.name, 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): def format_timestamp(timestamp):
"""Format ISO timestamp to readable format""" """Format ISO timestamp to readable format"""
try: 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') return dt.strftime('%Y-%m-%d %H:%M:%S UTC')
except: except:
return timestamp return timestamp
@@ -217,7 +217,7 @@ def list_snapshots(identifier):
format_timestamp(snap['date']), format_timestamp(snap['date']),
'On-Demand' if snap['isOnDemandSnapshot'] else 'Policy', 'On-Demand' if snap['isOnDemandSnapshot'] else 'Policy',
snap['slaDomain']['name'] if snap['slaDomain'] else 'None', 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'] headers = ['Snapshot ID', 'Date', 'Type', 'SLA Domain', 'Location Status']
@@ -245,14 +245,23 @@ def list_snapshots(identifier):
sys.exit(1) sys.exit(1)
def main(): def main():
if len(sys.argv) != 2: if len(sys.argv) == 2:
print("Usage: python list_db_snapshots.py <database_name_or_id>") 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("Examples:")
print(" python list_db_snapshots.py SCLONE") print(" python list_db_snapshots.py SCLONE")
print(" python list_db_snapshots.py 2cb7e201-9da0-53f2-8c69-8fc21f82e0d2") print(" python list_db_snapshots.py 2cb7e201-9da0-53f2-8c69-8fc21f82e0d2")
print(" python list_db_snapshots.py")
sys.exit(1) sys.exit(1)
identifier = sys.argv[1]
list_snapshots(identifier) list_snapshots(identifier)
if __name__ == "__main__": if __name__ == "__main__":

29
mount_oracle_filesonly.py Normal file → Executable file
View File

@@ -5,6 +5,7 @@ import sys
import os import os
import argparse import argparse
import time import time
import socket
from datetime import datetime from datetime import datetime
from rsc import RSCAuth, RSCGraphQL from rsc import RSCAuth, RSCGraphQL
@@ -58,7 +59,6 @@ def find_database_by_name_or_id(identifier):
variables = { variables = {
"filter": [ "filter": [
{"texts": [identifier], "field": "NAME_EXACT_MATCH"}, {"texts": [identifier], "field": "NAME_EXACT_MATCH"},
{"texts": ["false"], "field": "IS_RELIC"},
{"texts": ["false"], "field": "IS_REPLICATED"} {"texts": ["false"], "field": "IS_REPLICATED"}
] ]
} }
@@ -160,7 +160,7 @@ def get_latest_pit(db_id):
print(f"INFO: Latest PIT (ISO8601): {latest_endtime}") print(f"INFO: Latest PIT (ISO8601): {latest_endtime}")
# Convert to datetime and then to milliseconds since epoch # 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) unixtime_ms = int(dt.timestamp() * 1000)
print(f"INFO: Latest PIT unixtime (ms): {unixtime_ms}") print(f"INFO: Latest PIT unixtime (ms): {unixtime_ms}")
@@ -304,26 +304,39 @@ def main():
formatter_class=argparse.RawDescriptionHelpFormatter, formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=""" epilog="""
Examples: 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 SHED
python mount_oracle_filesonly.py --targethost target-host --mountpath /tmp/mount --timestamp "2025-11-25 12:00:00" 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, parser.add_argument("--targethost", required=False,
help="Target host where the files will be mounted") help="Target host where the files will be mounted (defaults to local hostname)")
parser.add_argument("--mountpath", required=True, parser.add_argument("--mountpath", required=True,
help="Target mount path for the files") help="Target mount path for the files")
parser.add_argument("--timestamp", parser.add_argument("--timestamp",
help="Optional timestamp for the recovery point in format 'YYYY-MM-DD HH:MM:SS'") help="Optional timestamp for the recovery point in format 'YYYY-MM-DD HH:MM:SS'")
parser.add_argument("srcdb", parser.add_argument("srcdb", nargs='?',
help="Source database name or RSC database ID") help="Source database name or RSC database ID (optional, defaults to local database)")
args = parser.parse_args() 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: try:
# Find the source database # Find the source database
print(f"INFO: Finding source database: {args.srcdb}") if args.srcdb:
db = find_database_by_name_or_id(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: Found database: {db['dbUniqueName']} (ID: {db['id']})")
print(f"INFO: Cluster: {db['cluster']['name']} (ID: {db['cluster']['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 os
import time import time
import requests import requests
import socket
class RSCAuth: class RSCAuth:
def __init__(self, config_file='rsc.json'): def __init__(self, config_file='rsc.json'):
@@ -102,6 +103,66 @@ class RSCGraphQL:
return data 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): def introspect_schema(self):
"""Introspect the GraphQL schema to get type information""" """Introspect the GraphQL schema to get type information"""
introspection_query = """ introspection_query = """