diff --git a/merge_to_bulk_collect.py b/merge_to_bulk_collect.py deleted file mode 100644 index 7cf1df3..0000000 --- a/merge_to_bulk_collect.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env python3 -""" -Transform Oracle MERGE statement into BULK COLLECT + cursor loop -to avoid Oracle XE bugs with very long MERGE statements. -""" - -import re -import sys - -def transform_merge_to_bulk(input_file, output_file): - with open(input_file, 'r', encoding='utf-8') as f: - content = f.read() - - # Find MERGE statement - merge_start = content.find('MERGE INTO') - if merge_start == -1: - print("ERROR: Could not find MERGE INTO") - sys.exit(1) - - print(f"Found MERGE at position {merge_start}") - - # Find the table name - merge_header = content[merge_start:merge_start+50] - table_match = re.search(r'MERGE INTO\s+(\w+)\s+(\w+)', merge_header, re.IGNORECASE) - if not table_match: - print("ERROR: Could not parse MERGE INTO table") - sys.exit(1) - - table_name = table_match.group(1) - table_alias = table_match.group(2) - print(f"Table: {table_name}, Alias: {table_alias}") - - # Find USING clause - using_start = merge_start + content[merge_start:].find('USING (') - if using_start == merge_start: - print("ERROR: Could not find USING clause") - sys.exit(1) - - # Find ON clause (end of USING subquery) - on_pattern = r'\)\s+(\w+)\s+ON\s+\(' - on_match = re.search(on_pattern, content[using_start:], re.IGNORECASE) - if not on_match: - print("ERROR: Could not find ON clause") - sys.exit(1) - - source_alias = on_match.group(1) - using_end = using_start + on_match.start() - on_start = using_start + on_match.start() + len(on_match.group(0)) - 1 - - # Extract ON condition - paren_count = 1 - on_end = on_start + 1 - while paren_count > 0 and on_end < len(content): - if content[on_end] == '(': - paren_count += 1 - elif content[on_end] == ')': - paren_count -= 1 - on_end += 1 - - on_condition = content[on_start+1:on_end-1].strip() - print(f"ON condition: {on_condition[:80]}...") - - # Extract USING subquery (remove outer parentheses and alias) - using_subquery = content[using_start+7:using_end].strip() - if using_subquery.endswith(')'): - using_subquery = using_subquery[:-1].strip() - if using_subquery.endswith(source_alias): - using_subquery = using_subquery[:-(len(source_alias))].strip() - if using_subquery.endswith(')'): - using_subquery = using_subquery[:-1].strip() - - print(f"Extracted USING subquery: {len(using_subquery)} chars") - - # Find WHEN MATCHED - when_matched_start = content[merge_start:].find('WHEN MATCHED THEN') - if when_matched_start == -1: - print("ERROR: Could not find WHEN MATCHED THEN") - sys.exit(1) - - when_matched_abs = merge_start + when_matched_start - - # Find WHEN NOT MATCHED - when_not_matched_start = content[merge_start:].find('WHEN NOT MATCHED THEN') - if when_not_matched_start == -1: - print("ERROR: Could not find WHEN NOT MATCHED THEN") - sys.exit(1) - - when_not_matched_abs = merge_start + when_not_matched_start - - # Find end of MERGE (semicolon at correct nesting level) - paren_count = 0 - merge_end = when_not_matched_abs - for i in range(when_not_matched_abs, len(content)): - if content[i] == '(': - paren_count += 1 - elif content[i] == ')': - paren_count -= 1 - elif content[i] == ';' and paren_count == 0: - merge_end = i - break - - # Extract UPDATE SET clause - update_section = content[when_matched_abs+len('WHEN MATCHED THEN'):when_not_matched_abs].strip() - update_match = re.search(r'UPDATE\s+SET\s+(.*)', update_section, re.IGNORECASE | re.DOTALL) - if not update_match: - print("ERROR: Could not parse UPDATE SET") - sys.exit(1) - - update_set_clause = update_match.group(1).strip() - - # Replace source alias references in UPDATE SET with record field references - # S.COL -> rec.COL - update_set_clause = re.sub( - rf'\b{source_alias}\.(\w+)', - r'rec.\1', - update_set_clause - ) - - # Extract INSERT clause - insert_section = content[when_not_matched_abs+len('WHEN NOT MATCHED THEN'):merge_end].strip() - insert_match = re.search(r'INSERT\s*\((.*?)\)\s*VALUES\s*\((.*)\)', insert_section, re.IGNORECASE | re.DOTALL) - if not insert_match: - print("ERROR: Could not parse INSERT") - sys.exit(1) - - insert_columns = insert_match.group(1).strip() - insert_values = insert_match.group(2).strip() - if insert_values.endswith(';'): - insert_values = insert_values[:-1].strip() - if insert_values.endswith(')'): - insert_values = insert_values[:-1].strip() - - # Replace source alias references in INSERT VALUES with record field references - # S.COL -> rec.COL - insert_values_transformed = re.sub( - rf'\b{source_alias}\.(\w+)', - r'rec.\1', - insert_values - ) - - # Transform ON condition for WHERE clause (replace S. with rec.) - where_condition = re.sub( - rf'\b{source_alias}\.(\w+)', - r'rec.\1', - on_condition - ) - - # Build transformed PL/SQL with cursor loop - transformation = f""" -- MERGE replaced with cursor loop to avoid Oracle XE bugs with very long MERGE statements - -- Overhead: ~30-50ms for <10k rows, 0 temp writes, 1 SELECT execution - - DECLARE - CURSOR c_source IS - {using_subquery}; - - TYPE t_source_tab IS TABLE OF c_source%ROWTYPE; - l_data t_source_tab; - l_idx PLS_INTEGER; - BEGIN - -- Load all source data into memory (single SELECT execution) - OPEN c_source; - FETCH c_source BULK COLLECT INTO l_data; - CLOSE c_source; - - -- Process each record: UPDATE if exists, INSERT if new - FOR l_idx IN 1..l_data.COUNT LOOP - DECLARE - rec c_source%ROWTYPE := l_data(l_idx); - BEGIN - -- Try UPDATE first (WHEN MATCHED equivalent) - UPDATE {table_name} {table_alias} - SET {update_set_clause} - WHERE {where_condition}; - - -- If no row was updated, INSERT (WHEN NOT MATCHED equivalent) - IF SQL%ROWCOUNT = 0 THEN - INSERT INTO {table_name} ({insert_columns}) - VALUES ({insert_values_transformed}); - END IF; - END; - END LOOP; - END;""" - - # Replace MERGE with transformation - new_content = content[:merge_start] + transformation + content[merge_end+1:] - - with open(output_file, 'w', encoding='utf-8') as f: - f.write(new_content) - - print(f"\nSUCCESS! Created {output_file}") - print(f"Original MERGE: {merge_end - merge_start + 1} chars") - print(f"New PL/SQL block: {len(transformation)} chars") - print(f"\nBenefits:") - print(f" - SELECT executes once (loaded into PGA memory)") - print(f" - No temp table writes") - print(f" - PL/SQL overhead: ~30-50ms for typical workload (<10k rows)") - print(f" - Avoids Oracle XE parser bugs with very long statements") - -if __name__ == '__main__': - if len(sys.argv) != 3: - print("Usage: python merge_to_bulk_collect.py input.sql output.sql") - sys.exit(1) - - transform_merge_to_bulk(sys.argv[1], sys.argv[2]) diff --git a/proxmox/ha-monitor.sh b/proxmox/ha-monitor.sh index 459e71d..ba80ca1 100644 --- a/proxmox/ha-monitor.sh +++ b/proxmox/ha-monitor.sh @@ -1,6 +1,6 @@ #!/bin/bash -# HA Monitor cu PVE::Notify - versiune finală +# HA Monitor cu PVE::Notify - versiune fără qdevice # Folosește sistemul nativ Proxmox cu template-uri personalizate # # TEMPLATE SYSTEM: @@ -33,22 +33,73 @@ FQDN=$(hostname -f) DATE=$(date '+%Y-%m-%d %H:%M:%S') START_TIME=$(date +%s) -# Funcție pentru crearea template-urilor de notificare -create_templates() { - local template_dir="/etc/pve/notification-templates/default" - - # Creează directorul dacă nu există - mkdir -p "$template_dir" - - echo "Creating notification templates in $template_dir..." - - # Template pentru subject - pentru SUCCESS - cat > "$template_dir/ha-status-subject.txt.hbs" << 'EOF' +# Verifică parametri înainte de execuție +if [ "$1" == "--help" ] || [ "$1" == "-h" ]; then + cat << 'HELP' +HA Monitor Script - Proxmox High Availability Monitoring + +USAGE: + /opt/scripts/ha-monitor.sh [OPTION] + +OPTIONS: + (no option) Run HA check and send notification via Proxmox notification system + -v, --verbose Run HA check with detailed console output + --create-templates Recreate notification templates in /etc/pve/notification-templates/default/ + -h, --help Display this help message + +DESCRIPTION: + This script monitors the Proxmox HA cluster status and sends notifications + using the native Proxmox notification system (PVE::Notify). + + It checks: + - HA Services (pve-ha-lrm, pve-ha-crm) + - Cluster Quorum status + - Number of online cluster nodes + +NOTIFICATION TEMPLATES: + Templates are stored in: /etc/pve/notification-templates/default/ + - ha-status-subject.txt.hbs (email subject) + - ha-status-body.txt.hbs (email body text) + - ha-status-body.html.hbs (email body HTML) + +LOG FILE: + /var/log/pve-ha-monitor.log + +EXAMPLES: + # Run normal check (silent, sends notification) + /opt/scripts/ha-monitor.sh + + # Run with verbose output + /opt/scripts/ha-monitor.sh -v + + # Recreate email templates + /opt/scripts/ha-monitor.sh --create-templates + +CRON SETUP: + To run every 5 minutes: + */5 * * * * /opt/scripts/ha-monitor.sh + +HELP + exit 0 +fi + +if [ "$1" == "--create-templates" ] || [ "$1" == "--templates" ]; then + # Funcție pentru crearea template-urilor de notificare + create_templates() { + local template_dir="/etc/pve/notification-templates/default" + + # Creează directorul dacă nu există + mkdir -p "$template_dir" + + echo "Creating notification templates in $template_dir..." + + # Template pentru subject - pentru SUCCESS + cat > "$template_dir/ha-status-subject.txt.hbs" << 'EOF' {{#if (eq status "SUCCESSFUL")}}✅ HA CLUSTER OK - {{ hostname }}{{else}}🚨 HA CLUSTER ISSUES - {{ hostname }}{{/if}} EOF - - # Template pentru body text - cat > "$template_dir/ha-status-body.txt.hbs" << 'EOF' + + # Template pentru body text + cat > "$template_dir/ha-status-body.txt.hbs" << 'EOF' {{#if (eq status "SUCCESSFUL")}}✅ HIGH AVAILABILITY STATUS: ALL SYSTEMS OK{{else}}🚨 HIGH AVAILABILITY CLUSTER HAS ISSUES{{/if}} Host: {{ hostname }} @@ -58,40 +109,17 @@ CLUSTER STATUS: {{ details }} {{#if (eq status "FAILED")}} -=== HOW TO READ pvecm status OUTPUT === +=== IMMEDIATE ACTIONS REQUIRED === -Your current problematic output shows: -- Total votes: 2 (WRONG - should be 3) -- Qdevice (votes 0) (WRONG - should be votes 1) +1. SSH to cluster: ssh root@{{ hostname }} +2. Check overall status: pvecm status +3. Review HA logs: journalctl -u pve-ha-lrm -u pve-ha-crm -n 20 +4. Check network connectivity between nodes +5. Verify all cluster nodes are online -After fix should show: -- Total votes: 3 (CORRECT) -- Qdevice (votes 1) (CORRECT) - -=== STEP-BY-STEP FIX === - -Step 1 - Fix Qdevice (PRIORITY): - systemctl restart corosync-qdevice - sleep 5 - corosync-qdevice-tool -s - -Step 2 - Verify cluster status: - pvecm status - LOOK FOR: Total votes: 3 (not 2!) and Qdevice (votes 1) - -Step 3 - Test HA functionality: - ha-manager status - -=== WHAT THIS MEANS === -QDEVICE DISCONNECTED: No tie-breaker vote -- If one node fails, cluster may lose quorum -- VMs won't automatically migrate - -The cluster works now but has no tie-breaker vote. -One node failure = no quorum = VMs can't migrate. {{else}} All HA components are functioning normally. -- Cluster has proper quorum with qdevice participation +- Cluster has proper quorum - Automatic VM migration is available - System is fully redundant {{/if}} @@ -115,8 +143,8 @@ Log file: /var/log/pve-ha-monitor.log Total check time: {{ runtime }}s EOF - # Template pentru body HTML cu font mai mare și consistent - cat > "$template_dir/ha-status-body.html.hbs" << 'EOF' + # Template pentru body HTML cu font mai mare și consistent + cat > "$template_dir/ha-status-body.html.hbs" << 'EOF'
{{ details }}
{{#if (eq status "FAILED")}}
-Your current problematic output shows:
-After fix should show:
-ssh root@{{ hostname }}pvecm statusjournalctl -u pve-ha-lrm -u pve-ha-crm -n 20LOOK FOR: Total votes: 3 (not 2!) and Qdevice (votes 1)
- -Bottom line: The cluster works now but has no tie-breaker vote.
-One node failure = no quorum = VMs can't migrate.
Warning: Issues detected in the cluster. Immediate attention required to ensure high availability.
{{else}}All HA components are functioning normally: