Skip to content

Instantly share code, notes, and snippets.

@vchrombie
Last active May 26, 2025 22:23
Show Gist options
  • Save vchrombie/722374781d687bbdf9e4f62584adc9db to your computer and use it in GitHub Desktop.
Save vchrombie/722374781d687bbdf9e4f62584adc9db to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import re
from collections import defaultdict
def parse_line(line):
"""
Parse a single line of input.
Expected formats:
- Client<TAB>AppServer<TAB>Instance Mismatch (a/b)
- Client<TAB>AppServer<TAB>SAM unreachable on <hostname>
Returns tuple (client, target, (action, a, b)) or None if no action needed.
"""
parts = line.strip().split('\t')
if len(parts) != 3:
return None
client, target, msg = parts
# Instance mismatch case
m = re.match(r'Instance Mismatch \((\d+)/(\d+)\)', msg)
if m:
a, b = map(int, m.groups())
if a < b:
action = 'start'
elif a > b:
action = 'stop extra'
else:
return None
return client, target, (action, a, b)
# SAM unreachable case
m2 = re.match(r'SAM is unreachable on (\S+)', msg)
if m2:
host = m2.group(1)
return client, host, ('agent-start', None, None)
return None
def group_records(records):
"""
Build grouping structures:
- by_client[(client, action)] = set of targets (appservers or hosts)
- by_server[(server, action)] = set of clients
Also collects reasons for mismatch actions.
"""
by_client = defaultdict(set)
by_server = defaultdict(set)
reasons = defaultdict(set)
for rec in records:
client, target, (action, a, b) = rec
key = (client, action)
if action == 'agent-start':
by_client[key].add(target)
else:
by_client[key].add(target)
by_server[(target, action)].add(client)
reason = (
"Instances fewer than expected"
if action == 'start'
else "Instances exceed expected"
)
reasons[key].add(reason)
return by_client, by_server, reasons
def format_samsh_command(action, clients, servers, reason=None):
"""
Format a samsh.pl command.
- For agent-start: include client outside quotes, list of hosts inside, with simple reason.
- For mismatch actions: include -c flag and full quoted payload with reason.
"""
base = "/usr/local/bfm/etc/samsh.pl"
if action == 'agent-start':
client = clients[0]
hosts = " ".join(servers)
# simple reason for SAM start
return f"{base} {client} -c '{action} {hosts} -r Starting SAM'"
else:
payload = f"{action} client={','.join(clients)} name={','.join(servers)}"
if reason:
payload += f" -r {reason}"
return f"{base} -c '{payload}'"
def generate_commands(by_client, by_server, reasons):
"""
Create the final list of commands in this order:
1. agent-start groups (per client)
2. instance-mismatch groups by client (multi-target)
3. instance-mismatch groups by server (multi-client)
4. any remaining single commands
"""
used = set()
cmds = []
# 1. agent-start (unreachable) by client
for (client, action), targets in sorted(by_client.items()):
if action == 'agent-start':
servers = sorted(targets)
cmds.append(format_samsh_command(action, [client], servers))
for srv in servers:
used.add((client, srv, action))
# 2. instance-mismatch group by client (multi-target)
for (client, action), servers in sorted(by_client.items()):
if action in ('start', 'stop extra') and len(servers) > 1:
reason_text = next(iter(reasons[(client, action)]))
cmds.append(format_samsh_command(
action, [client], sorted(servers), reason_text
))
for srv in servers:
used.add((client, srv, action))
# 3. instance-mismatch group by server (multi-client)
for (server, action), clients in sorted(by_server.items()):
remaining = [c for c in sorted(clients)
if (c, server, action) not in used]
if len(remaining) > 1:
reason_text = next(iter(reasons[(remaining[0], action)]))
cmds.append(format_samsh_command(
action, remaining, [server], reason_text
))
for c in remaining:
used.add((c, server, action))
# 4. remaining individual commands
for (client, action), servers in sorted(by_client.items()):
for server in sorted(servers):
if (client, server, action) not in used:
reason_text = next(iter(reasons[(client, action)]), None)
cmds.append(format_samsh_command(
action, [client], [server], reason_text
))
used.add((client, server, action))
return cmds
def write_script(commands, output_file='samsh.sh'):
"""
Write the commands to a bash script with header.
"""
with open(output_file, 'w') as f:
for cmd in commands:
f.write(cmd + '\n')
def main():
input_file = 'samsh.txt'
records = []
with open(input_file, 'r') as f:
for line in f:
parsed = parse_line(line)
if parsed:
records.append(parsed)
by_client, by_server, reasons = group_records(records)
commands = generate_commands(by_client, by_server, reasons)
write_script(commands, output_file='samsh.sh')
if __name__ == '__main__':
main()
/usr/local/bfm/etc/samsh.pl "stop extra client=BLK name=LogRaiderSenseBetaServer,RTCServer_BLUE -r Active instances exceed expected count"
/usr/local/bfm/etc/samsh.pl "start client=JPMA name=VARServer_DR_SITE,VARServer_plus_batch_DR_SITE -r Active instances fewer than expected"
/usr/local/bfm/etc/samsh.pl "start client=MIC name=AccountingPortalReportExportServer,AlphaValuationService,SRSAssetLoadServer -r Active instances fewer than expected"
/usr/local/bfm/etc/samsh.pl "stop extra client=GUGGENHEIM,MLC name=AlphaValuationService -r Active instances exceed expected count"
/usr/local/bfm/etc/samsh.pl "start client=AEG name=VARServer_plus_batch_DR_SITE -r Active instances fewer than expected"
/usr/local/bfm/etc/samsh.pl "stop extra client=AEG name=ADCDatasetManagementServer -r Active instances exceed expected count"
/usr/local/bfm/etc/samsh.pl "start client=ALX name=CLOServer_BETA -r Active instances fewer than expected"
/usr/local/bfm/etc/samsh.pl "start client=AVIVA name=GPLiveSQLServerRpt -r Active instances fewer than expected"
/usr/local/bfm/etc/samsh.pl "start client=CAIXABANK name=TSSHealth_TMP -r Active instances fewer than expected"
/usr/local/bfm/etc/samsh.pl "start client=GUGGENHEIM name=AlphaValuationService_BETA -r Active instances fewer than expected"
/usr/local/bfm/etc/samsh.pl "stop extra client=SPC name=GRServerPROD -r Active instances exceed expected count"
GUGGENHEIM AlphaValuationService_BETA Instance Mismatch (0/1)
GUGGENHEIM AlphaValuationService_BETA Instance Mismatch (0/1)
GUGGENHEIM AlphaValuationService Instance Mismatch (3/1)
MLC AlphaValuationService Instance Mismatch (2/1)
MIC AlphaValuationService Instance Mismatch (0/1)
MIC AccountingPortalReportExportServer Instance Mismatch (0/1)
MIC AccountingPortalReportExportServer Instance Mismatch (0/1)
SPC GRServerPROD Instance Mismatch (1/0)
BLK LogRaiderSenseBetaServer Instance Mismatch (3/1)
BLK RTCServer_BLUE Instance Mismatch (4/2)
MIC SRSAssetLoadServer Instance Mismatch (0/7)
MIC SRSAssetLoadServer Instance Mismatch (0/6)
MIC SRSAssetLoadServer Instance Mismatch (0/7)
AVIVA GPLiveSQLServerRpt Instance Mismatch (1/2)
JPMA VARServer_plus_batch_DR_SITE Instance Mismatch (0/19)
JPMA VARServer_DR_SITE Instance Mismatch (0/1)
ALX CLOServer_BETA Instance Mismatch (0/1)
AEG ADCDatasetManagementServer Instance Mismatch (2/1)
AEG VARServer_plus_batch_DR_SITE Instance Mismatch (3/4)
CAIXABANK TSSHealth_TMP Instance Mismatch (0/1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment