summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorПавел Жуков <33721692+LeaveMyYard@users.noreply.github.com>2023-04-10 17:21:26 +0300
committerПавел Жуков <33721692+LeaveMyYard@users.noreply.github.com>2023-04-10 17:21:26 +0300
commit43618112e45953528cde593275f755881f852d67 (patch)
treed594f2a3264e5beafa6e5910e83a74f02e83af03
parent144aea2ef44271f3cfc773bd3ddfe872c617160e (diff)
Lower the required python version to 3.9, improve verbose logs
-rw-r--r--pyproject.toml2
-rw-r--r--robusta_krr/__init__.py3
-rw-r--r--robusta_krr/core/abstract/formatters.py7
-rw-r--r--robusta_krr/core/abstract/strategies.py12
-rw-r--r--robusta_krr/core/integrations/kubernetes.py19
-rw-r--r--robusta_krr/core/integrations/prometheus.py6
-rw-r--r--robusta_krr/core/models/allocations.py8
-rw-r--r--robusta_krr/core/models/config.py12
-rw-r--r--robusta_krr/core/models/objects.py4
-rw-r--r--robusta_krr/core/models/result.py6
-rw-r--r--robusta_krr/core/runner.py5
-rw-r--r--robusta_krr/formatters/table.py3
-rw-r--r--robusta_krr/utils/configurable.py10
-rw-r--r--robusta_krr/utils/resource_units.py3
-rw-r--r--robusta_krr/utils/service_discovery.py5
15 files changed, 68 insertions, 37 deletions
diff --git a/pyproject.toml b/pyproject.toml
index e3fa7ed..1d0e1fd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,7 @@ include_trailing_comma = true
krr = "robusta_krr.main:run"
[tool.poetry.dependencies]
-python = ">=3.11,<3.12"
+python = ">=3.9"
typer = {extras = ["all"], version = "^0.7.0"}
pydantic = "1.10.7"
kubernetes = "^26.1.0"
diff --git a/robusta_krr/__init__.py b/robusta_krr/__init__.py
index cd50bf6..2564844 100644
--- a/robusta_krr/__init__.py
+++ b/robusta_krr/__init__.py
@@ -1,5 +1,4 @@
-from . import api
from .main import run
__version__ = "0.1.0"
-__all__ = ["run", "api", "__version__"]
+__all__ = ["run", "__version__"]
diff --git a/robusta_krr/core/abstract/formatters.py b/robusta_krr/core/abstract/formatters.py
index 3d0102e..359edab 100644
--- a/robusta_krr/core/abstract/formatters.py
+++ b/robusta_krr/core/abstract/formatters.py
@@ -2,7 +2,7 @@ from __future__ import annotations
import abc
import os
-from typing import TYPE_CHECKING, Any, Self
+from typing import TYPE_CHECKING, Any, TypeVar
if TYPE_CHECKING:
from robusta_krr.core.models.result import Result
@@ -11,6 +11,9 @@ if TYPE_CHECKING:
DEFAULT_FORMATTERS_PATH = os.path.join(os.path.dirname(__file__), "formatters")
+Self = TypeVar("Self", bound="BaseFormatter")
+
+
class BaseFormatter(abc.ABC):
"""Base class for result formatters."""
@@ -31,7 +34,7 @@ class BaseFormatter(abc.ABC):
"""
@classmethod
- def get_all(cls) -> dict[str, type[Self]]:
+ def get_all(cls: type[Self]) -> dict[str, type[Self]]:
"""Get all available formatters."""
# NOTE: Load default formatters
diff --git a/robusta_krr/core/abstract/strategies.py b/robusta_krr/core/abstract/strategies.py
index 9a73177..476779a 100644
--- a/robusta_krr/core/abstract/strategies.py
+++ b/robusta_krr/core/abstract/strategies.py
@@ -3,7 +3,7 @@ from __future__ import annotations
import abc
import datetime
from decimal import Decimal
-from typing import Generic, Self, TypeVar, get_args
+from typing import Generic, Optional, TypeVar, get_args
import pydantic as pd
@@ -11,8 +11,8 @@ from robusta_krr.core.models.result import K8sObjectData, ResourceType
class ResourceRecommendation(pd.BaseModel):
- request: Decimal | None
- limit: Decimal | None
+ request: Optional[Decimal]
+ limit: Optional[Decimal]
class StrategySettings(pd.BaseModel):
@@ -35,6 +35,8 @@ ResourceHistoryData = dict[str, list[Decimal]]
HistoryData = dict[ResourceType, ResourceHistoryData]
RunResult = dict[ResourceType, ResourceRecommendation]
+Self = TypeVar("Self", bound="BaseStrategy")
+
class BaseStrategy(abc.ABC, Generic[_StrategySettings]):
__display_name__: str
@@ -51,7 +53,7 @@ class BaseStrategy(abc.ABC, Generic[_StrategySettings]):
"""Run the strategy to calculate the recommendation"""
@classmethod
- def find(cls, name: str) -> type[Self]:
+ def find(cls: type[Self], name: str) -> type[Self]:
"""Get a strategy from its name."""
strategies = cls.get_all()
@@ -61,7 +63,7 @@ class BaseStrategy(abc.ABC, Generic[_StrategySettings]):
raise ValueError(f"Unknown strategy name: {name}. Available strategies: {', '.join(strategies)}")
@classmethod
- def get_all(cls) -> dict[str, type[Self]]:
+ def get_all(cls: type[Self]) -> dict[str, type[Self]]:
# NOTE: Load default formatters
from robusta_krr import strategies as _ # noqa: F401
diff --git a/robusta_krr/core/integrations/kubernetes.py b/robusta_krr/core/integrations/kubernetes.py
index 607e298..3affe7e 100644
--- a/robusta_krr/core/integrations/kubernetes.py
+++ b/robusta_krr/core/integrations/kubernetes.py
@@ -1,7 +1,8 @@
import asyncio
import itertools
+from typing import Union
-from kubernetes import client, config
+from kubernetes import client, config # type: ignore
from kubernetes.client.models import (
V1Container,
V1DaemonSet,
@@ -69,7 +70,7 @@ class ClusterLoader(Configurable):
return f"{expression.key} {expression.operator} ({values})"
@staticmethod
- def _build_selector_query(selector: V1LabelSelector) -> str | None:
+ def _build_selector_query(selector: V1LabelSelector) -> Union[str, None]:
label_filters = [f"{label[0]}={label[1]}" for label in selector.match_labels.items()]
if selector.match_expressions is not None:
@@ -79,7 +80,7 @@ class ClusterLoader(Configurable):
return ",".join(label_filters)
- async def __list_pods(self, resource: V1Deployment | V1DaemonSet | V1StatefulSet) -> list[str]:
+ async def __list_pods(self, resource: Union[V1Deployment, V1DaemonSet, V1StatefulSet]) -> list[str]:
selector = self._build_selector_query(resource.spec.selector)
if selector is None:
return []
@@ -90,7 +91,7 @@ class ClusterLoader(Configurable):
return [pod.metadata.name for pod in ret.items]
async def __build_obj(
- self, item: V1Deployment | V1DaemonSet | V1StatefulSet, container: V1Container
+ self, item: Union[V1Deployment, V1DaemonSet, V1StatefulSet], container: V1Container
) -> K8sObjectData:
return K8sObjectData(
cluster=self.cluster,
@@ -103,7 +104,9 @@ class ClusterLoader(Configurable):
)
async def _list_deployments(self) -> list[K8sObjectData]:
+ self.debug(f"Listing deployments in {self.cluster}")
ret: V1DeploymentList = await asyncio.to_thread(self.apps.list_deployment_for_all_namespaces, watch=False)
+ self.debug(f"Found {len(ret.items)} deployments in {self.cluster}")
return await asyncio.gather(
*[
@@ -114,7 +117,9 @@ class ClusterLoader(Configurable):
)
async def _list_all_statefulsets(self) -> list[K8sObjectData]:
+ self.debug(f"Listing statefulsets in {self.cluster}")
ret: V1StatefulSetList = await asyncio.to_thread(self.apps.list_stateful_set_for_all_namespaces, watch=False)
+ self.debug(f"Found {len(ret.items)} statefulsets in {self.cluster}")
return await asyncio.gather(
*[
@@ -125,7 +130,9 @@ class ClusterLoader(Configurable):
)
async def _list_all_daemon_set(self) -> list[K8sObjectData]:
+ self.debug(f"Listing daemonsets in {self.cluster}")
ret: V1DaemonSetList = await asyncio.to_thread(self.apps.list_daemon_set_for_all_namespaces, watch=False)
+ self.debug(f"Found {len(ret.items)} daemonsets in {self.cluster}")
return await asyncio.gather(
*[
@@ -136,7 +143,9 @@ class ClusterLoader(Configurable):
)
async def _list_all_jobs(self) -> list[K8sObjectData]:
+ self.debug(f"Listing jobs in {self.cluster}")
ret: V1JobList = await asyncio.to_thread(self.batch.list_job_for_all_namespaces, watch=False)
+ self.debug(f"Found {len(ret.items)} jobs in {self.cluster}")
return await asyncio.gather(
*[
@@ -149,7 +158,9 @@ class ClusterLoader(Configurable):
async def _list_pods(self) -> list[K8sObjectData]:
"""For future use, not supported yet."""
+ self.debug(f"Listing pods in {self.cluster}")
ret: V1PodList = await asyncio.to_thread(self.apps.list_pod_for_all_namespaces, watch=False)
+ self.debug(f"Found {len(ret.items)} pods in {self.cluster}")
return await asyncio.gather(
*[self.__build_obj(item, container) for item in ret.items for container in item.spec.containers]
diff --git a/robusta_krr/core/integrations/prometheus.py b/robusta_krr/core/integrations/prometheus.py
index 16aeaa2..a52dde7 100644
--- a/robusta_krr/core/integrations/prometheus.py
+++ b/robusta_krr/core/integrations/prometheus.py
@@ -1,7 +1,7 @@
import asyncio
import datetime
from decimal import Decimal
-from typing import no_type_check
+from typing import Optional, no_type_check
import requests
from kubernetes import config as k8s_config
@@ -19,7 +19,7 @@ from robusta_krr.utils.service_discovery import ServiceDiscovery
class PrometheusDiscovery(ServiceDiscovery):
- def find_prometheus_url(self, *, api_client: ApiClient | None = None) -> str | None:
+ def find_prometheus_url(self, *, api_client: Optional[ApiClient] = None) -> Optional[str]:
return super().find_url(
selectors=[
"app=kube-prometheus-stack-prometheus",
@@ -58,7 +58,7 @@ class PrometheusLoader(Configurable):
self,
config: Config,
*,
- cluster: str | None = None,
+ cluster: Optional[str] = None,
) -> None:
super().__init__(config=config)
diff --git a/robusta_krr/core/models/allocations.py b/robusta_krr/core/models/allocations.py
index 88c1f06..dd7c94f 100644
--- a/robusta_krr/core/models/allocations.py
+++ b/robusta_krr/core/models/allocations.py
@@ -2,7 +2,7 @@ from __future__ import annotations
import enum
from decimal import Decimal
-from typing import Literal, Self
+from typing import Literal, TypeVar, Union
import pydantic as pd
from kubernetes.client.models import V1Container
@@ -20,7 +20,9 @@ class ResourceType(str, enum.Enum):
Memory = "memory"
-RecommendationValue = Decimal | Literal["?"] | None
+RecommendationValue = Union[Decimal, Literal["?"], None]
+
+Self = TypeVar("Self", bound="ResourceAllocations")
class ResourceAllocations(pd.BaseModel):
@@ -49,7 +51,7 @@ class ResourceAllocations(pd.BaseModel):
}
@classmethod
- def from_container(cls, container: V1Container) -> Self:
+ def from_container(cls: type[Self], container: V1Container) -> Self:
"""Get the resource allocations from a Kubernetes container.
Args:
diff --git a/robusta_krr/core/models/config.py b/robusta_krr/core/models/config.py
index 83b62af..153f9d3 100644
--- a/robusta_krr/core/models/config.py
+++ b/robusta_krr/core/models/config.py
@@ -1,4 +1,4 @@
-from typing import Literal
+from typing import Literal, Optional, Union
import pydantic as pd
@@ -10,8 +10,8 @@ class Config(pd.BaseSettings):
quiet: bool = pd.Field(False)
verbose: bool = pd.Field(False)
- clusters: list[str] | Literal["*"] | None = None
- namespaces: list[str] | Literal["*"] = pd.Field("*")
+ clusters: Union[list[str], Literal["*"], None] = None
+ namespaces: Union[list[str], Literal["*"]] = pd.Field("*")
# Make this True if you are running KRR inside the cluster
inside_cluster: bool = pd.Field(False)
@@ -21,8 +21,8 @@ class Config(pd.BaseSettings):
memory_min_value: int = pd.Field(10, ge=0) # in megabytes
# Prometheus Settings
- prometheus_url: str | None = pd.Field(None)
- prometheus_auth_header: str | None = pd.Field(None)
+ prometheus_url: Optional[str] = pd.Field(None)
+ prometheus_auth_header: Optional[str] = pd.Field(None)
prometheus_ssl_enabled: bool = pd.Field(False)
# Logging Settings
@@ -32,7 +32,7 @@ class Config(pd.BaseSettings):
other_args: list[str] = pd.Field([])
@pd.validator("namespaces")
- def validate_namespaces(cls, v: list[str] | Literal["*"]) -> list[str] | Literal["*"]:
+ def validate_namespaces(cls, v: Union[list[str], Literal["*"]]) -> Union[list[str], Literal["*"]]:
if v == []:
return "*"
diff --git a/robusta_krr/core/models/objects.py b/robusta_krr/core/models/objects.py
index 328e9c8..74f07ac 100644
--- a/robusta_krr/core/models/objects.py
+++ b/robusta_krr/core/models/objects.py
@@ -1,3 +1,5 @@
+from typing import Optional
+
import pydantic as pd
from robusta_krr.core.models.allocations import ResourceAllocations
@@ -9,7 +11,7 @@ class K8sObjectData(pd.BaseModel):
container: str
pods: list[str]
namespace: str
- kind: str | None
+ kind: Optional[str]
allocations: ResourceAllocations
def __str__(self) -> str:
diff --git a/robusta_krr/core/models/result.py b/robusta_krr/core/models/result.py
index a768a52..eeaa565 100644
--- a/robusta_krr/core/models/result.py
+++ b/robusta_krr/core/models/result.py
@@ -2,7 +2,7 @@ from __future__ import annotations
import enum
import itertools
-from typing import Any
+from typing import Any, Union
import pydantic as pd
@@ -11,7 +11,7 @@ from robusta_krr.core.models.allocations import RecommendationValue, ResourceAll
from robusta_krr.core.models.objects import K8sObjectData
-class Severity(enum.StrEnum):
+class Severity(str, enum.Enum):
"""The severity of the scan."""
UNKNOWN = "UNKNOWN"
@@ -81,7 +81,7 @@ class Result(pd.BaseModel):
super().__init__(*args, **kwargs)
self.score = self.__calculate_score()
- def format(self, formatter: type[BaseFormatter] | str, **kwargs: Any) -> Any:
+ def format(self, formatter: Union[type[BaseFormatter], str], **kwargs: Any) -> Any:
"""Format the result.
Args:
diff --git a/robusta_krr/core/runner.py b/robusta_krr/core/runner.py
index 49354fa..7aae3bd 100644
--- a/robusta_krr/core/runner.py
+++ b/robusta_krr/core/runner.py
@@ -1,6 +1,7 @@
import asyncio
import math
from decimal import Decimal
+from typing import Optional, Union
from robusta_krr.core.abstract.strategies import ResourceRecommendation, RunResult
from robusta_krr.core.integrations.kubernetes import KubernetesLoader
@@ -17,7 +18,7 @@ class Runner(Configurable):
def __init__(self, config: Config) -> None:
super().__init__(config)
self._k8s_loader = KubernetesLoader(self.config)
- self._prometheus_loaders: dict[str, PrometheusLoader | Exception] = {}
+ self._prometheus_loaders: dict[str, Union[PrometheusLoader, Exception]] = {}
self._strategy = self.config.create_strategy()
def _get_prometheus_loader(self, cluster: str) -> PrometheusLoader:
@@ -53,7 +54,7 @@ class Runner(Configurable):
else:
return Decimal(0)
- def _round_value(self, value: Decimal | None, resource: ResourceType) -> Decimal | None:
+ def _round_value(self, value: Optional[Decimal], resource: ResourceType) -> Optional[Decimal]:
if value is None:
return value
diff --git a/robusta_krr/formatters/table.py b/robusta_krr/formatters/table.py
index fdc9508..8ea2003 100644
--- a/robusta_krr/formatters/table.py
+++ b/robusta_krr/formatters/table.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import itertools
+from typing import Optional
from rich.table import Table
@@ -20,7 +21,7 @@ class TableFormatter(BaseFormatter):
__display_name__ = "table"
- def _format_united_decimal(self, value: RecommendationValue, prescision: int | None = None) -> str:
+ def _format_united_decimal(self, value: RecommendationValue, prescision: Optional[int] = None) -> str:
if value is None:
return NONE_LITERAL
elif isinstance(value, str):
diff --git a/robusta_krr/utils/configurable.py b/robusta_krr/utils/configurable.py
index d2f1442..3957e6e 100644
--- a/robusta_krr/utils/configurable.py
+++ b/robusta_krr/utils/configurable.py
@@ -1,4 +1,5 @@
import abc
+from inspect import getframeinfo, stack
from typing import Literal
from rich.console import Console
@@ -51,7 +52,14 @@ class Configurable(abc.ABC):
"""
if self.debug_active:
- self.console.print(self.__add_prefix(message, "[bold green][DEBUG][/bold green]", no_prefix=False))
+ caller = getframeinfo(stack()[1][0])
+ self.console.print(
+ self.__add_prefix(
+ message + f"\t\t({caller.filename}:{caller.lineno})",
+ "[bold green][DEBUG][/bold green]",
+ no_prefix=False,
+ )
+ )
def debug_exception(self) -> None:
"""
diff --git a/robusta_krr/utils/resource_units.py b/robusta_krr/utils/resource_units.py
index 37a3646..d80ac05 100644
--- a/robusta_krr/utils/resource_units.py
+++ b/robusta_krr/utils/resource_units.py
@@ -1,4 +1,5 @@
from decimal import Decimal
+from typing import Optional
UNITS = {
"m": Decimal("1e-3"),
@@ -25,7 +26,7 @@ def parse(x: str) -> Decimal:
return Decimal(x)
-def format(x: Decimal, prescision: int | None = None) -> str:
+def format(x: Decimal, prescision: Optional[int] = None) -> str:
"""Converts an integer to a string with respect of units."""
if prescision is not None:
diff --git a/robusta_krr/utils/service_discovery.py b/robusta_krr/utils/service_discovery.py
index 8c498fe..f341225 100644
--- a/robusta_krr/utils/service_discovery.py
+++ b/robusta_krr/utils/service_discovery.py
@@ -1,4 +1,5 @@
import logging
+from typing import Optional
from cachetools import TTLCache
from kubernetes import client
@@ -13,7 +14,7 @@ class ServiceDiscovery(Configurable):
SERVICE_CACHE_TTL_SEC = 900
cache: TTLCache = TTLCache(maxsize=1, ttl=SERVICE_CACHE_TTL_SEC)
- def find_service_url(self, label_selector: str, *, api_client: ApiClient | None = None) -> str | None:
+ def find_service_url(self, label_selector: str, *, api_client: Optional[ApiClient] = None) -> Optional[str]:
"""
Get the url of an in-cluster service with a specific label
"""
@@ -36,7 +37,7 @@ class ServiceDiscovery(Configurable):
return None
- def find_url(self, selectors: list[str], *, api_client: ApiClient | None = None) -> str | None:
+ def find_url(self, selectors: list[str], *, api_client: Optional[ApiClient] = None) -> Optional[str]:
"""
Try to autodiscover the url of an in-cluster service
"""