Refactor: group smart recommendations by type and remove redundant View Details button

This commit is contained in:
2025-10-02 10:15:51 -03:00
parent 91e68b79c7
commit 943fe4fcac
3 changed files with 186 additions and 77 deletions

View File

@@ -115,6 +115,7 @@ class SmartRecommendation(BaseModel):
implementation_steps: Optional[List[str]] = None implementation_steps: Optional[List[str]] = None
kubectl_commands: Optional[List[str]] = None kubectl_commands: Optional[List[str]] = None
vpa_yaml: Optional[str] = None vpa_yaml: Optional[str] = None
workload_list: Optional[List[str]] = None # List of workloads for grouped recommendations
class QoSClassification(BaseModel): class QoSClassification(BaseModel):
"""QoS (Quality of Service) classification""" """QoS (Quality of Service) classification"""

View File

@@ -63,7 +63,10 @@ class SmartRecommendationsService:
categories: List[WorkloadCategory] categories: List[WorkloadCategory]
) -> List[SmartRecommendation]: ) -> List[SmartRecommendation]:
"""Generate smart recommendations based on workload analysis""" """Generate smart recommendations based on workload analysis"""
recommendations = [] # Group workloads by recommendation type
vpa_workloads = []
resource_config_workloads = []
ratio_adjustment_workloads = []
for category in categories: for category in categories:
workload_pods = [p for p in pods if self._extract_workload_name(p.name) == category.workload_name and p.namespace == category.namespace] workload_pods = [p for p in pods if self._extract_workload_name(p.name) == category.workload_name and p.namespace == category.namespace]
@@ -71,17 +74,173 @@ class SmartRecommendationsService:
if not workload_pods: if not workload_pods:
continue continue
# Generate recommendations based on category # Categorize workloads by recommendation type
workload_recommendations = await self._generate_workload_recommendations( if category.vpa_candidate:
category, workload_pods vpa_workloads.append((category, workload_pods))
) elif category.resource_config_status == "missing_requests":
recommendations.extend(workload_recommendations) resource_config_workloads.append((category, workload_pods))
elif category.resource_config_status == "missing_limits":
resource_config_workloads.append((category, workload_pods))
elif category.resource_config_status == "suboptimal_ratio":
ratio_adjustment_workloads.append((category, workload_pods))
# Generate grouped recommendations
recommendations = []
# VPA Activation group
if vpa_workloads:
recommendations.append(self._create_grouped_vpa_recommendation(vpa_workloads))
# Resource Config group
if resource_config_workloads:
recommendations.append(self._create_grouped_resource_config_recommendation(resource_config_workloads))
# Ratio Adjustment group
if ratio_adjustment_workloads:
recommendations.append(self._create_grouped_ratio_adjustment_recommendation(ratio_adjustment_workloads))
# Sort by priority # Sort by priority
recommendations.sort(key=lambda x: self._get_priority_score(x.priority), reverse=True) recommendations.sort(key=lambda x: self._get_priority_score(x.priority), reverse=True)
return recommendations return recommendations
def _create_grouped_vpa_recommendation(self, vpa_workloads: List[tuple]) -> SmartRecommendation:
"""Create grouped VPA activation recommendation"""
# Sort workloads by priority
vpa_workloads.sort(key=lambda x: self._get_priority_score(x[0].estimated_impact), reverse=True)
# Get highest priority
highest_priority = vpa_workloads[0][0].estimated_impact
# Create workload list
workload_list = []
for category, _ in vpa_workloads:
workload_list.append(f"{category.workload_name} ({category.namespace}) - {category.estimated_impact.upper()}")
# Generate consolidated VPA YAML
vpa_yaml = self._generate_consolidated_vpa_yaml(vpa_workloads)
return SmartRecommendation(
workload_name="Multiple Workloads",
namespace="Multiple",
recommendation_type="vpa_activation",
priority=highest_priority,
title="Activate VPA for Workloads",
description=f"Enable VPA for {len(vpa_workloads)} workloads to get automatic resource recommendations based on usage patterns.",
confidence_level=0.8,
estimated_impact=highest_priority,
implementation_steps=[
"Create VPA resources for all workloads",
"Set updateMode to 'Off' for recommendation-only mode",
"Monitor VPA recommendations for 24-48 hours",
"Apply recommended values when confident"
],
kubectl_commands=[
"oc apply -f vpa-workloads-batch.yaml"
],
vpa_yaml=vpa_yaml,
workload_list=workload_list
)
def _create_grouped_resource_config_recommendation(self, resource_workloads: List[tuple]) -> SmartRecommendation:
"""Create grouped resource configuration recommendation"""
# Sort workloads by priority
resource_workloads.sort(key=lambda x: self._get_priority_score(x[0].estimated_impact), reverse=True)
# Get highest priority
highest_priority = resource_workloads[0][0].estimated_impact
# Create workload list
workload_list = []
for category, _ in resource_workloads:
workload_list.append(f"{category.workload_name} ({category.namespace}) - {category.estimated_impact.upper()}")
return SmartRecommendation(
workload_name="Multiple Workloads",
namespace="Multiple",
recommendation_type="resource_config",
priority=highest_priority,
title="Configure Resource Requests/Limits",
description=f"Define proper resource requests and limits for {len(resource_workloads)} workloads to ensure QoS and prevent resource conflicts.",
confidence_level=0.9,
estimated_impact=highest_priority,
implementation_steps=[
"Analyze current resource usage for all workloads",
"Set CPU requests based on P95 usage + 20% buffer",
"Set memory requests based on P95 usage + 20% buffer",
"Set limits with 3:1 ratio to requests",
"Update deployments with new resource configuration"
],
kubectl_commands=[
"oc patch deployment <workload-name> -n <namespace> -p '{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"<container-name>\",\"resources\":{\"requests\":{\"cpu\":\"200m\",\"memory\":\"512Mi\"},\"limits\":{\"cpu\":\"600m\",\"memory\":\"1536Mi\"}}}]}}}}'"
],
workload_list=workload_list
)
def _create_grouped_ratio_adjustment_recommendation(self, ratio_workloads: List[tuple]) -> SmartRecommendation:
"""Create grouped ratio adjustment recommendation"""
# Sort workloads by priority
ratio_workloads.sort(key=lambda x: self._get_priority_score(x[0].estimated_impact), reverse=True)
# Get highest priority
highest_priority = ratio_workloads[0][0].estimated_impact
# Create workload list
workload_list = []
for category, _ in ratio_workloads:
workload_list.append(f"{category.workload_name} ({category.namespace}) - {category.estimated_impact.upper()}")
return SmartRecommendation(
workload_name="Multiple Workloads",
namespace="Multiple",
recommendation_type="ratio_adjustment",
priority=highest_priority,
title="Optimize Resource Ratios",
description=f"Optimize CPU and memory limit:request ratios for {len(ratio_workloads)} workloads to follow best practices (3:1 ratio).",
confidence_level=0.8,
estimated_impact=highest_priority,
implementation_steps=[
"Analyze current resource ratios for all workloads",
"Adjust limits to maintain 3:1 ratio with requests",
"Test with updated ratios in staging environment",
"Apply changes to production"
],
kubectl_commands=[
"oc patch deployment <workload-name> -n <namespace> -p '{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"<container-name>\",\"resources\":{\"requests\":{\"cpu\":\"200m\",\"memory\":\"512Mi\"},\"limits\":{\"cpu\":\"600m\",\"memory\":\"1536Mi\"}}}]}}}}'"
],
workload_list=workload_list
)
def _generate_consolidated_vpa_yaml(self, vpa_workloads: List[tuple]) -> str:
"""Generate consolidated VPA YAML for multiple workloads"""
yaml_parts = []
for category, _ in vpa_workloads:
yaml_parts.append(f"""---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: {category.workload_name}-vpa
namespace: {category.namespace}
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: {category.workload_name}
updatePolicy:
updateMode: "Off" # Recommendation only
resourcePolicy:
containerPolicies:
- containerName: {category.workload_name}
maxAllowed:
cpu: 2
memory: 4Gi
minAllowed:
cpu: 100m
memory: 128Mi""")
return "\n".join(yaml_parts)
def _group_pods_by_workload(self, pods: List[PodResource]) -> Dict[str, List[PodResource]]: def _group_pods_by_workload(self, pods: List[PodResource]) -> Dict[str, List[PodResource]]:
"""Group pods by workload (deployment) name""" """Group pods by workload (deployment) name"""
workloads = {} workloads = {}

View File

@@ -1569,6 +1569,9 @@
'low': '⚪' 'low': '⚪'
}[rec.priority] || '⚪'; }[rec.priority] || '⚪';
// Check if this is a grouped recommendation
const isGrouped = rec.workload_name === "Multiple Workloads" && rec.workload_list;
html += ` html += `
<div class="recommendation-card" data-priority="${rec.priority}" data-type="${rec.recommendation_type}" <div class="recommendation-card" data-priority="${rec.priority}" data-type="${rec.recommendation_type}"
style="border: 1px solid #ddd; border-radius: 8px; margin-bottom: 1rem; overflow: hidden; background: white;"> style="border: 1px solid #ddd; border-radius: 8px; margin-bottom: 1rem; overflow: hidden; background: white;">
@@ -1576,9 +1579,13 @@
<div> <div>
<h3 style="margin: 0 0 0.5rem 0; color: #2c3e50;">${priorityIcon} ${rec.title}</h3> <h3 style="margin: 0 0 0.5rem 0; color: #2c3e50;">${priorityIcon} ${rec.title}</h3>
<p style="margin: 0; color: #7f8c8d; font-size: 0.9rem;"> <p style="margin: 0; color: #7f8c8d; font-size: 0.9rem;">
<strong>Workload:</strong> ${rec.workload_name} | ${isGrouped ?
`<strong>Workloads:</strong> ${rec.workload_list.length} workloads |
<strong>Type:</strong> ${rec.recommendation_type.replace('_', ' ').toUpperCase()}` :
`<strong>Workload:</strong> ${rec.workload_name} |
<strong>Namespace:</strong> ${rec.namespace} | <strong>Namespace:</strong> ${rec.namespace} |
<strong>Type:</strong> ${rec.recommendation_type.replace('_', ' ').toUpperCase()} <strong>Type:</strong> ${rec.recommendation_type.replace('_', ' ').toUpperCase()}`
}
</p> </p>
</div> </div>
<div style="text-align: right;"> <div style="text-align: right;">
@@ -1591,6 +1598,15 @@
<div class="recommendation-body" style="padding: 1rem;"> <div class="recommendation-body" style="padding: 1rem;">
<p style="margin: 0 0 1rem 0; color: #2c3e50;">${rec.description}</p> <p style="margin: 0 0 1rem 0; color: #2c3e50;">${rec.description}</p>
${isGrouped && rec.workload_list ? `
<div style="margin-bottom: 1rem;">
<h4 style="color: #2c3e50; margin-bottom: 0.5rem; font-size: 1rem;">Affected Workloads:</h4>
<ul style="margin: 0; padding-left: 1.5rem; color: #555;">
${rec.workload_list.map(workload => `<li style="margin-bottom: 0.25rem;">${workload}</li>`).join('')}
</ul>
</div>
` : ''}
${rec.implementation_steps ? ` ${rec.implementation_steps ? `
<div style="margin-bottom: 1rem;"> <div style="margin-bottom: 1rem;">
<h4 style="color: #2c3e50; margin-bottom: 0.5rem; font-size: 1rem;">Implementation Steps:</h4> <h4 style="color: #2c3e50; margin-bottom: 0.5rem; font-size: 1rem;">Implementation Steps:</h4>
@@ -1610,14 +1626,10 @@
` : ''} ` : ''}
<div style="display: flex; gap: 0.5rem; margin-top: 1rem;"> <div style="display: flex; gap: 0.5rem; margin-top: 1rem;">
<button onclick="showRecommendationDetails(${index})"
style="padding: 0.5rem 1rem; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9rem;">
📋 View Details
</button>
${rec.vpa_yaml ? ` ${rec.vpa_yaml ? `
<button onclick="showVPAYaml(${index})" <button onclick="showVPAYaml(${index})"
style="padding: 0.5rem 1rem; background: #9b59b6; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9rem;"> style="padding: 0.5rem 1rem; background: #9b59b6; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9rem;">
📄 VPA YAML 📄 Generate VPA YAML
</button> </button>
` : ''} ` : ''}
</div> </div>
@@ -2036,62 +2048,6 @@
loadSmartRecommendations(); loadSmartRecommendations();
} }
function showRecommendationDetails(index) {
const rec = window.currentRecommendations[index];
if (!rec) return;
let modalContent = `
<div class="modal-header" style="background: #f8f9fa; color: #2c3e50; padding: 2rem; position: relative; border-bottom: 1px solid #dee2e6;">
<h2 style="margin: 0; font-size: 1.5rem; font-weight: 600;">📋 Recommendation Details</h2>
<span class="close" onclick="closeModal()">&times;</span>
</div>
<div class="modal-body" style="padding: 2rem; max-height: 70vh; overflow-y: auto;">
<div style="margin-bottom: 2rem;">
<h3 style="color: #2c3e50; margin-bottom: 1rem;">${rec.title}</h3>
<div style="background: #f8f9fa; padding: 1rem; border-radius: 8px; margin-bottom: 1rem;">
<p><strong>Workload:</strong> ${rec.workload_name}</p>
<p><strong>Namespace:</strong> ${rec.namespace}</p>
<p><strong>Type:</strong> ${rec.recommendation_type.replace('_', ' ').toUpperCase()}</p>
<p><strong>Priority:</strong> <span style="background: ${getPriorityColor(rec.priority)}; color: white; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8rem;">${rec.priority.toUpperCase()}</span></p>
${rec.confidence_level ? `<p><strong>Confidence:</strong> ${Math.round(rec.confidence_level * 100)}%</p>` : ''}
</div>
<h4 style="color: #2c3e50; margin-bottom: 1rem;">Description</h4>
<p style="margin-bottom: 2rem; color: #555;">${rec.description}</p>
${rec.implementation_steps ? `
<h4 style="color: #2c3e50; margin-bottom: 1rem;">Implementation Steps</h4>
<ol style="margin-bottom: 2rem; padding-left: 1.5rem; color: #555;">
${rec.implementation_steps.map(step => `<li style="margin-bottom: 0.5rem;">${step}</li>`).join('')}
</ol>
` : ''}
${rec.kubectl_commands && rec.kubectl_commands.length > 0 ? `
<h4 style="color: #2c3e50; margin-bottom: 1rem;">OpenShift Commands</h4>
<div style="background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; padding: 1rem; margin-bottom: 2rem; font-family: 'Courier New', monospace; font-size: 0.9rem;">
${rec.kubectl_commands.map(cmd => `<div style="margin-bottom: 0.5rem;">${cmd}</div>`).join('')}
</div>
<div style="background: #e3f2fd; border: 1px solid #2196f3; border-radius: 4px; padding: 1rem; margin-bottom: 2rem;">
<p style="margin: 0; color: #1565c0; font-size: 0.9rem;">
<strong>💡 Note:</strong> To get the VPA YAML file, click the "📄 VPA YAML" button in the recommendation card.
</p>
</div>
` : ''}
<div style="text-align: center; margin-top: 2rem;">
<button onclick="copyRecommendationCommands(${index})" style="padding: 0.75rem 1.5rem; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 1rem;">
📋 Copy Commands
</button>
<button onclick="closeModal()" style="padding: 0.75rem 1.5rem; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer;">
Close
</button>
</div>
</div>
</div>
`;
showModal(modalContent);
}
function showVPAYaml(index) { function showVPAYaml(index) {
const rec = window.currentRecommendations[index]; const rec = window.currentRecommendations[index];
@@ -2123,13 +2079,6 @@ ${rec.vpa_yaml}
showModal(modalContent); showModal(modalContent);
} }
function copyRecommendationCommands(index) {
const rec = window.currentRecommendations[index];
if (!rec || !rec.kubectl_commands) return;
const commands = rec.kubectl_commands.join('\n');
copyToClipboard(commands);
}
function copyVPAYaml(index) { function copyVPAYaml(index) {
const rec = window.currentRecommendations[index]; const rec = window.currentRecommendations[index];