Refactor: group smart recommendations by type and remove redundant View Details button
This commit is contained in:
@@ -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"""
|
||||||
|
|||||||
@@ -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 = {}
|
||||||
|
|||||||
@@ -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>Namespace:</strong> ${rec.namespace} |
|
`<strong>Workloads:</strong> ${rec.workload_list.length} workloads |
|
||||||
<strong>Type:</strong> ${rec.recommendation_type.replace('_', ' ').toUpperCase()}
|
<strong>Type:</strong> ${rec.recommendation_type.replace('_', ' ').toUpperCase()}` :
|
||||||
|
`<strong>Workload:</strong> ${rec.workload_name} |
|
||||||
|
<strong>Namespace:</strong> ${rec.namespace} |
|
||||||
|
<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()">×</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];
|
||||||
|
|||||||
Reference in New Issue
Block a user