282 lines
9.5 KiB
Python
Executable File
282 lines
9.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Analyze Network resources and diagnostics from must-gather data.
|
|
Shows network operator status, OVN pods, and connectivity checks.
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import yaml
|
|
from pathlib import Path
|
|
from typing import List, Dict, Any, Optional
|
|
|
|
|
|
def parse_yaml_file(file_path: Path) -> Optional[Dict[str, Any]]:
|
|
"""Parse a YAML file."""
|
|
try:
|
|
with open(file_path, 'r') as f:
|
|
doc = yaml.safe_load(f)
|
|
return doc
|
|
except Exception as e:
|
|
print(f"Warning: Failed to parse {file_path}: {e}", file=sys.stderr)
|
|
return None
|
|
|
|
|
|
def get_network_type(must_gather_path: Path) -> str:
|
|
"""Determine the network type from cluster network config."""
|
|
# First try to find networks.yaml (List object)
|
|
patterns = [
|
|
"cluster-scoped-resources/config.openshift.io/networks.yaml",
|
|
"*/cluster-scoped-resources/config.openshift.io/networks.yaml",
|
|
]
|
|
|
|
for pattern in patterns:
|
|
for network_file in must_gather_path.glob(pattern):
|
|
network_list = parse_yaml_file(network_file)
|
|
if network_list:
|
|
# Handle NetworkList object
|
|
items = network_list.get('items', [])
|
|
if items:
|
|
# Get the first network item
|
|
network = items[0]
|
|
spec = network.get('spec', {})
|
|
network_type = spec.get('networkType', 'Unknown')
|
|
if network_type != 'Unknown':
|
|
return network_type
|
|
|
|
# Fallback: try individual network config files
|
|
patterns = [
|
|
"cluster-scoped-resources/config.openshift.io/*.yaml",
|
|
]
|
|
|
|
for pattern in patterns:
|
|
for network_file in must_gather_path.glob(pattern):
|
|
if network_file.name in ['networks.yaml']:
|
|
continue
|
|
|
|
network = parse_yaml_file(network_file)
|
|
if network:
|
|
spec = network.get('spec', {})
|
|
network_type = spec.get('networkType', 'Unknown')
|
|
if network_type != 'Unknown':
|
|
return network_type
|
|
|
|
return 'Unknown'
|
|
|
|
|
|
def analyze_network_operator(must_gather_path: Path) -> Optional[Dict[str, Any]]:
|
|
"""Analyze network operator status."""
|
|
patterns = [
|
|
"cluster-scoped-resources/config.openshift.io/clusteroperators/network.yaml",
|
|
"*/cluster-scoped-resources/config.openshift.io/clusteroperators/network.yaml",
|
|
]
|
|
|
|
for pattern in patterns:
|
|
for op_file in must_gather_path.glob(pattern):
|
|
operator = parse_yaml_file(op_file)
|
|
if operator:
|
|
conditions = operator.get('status', {}).get('conditions', [])
|
|
result = {}
|
|
|
|
for cond in conditions:
|
|
cond_type = cond.get('type')
|
|
if cond_type in ['Available', 'Progressing', 'Degraded']:
|
|
result[cond_type] = cond.get('status', 'Unknown')
|
|
result[f'{cond_type}_message'] = cond.get('message', '')
|
|
|
|
return result
|
|
|
|
return None
|
|
|
|
|
|
def analyze_ovn_pods(must_gather_path: Path) -> List[Dict[str, str]]:
|
|
"""Analyze OVN-Kubernetes pods."""
|
|
pods = []
|
|
|
|
patterns = [
|
|
"namespaces/openshift-ovn-kubernetes/pods/*/*.yaml",
|
|
"*/namespaces/openshift-ovn-kubernetes/pods/*/*.yaml",
|
|
]
|
|
|
|
for pattern in patterns:
|
|
for pod_file in must_gather_path.glob(pattern):
|
|
if pod_file.name == 'pods.yaml':
|
|
continue
|
|
|
|
pod = parse_yaml_file(pod_file)
|
|
if pod:
|
|
name = pod.get('metadata', {}).get('name', 'unknown')
|
|
status = pod.get('status', {})
|
|
phase = status.get('phase', 'Unknown')
|
|
|
|
container_statuses = status.get('containerStatuses', [])
|
|
total = len(pod.get('spec', {}).get('containers', []))
|
|
ready = sum(1 for cs in container_statuses if cs.get('ready', False))
|
|
|
|
pods.append({
|
|
'name': name,
|
|
'ready': f"{ready}/{total}",
|
|
'status': phase
|
|
})
|
|
|
|
# Remove duplicates
|
|
seen = set()
|
|
unique_pods = []
|
|
for p in pods:
|
|
if p['name'] not in seen:
|
|
seen.add(p['name'])
|
|
unique_pods.append(p)
|
|
|
|
return sorted(unique_pods, key=lambda x: x['name'])
|
|
|
|
|
|
def analyze_connectivity_checks(must_gather_path: Path) -> Dict[str, Any]:
|
|
"""Analyze PodNetworkConnectivityCheck resources."""
|
|
# First try to find podnetworkconnectivitychecks.yaml (List object)
|
|
patterns = [
|
|
"pod_network_connectivity_check/podnetworkconnectivitychecks.yaml",
|
|
"*/pod_network_connectivity_check/podnetworkconnectivitychecks.yaml",
|
|
]
|
|
|
|
total_checks = 0
|
|
failed_checks = []
|
|
|
|
for pattern in patterns:
|
|
for check_file in must_gather_path.glob(pattern):
|
|
check_list = parse_yaml_file(check_file)
|
|
if check_list:
|
|
items = check_list.get('items', [])
|
|
for check in items:
|
|
total_checks += 1
|
|
name = check.get('metadata', {}).get('name', 'unknown')
|
|
status = check.get('status', {})
|
|
|
|
conditions = status.get('conditions', [])
|
|
for cond in conditions:
|
|
if cond.get('type') == 'Reachable' and cond.get('status') == 'False':
|
|
failed_checks.append({
|
|
'name': name,
|
|
'message': cond.get('message', 'Unknown')
|
|
})
|
|
|
|
# If we found the list file, no need to continue
|
|
if total_checks > 0:
|
|
return {
|
|
'total': total_checks,
|
|
'failed': failed_checks
|
|
}
|
|
|
|
# Fallback: try individual check files
|
|
patterns = [
|
|
"*/pod_network_connectivity_check/*.yaml",
|
|
]
|
|
|
|
for pattern in patterns:
|
|
for check_file in must_gather_path.glob(pattern):
|
|
if check_file.name == 'podnetworkconnectivitychecks.yaml':
|
|
continue
|
|
|
|
check = parse_yaml_file(check_file)
|
|
if check:
|
|
total_checks += 1
|
|
name = check.get('metadata', {}).get('name', 'unknown')
|
|
status = check.get('status', {})
|
|
|
|
conditions = status.get('conditions', [])
|
|
for cond in conditions:
|
|
if cond.get('type') == 'Reachable' and cond.get('status') == 'False':
|
|
failed_checks.append({
|
|
'name': name,
|
|
'message': cond.get('message', 'Unknown')
|
|
})
|
|
|
|
return {
|
|
'total': total_checks,
|
|
'failed': failed_checks
|
|
}
|
|
|
|
|
|
def print_network_summary(network_type: str, operator_status: Optional[Dict],
|
|
ovn_pods: List[Dict], connectivity: Dict):
|
|
"""Print network analysis summary."""
|
|
print(f"{'NETWORK TYPE':<30} {network_type}")
|
|
print()
|
|
|
|
if operator_status:
|
|
print("NETWORK OPERATOR STATUS")
|
|
print(f"{'Available':<15} {operator_status.get('Available', 'Unknown')}")
|
|
print(f"{'Progressing':<15} {operator_status.get('Progressing', 'Unknown')}")
|
|
print(f"{'Degraded':<15} {operator_status.get('Degraded', 'Unknown')}")
|
|
|
|
if operator_status.get('Degraded') == 'True':
|
|
msg = operator_status.get('Degraded_message', '')
|
|
if msg:
|
|
print(f" Message: {msg}")
|
|
print()
|
|
|
|
if ovn_pods and network_type == 'OVNKubernetes':
|
|
print("OVN-KUBERNETES PODS")
|
|
print(f"{'NAME':<60} {'READY':<10} STATUS")
|
|
for pod in ovn_pods:
|
|
name = pod['name'][:60]
|
|
ready = pod['ready'][:10]
|
|
status = pod['status']
|
|
print(f"{name:<60} {ready:<10} {status}")
|
|
print()
|
|
|
|
if connectivity['total'] > 0:
|
|
print(f"NETWORK CONNECTIVITY CHECKS: {connectivity['total']} total")
|
|
if connectivity['failed']:
|
|
print(f" Failed: {len(connectivity['failed'])}")
|
|
for failed in connectivity['failed'][:10]: # Show first 10
|
|
print(f" - {failed['name']}")
|
|
if failed['message']:
|
|
print(f" {failed['message'][:100]}")
|
|
else:
|
|
print(" All checks passing")
|
|
print()
|
|
|
|
|
|
def analyze_network(must_gather_path: str):
|
|
"""Analyze network resources in a must-gather directory."""
|
|
base_path = Path(must_gather_path)
|
|
|
|
# Get network type
|
|
network_type = get_network_type(base_path)
|
|
|
|
# Get network operator status
|
|
operator_status = analyze_network_operator(base_path)
|
|
|
|
# Get OVN pods if applicable
|
|
ovn_pods = []
|
|
if network_type == 'OVNKubernetes':
|
|
ovn_pods = analyze_ovn_pods(base_path)
|
|
|
|
# Get connectivity checks
|
|
connectivity = analyze_connectivity_checks(base_path)
|
|
|
|
# Print summary
|
|
print_network_summary(network_type, operator_status, ovn_pods, connectivity)
|
|
|
|
return 0
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("Usage: analyze_network.py <must-gather-directory>", file=sys.stderr)
|
|
print("\nExample:", file=sys.stderr)
|
|
print(" analyze_network.py ./must-gather.local.123456789", file=sys.stderr)
|
|
return 1
|
|
|
|
must_gather_path = sys.argv[1]
|
|
|
|
if not os.path.isdir(must_gather_path):
|
|
print(f"Error: Directory not found: {must_gather_path}", file=sys.stderr)
|
|
return 1
|
|
|
|
return analyze_network(must_gather_path)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|