# flake8: noqa: E501 (demo data and canned output contain long literal lines) import hashlib import json import pathlib import random from datetime import datetime, timedelta random.seed(7) D = str(pathlib.Path(__file__).resolve().parent) now = datetime.now().replace(microsecond=0) def q(s): return "'" + str(s).replace("\\", "\\\\").replace("'", "\\'") + "'" def ts(h): return q((now - timedelta(hours=h)).strftime("%Y-%m-%d %H:%M:%S")) sql = [] users = [ (1, "alex.rivera", "Alex Rivera", "alex@example.com", "admin,employee", 1), (2, "sam.chen", "Sam Chen", "sam@example.com", "employee", 0), (3, "riley.brooks", "Riley Brooks", "riley@example.com", "employee", 0), (4, "jordan.kim", "Jordan Kim", "jordan@example.com", "employee", 0), (5, "hwmon-daemon", "hwmonDaemon", "", "employee", 0), ] for u in users: sql.append( f"INSERT INTO users(user_id,username,display_name,email,`groups`,is_admin,last_login) VALUES({u[0]},{q(u[1])},{q(u[2])},{q(u[3])},{q(u[4])},{u[5]},{ts(1)});" ) tr = [ ("Open", "In Progress", 0, 0), ("Open", "Pending", 0, 0), ("Open", "Closed", 1, 0), ("Pending", "Open", 0, 0), ("Pending", "In Progress", 0, 0), ("Pending", "Closed", 1, 0), ("In Progress", "Pending", 0, 0), ("In Progress", "Open", 0, 0), ("In Progress", "Closed", 1, 0), ("Closed", "Open", 0, 1), ] for a, b, c, d in tr: sql.append( f"INSERT INTO status_transitions(from_status,to_status,requires_comment,requires_admin) VALUES({q(a)},{q(b)},{c},{d});" ) # (title, cat, type, status, prio, assignee, creator, age_h, desc) T = [ ( "[storage-01][auto][hardware] Drive SN-4F21A8 has SMART issues [single-node][production][problem]", "Hardware", "Problem", "Open", 2, 2, 5, 3, "SMART reports 12 reallocated sectors and 1 pending sector on /dev/sdc.\n\n```\nReallocated_Sector_Ct 12\nCurrent_Pending_Sector 1\n```\nOSD.14 is backed by this drive. Plan a replacement in the next maintenance window.", ), ( "[node-02][auto][hardware] CPU temperature sustained above 85C [single-node][production][problem]", "Hardware", "Problem", "Open", 2, None, 5, 5, "15-minute average package temperature 87C (threshold 85C). Fans at 100%. Check airflow and heatsink seating.", ), ( "[proxmox-cluster][auto][software] Ceph HEALTH_WARN: 3 slow ops on osd.9 [cluster-wide][production][problem]", "Software", "Problem", "In Progress", 3, 1, 5, 26, "`ceph health detail` reports slow BlueStore ops on osd.9. PGs remain active+clean. Triage steps in the Ceph runbook.", ), ( "[node-03][auto][hardware] LXC storage pool above 85% usage [single-node][production][maintenance]", "Hardware", "Maintenance", "Open", 3, 3, 5, 9, "Pool plpSSDPool is at 86%. Grow or move guests before it reaches 90%.", ), ( "[switch-02][auto][network] Link down on uplink port 14 [single-node][production][problem]", "Network", "Problem", "Open", 1, 1, 5, 2, "Port 14 (10GbE uplink to node-03) went down 2 hours ago. Traffic failed over to the backup path. Cable or SFP+ suspected.", ), ( "[proxmox-cluster][manual][software] Upgrade Proxmox VE 8.3 to 8.4 [cluster-wide][production][upgrade]", "Software", "Upgrade", "Pending", 3, 2, 1, 70, "Rolling upgrade, one node at a time. Blocked until the Ceph warning on osd.9 is resolved.", ), ( "[monitor-01][manual][software] Renew TLS certificate for status page [single-node][production][maintenance]", "Software", "Maintenance", "In Progress", 4, 4, 1, 30, "Certificate expires in 12 days. Renewal job failed on the DNS challenge.", ), ( "[proxmox-cluster][manual][security] Rotate API keys used by monitoring agents [cluster-wide][production][task]", "Security", "Task", "Open", 3, 1, 1, 48, "Quarterly rotation. Keys: hwmonDaemon, gandalf, pulse workers.", ), ( "[node-01][auto][hardware] Drive SN-88C0D2 has SMART issues [single-node][production][problem]", "Hardware", "Problem", "Closed", 2, 2, 5, 150, "Replaced during maintenance. OSD rebuilt, cluster healthy.", ), ( "[proxmox-cluster][manual][configuration] Add Prometheus scrape target for new exporter [cluster-wide][production][task]", "General", "Task", "Closed", 5, 3, 3, 200, "Added and verified in Targets.", ), ( "[node-02][manual][hardware] Replace failed NVMe in bay 2 [single-node][production][maintenance]", "Hardware", "Maintenance", "Pending", 2, 2, 1, 52, "Replacement ordered. Awaiting delivery.", ), ( "[storage-01][auto][hardware] Memory ECC correctable errors increasing [single-node][production][problem]", "Hardware", "Problem", "Open", 3, None, 5, 14, "EDAC reports 41 correctable errors on DIMM_B1 in 24 hours.", ), ( "[proxmox-cluster][manual][software] LXC 134 has no log retention configured [single-node][production][maintenance]", "Software", "Maintenance", "Open", 4, None, 3, 96, "Log volume grows without bound. Add retention and a compactor.", ), ( "[backup-01][manual][software] Verify weekly offsite backup restore [single-node][production][task]", "General", "Task", "In Progress", 4, 4, 1, 40, "Restore one guest from the weekly backup and confirm it boots.", ), ( "[switch-01][manual][network] Move camera VLAN to dedicated port group [single-node][testing][configuration]", "Network", "Task", "Pending", 5, 3, 3, 120, "Needs a maintenance window.", ), ( "[node-03][auto][hardware] Fan 2 speed below threshold [single-node][production][problem]", "Hardware", "Problem", "Closed", 3, 1, 5, 310, "Fan replaced.", ), ( "[proxmox-cluster][manual][software] Document UPS shutdown order [cluster-wide][production][task]", "General", "Task", "Closed", 4, 2, 2, 400, "Documented in the wiki.", ), ( "[node-02][auto][hardware] Drive SN-1B77E4 temperature above 55C [single-node][production][problem]", "Hardware", "Problem", "Open", 4, None, 5, 6, "Drive temperature 57C, threshold 55C.", ), ( "[proxmox-cluster][manual][security] Review SSH access for decommissioned hosts [cluster-wide][production][task]", "Security", "Task", "Pending", 3, 1, 1, 88, "Remove stale authorized_keys entries.", ), ( "[monitor-01][manual][software] Grafana alert rules need review [single-node][production][maintenance]", "Software", "Maintenance", "Open", 4, 4, 1, 60, "Several rules have no contact point.", ), ] tids = [] for i, t in enumerate(T): tid = str(100000000 + random.randint(1000000, 899999999)) tids.append(tid) title, cat, typ, status, pr, asg, cr, age, desc = t closed = ts(max(age - 5, 1)) if status == "Closed" else "NULL" sql.append( f"INSERT INTO tickets(ticket_id,title,category,type,status,description,created_at,updated_at,closed_at,priority,hash,created_by,updated_by,assigned_to) VALUES({q(tid)},{q(title)},{q(cat)},{q(typ)},{q(status)},{q(desc)},{ts(age)},{ts(max(age-3,1))},{closed},{pr},{q(hashlib.sha256(title.encode()).hexdigest())},{cr},{cr},{asg if asg else 'NULL'});" ) sql.append( f"INSERT INTO audit_log(user_id,action_type,entity_type,entity_id,details,created_at) VALUES({cr},'create','ticket',{q(tid)},{q(json.dumps({'title':title}))},{ts(age)});" ) if asg: sql.append( f"INSERT INTO audit_log(user_id,action_type,entity_type,entity_id,details,created_at) VALUES(1,'assign','ticket',{q(tid)},{q(json.dumps({'assigned_to':{'from':'','to':users[asg-1][2]}}))},{ts(max(age-1,1))});" ) if status != "Open": sql.append( f"INSERT INTO audit_log(user_id,action_type,entity_type,entity_id,details,created_at) VALUES({asg or 1},'status_change','ticket',{q(tid)},{q(json.dumps({'status':{'from':'Open','to':status}}))},{ts(max(age-2,1))});" ) C = { 0: [ (2, "Confirmed on the node. Pulling the drive from the pool tonight and ordering a replacement.", 2), (1, "Approved. Use the spare from the shelf, then reorder.", 1), ], 2: [ ( 1, "Slow ops are isolated to osd.9. `ceph tell osd.9 dump_historic_slow_ops` shows long waits on the DB device.", 20, ), (2, "Marked the OSD out for now. Recovery is progressing.", 12), ], 4: [(1, "Failover worked as designed. Swapping the SFP+ module first.", 1)], 5: [(2, "Waiting for the Ceph warning to clear before starting the first node.", 30)], 6: [(4, "DNS challenge fails because the API token expired. Generated a new one.", 8)], } for idx, cs in C.items(): for uid, txt, age in cs: sql.append( f"INSERT INTO ticket_comments(ticket_id,user_name,comment_text,created_at,markdown_enabled,user_id) VALUES({q(tids[idx])},{q(users[uid-1][2])},{q(txt)},{ts(age)},1,{uid});" ) sql.append( f"INSERT INTO audit_log(user_id,action_type,entity_type,entity_id,details,created_at) VALUES({uid},'comment','comment','0',{q(json.dumps({'ticket_id':tids[idx]}))},{ts(age)});" ) sql.append( f"INSERT INTO ticket_dependencies(ticket_id,depends_on_id,dependency_type,created_by) VALUES({q(tids[5])},{q(tids[2])},'blocked_by',1);" ) sql.append( f"INSERT INTO ticket_dependencies(ticket_id,depends_on_id,dependency_type,created_by) VALUES({q(tids[0])},{q(tids[8])},'relates_to',1);" ) sql.append( f"INSERT INTO ticket_dependencies(ticket_id,depends_on_id,dependency_type,created_by) VALUES({q(tids[4])},{q(tids[14])},'relates_to',1);" ) for tid in (tids[0], tids[2], tids[4]): sql.append(f"INSERT INTO ticket_watchers(ticket_id,user_id) VALUES({q(tid)},1);") for n, p, s in ( ("hwmonDaemon (prod)", "hwm_9f3a", "read_write"), ("gandalf", "gnd_71c2", "read_write"), ("readonly-dashboard", "ro_55de", "read"), ): sql.append( f"INSERT INTO api_keys(key_name,key_hash,key_prefix,scope,created_by,last_used) VALUES({q(n)},{q(hashlib.sha256(p.encode()).hexdigest())},{q(p)},{q(s)},1,{ts(2)});" ) sql.append( f"INSERT INTO recurring_tickets(title_template,description_template,category,type,priority,schedule_type,schedule_day,next_run_at,created_by) VALUES('[proxmox-cluster][auto][software] Monthly patch review [cluster-wide][production][maintenance]','Review pending updates and schedule a window.','Software','Maintenance',4,'monthly',1,{ts(-200)},1);" ) sql.append( f"INSERT INTO recurring_tickets(title_template,description_template,category,type,priority,schedule_type,schedule_day,next_run_at,created_by) VALUES('[backup-01][auto][software] Weekly restore test [single-node][production][task]','Restore one guest and verify.','General','Task',4,'weekly',1,{ts(-60)},1);" ) open(D + "/seed.sql", "w").write("\n".join(sql)) print(len(T), "tickets; ids sample", tids[:3])