196 lines
6.5 KiB
Python
Executable File
196 lines
6.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
import os
|
|
import re
|
|
import time
|
|
import subprocess
|
|
import requests
|
|
from requests.auth import HTTPBasicAuth
|
|
|
|
CONSOLE_URL = os.environ.get("MVS_CONSOLE_URL", "http://oldcomputernerd.com:8038/cgi-bin/tasks/syslog")
|
|
CONSOLE_USER = os.environ.get("MVS_CONSOLE_USER", "gmgauthier")
|
|
CONSOLE_PASS = os.environ.get("MVS_CONSOLE_PASSWORD")
|
|
LINODE_HOST = os.environ.get("LINODE_SSH_HOST", "gmgauthier@socrates")
|
|
LINODE_PRINTOUT_DIR = os.environ.get("LINODE_PRINTOUT_DIR", "/home/gmgauthier/printouts")
|
|
|
|
def get_syslog():
|
|
"""Fetch the Hercules syslog via HTTP"""
|
|
try:
|
|
response = requests.get(
|
|
CONSOLE_URL,
|
|
auth=HTTPBasicAuth(CONSOLE_USER, CONSOLE_PASS),
|
|
timeout=10
|
|
)
|
|
response.raise_for_status()
|
|
return response.text
|
|
except requests.RequestException as e:
|
|
print(f"Failed to fetch syslog: {e}")
|
|
return None
|
|
|
|
def find_job_number(syslog, jobname):
|
|
"""Extract job number from $HASP100 message"""
|
|
# Pattern: /12.28.02 JOB 257 $HASP100 SIMPLE2 ON READER1
|
|
pattern = rf'/\d+\.\d+\.\d+\s+JOB\s+(\d+)\s+\$HASP100\s+{jobname}\s+ON\s+READER'
|
|
match = re.search(pattern, syslog, re.IGNORECASE)
|
|
if match:
|
|
return match.group(1)
|
|
return None
|
|
|
|
def check_job_completed(syslog, jobname, job_number):
|
|
"""Check if a job has completed printing (HASP150 message)"""
|
|
# Pattern: /12.28.03 JOB 257 $HASP150 SIMPLE2 ON PRINTER1
|
|
pattern = rf'/\d+\.\d+\.\d+\s+JOB\s+{job_number}\s+\$HASP150\s+{jobname}\s+ON\s+PRINTER'
|
|
return re.search(pattern, syslog, re.IGNORECASE) is not None
|
|
|
|
def list_pdfs_local(local_dir):
|
|
"""List PDF files in a local directory (for mounted volumes)"""
|
|
import glob
|
|
pdf_files = glob.glob(f"{local_dir}/v1403-*.pdf")
|
|
# Sort by modification time, newest first
|
|
pdf_files.sort(key=os.path.getmtime, reverse=True)
|
|
return pdf_files
|
|
|
|
def list_pdfs_remote():
|
|
"""List PDF files on remote Linode via SSH"""
|
|
cmd = f"ssh {LINODE_HOST} ls -t {LINODE_PRINTOUT_DIR}/v1403-*.pdf"
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
shell=True,
|
|
check=True,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
return result.stdout.strip().split('\n')
|
|
except subprocess.CalledProcessError:
|
|
return []
|
|
|
|
def find_pdf_for_job(job_number, jname, local_printout_dir=None):
|
|
"""Find the PDF matching job number and name"""
|
|
pattern = f"v1403-J{job_number}_{jname}-"
|
|
|
|
# Try the local directory first (for mounted volumes in CI)
|
|
if local_printout_dir and os.path.isdir(str(local_printout_dir)):
|
|
pdfs = list_pdfs_local(local_printout_dir)
|
|
for pdf_path in pdfs:
|
|
if pattern in pdf_path:
|
|
return pdf_path
|
|
return None
|
|
|
|
# Fall back to remote SSH access
|
|
pdfs = list_pdfs_remote()
|
|
for pdf_path in pdfs:
|
|
if pattern in pdf_path:
|
|
return pdf_path
|
|
return None
|
|
|
|
def retrieve_pdf(source_path, local_filename, is_local=False):
|
|
"""Retrieve PDF either locally (copy) or remotely (SCP)"""
|
|
try:
|
|
if is_local:
|
|
# Local copy from a mounted volume
|
|
import shutil
|
|
shutil.copy2(source_path, local_filename)
|
|
print(f"Copied: {local_filename}")
|
|
else:
|
|
# Remote SCP
|
|
cmd = f"scp {LINODE_HOST}:{source_path} {local_filename}"
|
|
subprocess.run(cmd, shell=True, check=True)
|
|
print(f"Retrieved: {local_filename}")
|
|
return True
|
|
except (subprocess.CalledProcessError, IOError) as e:
|
|
print(f"Failed to retrieve PDF: {e}")
|
|
return False
|
|
|
|
def poll_for_job(jn, to=300, poll_interval=5):
|
|
"""Poll the console for job completion and retrieve PDF"""
|
|
jobname_upper = jn.upper()
|
|
start_time = time.time()
|
|
job_number = None
|
|
|
|
print(f"Polling for job: {jobname_upper}")
|
|
print(f"Timeout: {to}s, Poll interval: {poll_interval}s")
|
|
print(f"Console URL: {CONSOLE_URL}")
|
|
print(f"Console User: {CONSOLE_USER}")
|
|
print(f"Console Pass: {'***' if CONSOLE_PASS else 'NOT SET'}")
|
|
print()
|
|
|
|
# Phase 1: Find a job number
|
|
print("Phase 1: Looking for job submission ($HASP100)...")
|
|
while time.time() - start_time < to:
|
|
syslog = get_syslog()
|
|
if not syslog:
|
|
time.sleep(poll_interval)
|
|
continue
|
|
|
|
job_number = find_job_number(syslog, jobname_upper)
|
|
if job_number:
|
|
print(f"Found job number: J{job_number}")
|
|
break
|
|
|
|
time.sleep(poll_interval)
|
|
|
|
if not job_number:
|
|
print(f"Timeout: Job {jobname_upper} not found in console after {to}s")
|
|
return 1
|
|
|
|
# Phase 2: Wait for completion
|
|
print(f"Phase 2: Waiting for job completion ($HASP150)...")
|
|
while time.time() - start_time < to:
|
|
syslog = get_syslog()
|
|
if not syslog:
|
|
time.sleep(poll_interval)
|
|
continue
|
|
|
|
if check_job_completed(syslog, jobname_upper, job_number):
|
|
print(f"Job J{job_number} completed and printed!")
|
|
break
|
|
|
|
time.sleep(poll_interval)
|
|
else:
|
|
print(f"Timeout: Job J{job_number} did not complete after {to}s")
|
|
return 1
|
|
|
|
# Phase 3: Retrieve PDF
|
|
print("Phase 3: Retrieving PDF...")
|
|
# Give the PDF a moment to be written to disk
|
|
time.sleep(2)
|
|
|
|
# Check for local mounted directory (CI environment)
|
|
local_printout_dir = os.environ.get("LOCAL_PRINTOUT_DIR")
|
|
is_local = local_printout_dir and os.path.isdir(local_printout_dir)
|
|
|
|
if is_local:
|
|
print(f"Using local mounted directory: {local_printout_dir}")
|
|
|
|
pdf_path = find_pdf_for_job(job_number, jobname_upper, local_printout_dir)
|
|
if not pdf_path:
|
|
print(f"Error: PDF not found for J{job_number}_{jobname_upper}")
|
|
return 1
|
|
|
|
local_filename = f"{jobname_upper}_J{job_number}.pdf"
|
|
if retrieve_pdf(pdf_path, local_filename, is_local):
|
|
print(f"Success! Job output saved to: {local_filename}")
|
|
return 0
|
|
else:
|
|
return 1
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
print("Usage: poll_job.py <jobname> [timeout_seconds]")
|
|
print()
|
|
print("Arguments:")
|
|
print(" jobname - Job name to poll for (required)")
|
|
print(" timeout_seconds - Maximum time to wait (optional, default: 300)")
|
|
print()
|
|
print("Example:")
|
|
print(" poll_job.py SIMPLE2")
|
|
print(" poll_job.py SIMPLE2 600")
|
|
sys.exit(1)
|
|
|
|
jobname = sys.argv[1]
|
|
timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 300
|
|
|
|
sys.exit(poll_for_job(jobname, timeout))
|