summaryrefslogtreecommitdiff
path: root/robusta_krr/core/models/result.py
blob: 1735125ddd1617a25c3654dc29167ba73b3e6603 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
from __future__ import annotations

from typing import Any, Optional, Union

import pydantic as pd

from robusta_krr.core.abstract import formatters
from robusta_krr.core.models.allocations import RecommendationValue, ResourceAllocations, ResourceType
from robusta_krr.core.models.objects import K8sWorkload
from robusta_krr.core.models.severity import Severity


class Recommendation(pd.BaseModel):
    value: RecommendationValue
    severity: Severity


class ResourceRecommendation(pd.BaseModel):
    requests: dict[ResourceType, RecommendationValue]
    limits: dict[ResourceType, RecommendationValue]
    info: dict[ResourceType, Optional[str]]


class ResourceScan(pd.BaseModel):
    object: K8sWorkload
    recommended: ResourceRecommendation
    severity: Severity

    @classmethod
    def calculate(cls, object: K8sWorkload, recommendation: ResourceAllocations) -> ResourceScan:
        recommendation_processed = ResourceRecommendation(requests={}, limits={}, info={})

        for resource_type in ResourceType:
            recommendation_processed.info[resource_type] = recommendation.info.get(resource_type)

            for selector in ["requests", "limits"]:
                current = getattr(object.allocations, selector).get(resource_type)
                recommended = getattr(recommendation, selector).get(resource_type)

                current_severity = Severity.calculate(current, recommended, resource_type)

                getattr(recommendation_processed, selector)[resource_type] = Recommendation(
                    value=recommended, severity=current_severity
                )

        for severity in [Severity.CRITICAL, Severity.WARNING, Severity.OK, Severity.GOOD, Severity.UNKNOWN]:
            for selector in ["requests", "limits"]:
                for recommendation_request in getattr(recommendation_processed, selector).values():
                    if recommendation_request.severity == severity:
                        return cls(object=object, recommended=recommendation_processed, severity=severity)

        return cls(object=object, recommended=recommendation_processed, severity=Severity.UNKNOWN)


class StrategyData(pd.BaseModel):
    name: str
    settings: dict[str, Any]


class Result(pd.BaseModel):
    scans: list[ResourceScan]
    score: int = 0
    resources: list[str] = ["cpu", "memory"]
    description: Optional[str] = None
    strategy: StrategyData
    errors: list[dict[str, Any]] = pd.Field(default_factory=list)

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.score = self.__calculate_score()

    def format(self, formatter: Union[formatters.FormatterFunc, str]) -> Any:
        """Format the result.

        Args:
            formatter: The formatter to use.

        Returns:
            The formatted result.
        """

        formatter = formatters.find(formatter) if isinstance(formatter, str) else formatter
        return formatter(self)

    @staticmethod
    def __scan_cost(scan: ResourceScan) -> float:
        return 0.7 if scan.severity == Severity.WARNING else 1 if scan.severity == Severity.CRITICAL else 0

    def __calculate_score(self) -> int:
        """Get the score of the result.

        Returns:
            The score of the result.
        """

        score = sum(self.__scan_cost(scan) for scan in self.scans)
        return int((len(self.scans) - score) / len(self.scans) * 100) if self.scans else 0

    @property
    def score_letter(self) -> str:
        return (
            "F"
            if self.score < 30
            else "D"
            if self.score < 55
            else "C"
            if self.score < 70
            else "B"
            if self.score < 90
            else "A"
        )