diff --git a/linode_api4/objects/monitor.py b/linode_api4/objects/monitor.py index ed6ce79a5..1888fa7ce 100644 --- a/linode_api4/objects/monitor.py +++ b/linode_api4/objects/monitor.py @@ -49,6 +49,7 @@ class ServiceType(StrEnum): firewall = "firewall" object_storage = "object_storage" aclb = "aclb" + netloadbalancer = "netloadbalancer" class MetricType(StrEnum): @@ -82,6 +83,10 @@ class MetricUnit(StrEnum): RATIO = "ratio" OPS_PER_SECOND = "ops_per_second" IOPS = "iops" + KILO_BYTES_PER_SECOND = "kilo_bytes_per_second" + SESSIONS_PER_SECOND = "sessions_per_second" + PACKETS_PER_SECOND = "packets_per_second" + KILO_BITS_PER_SECOND = "kilo_bits_per_second" class DashboardType(StrEnum): @@ -93,6 +98,17 @@ class DashboardType(StrEnum): custom = "custom" +@dataclass +class Filter(JSONObject): + """ + Represents a filter in the filters list of a dashboard widget. + """ + + dimension_label: str = "" + operator: str = "" + value: str = "" + + @dataclass class DashboardWidget(JSONObject): """ @@ -107,6 +123,19 @@ class DashboardWidget(JSONObject): chart_type: ChartType = "" y_label: str = "" aggregate_function: AggregateFunction = "" + group_by: list[str] = field(default_factory=list) + filters: list[Filter] | None = None + + +@dataclass +class ServiceAlert(JSONObject): + """ + Represents alert configuration options for a monitor service. + """ + + polling_interval_seconds: list[int] = field(default_factory=list) + evaluation_period_seconds: list[int] = field(default_factory=list) + scope: list[str] = field(default_factory=list) @dataclass @@ -115,9 +144,9 @@ class Dimension(JSONObject): Represents a single dimension in the dimensions list. """ - dimension_label: Optional[str] = None - label: Optional[str] = None - values: Optional[List[str]] = None + dimension_label: str | None = None + label: str | None = None + values: list[str] | None = None @dataclass @@ -134,8 +163,8 @@ class MonitorMetricsDefinition(JSONObject): unit: MetricUnit = "" scrape_interval: int = 0 is_alertable: bool = False - dimensions: Optional[List[Dimension]] = None - available_aggregate_functions: List[AggregateFunction] = field( + dimensions: list[Dimension] | None = None + available_aggregate_functions: list[AggregateFunction] = field( default_factory=list ) @@ -154,7 +183,7 @@ class MonitorDashboard(Base): "label": Property(), "service_type": Property(ServiceType), "type": Property(DashboardType), - "widgets": Property(List[DashboardWidget]), + "widgets": Property(list[DashboardWidget]), "updated": Property(is_datetime=True), } @@ -171,6 +200,7 @@ class MonitorService(Base): properties = { "service_type": Property(ServiceType), "label": Property(), + "alert": Property(json_object=ServiceAlert), } diff --git a/linode_api4/objects/region.py b/linode_api4/objects/region.py index 34577c336..c7ba16b70 100644 --- a/linode_api4/objects/region.py +++ b/linode_api4/objects/region.py @@ -16,6 +16,24 @@ class RegionPlacementGroupLimits(JSONObject): maximum_linodes_per_pg: int = 0 +@dataclass +class RegionMonitors(JSONObject): + """ + Represents the monitor services available in a region. + Lists the services in this region that support metrics and alerts + use with Akamai Cloud Pulse (ACLP). + """ + + alerts: list[str] | None = None + metrics: list[str] | None = None + + def __post_init__(self): + if self.alerts is None: + self.alerts = [] + if self.metrics is None: + self.metrics = [] + + class Region(Base): """ A Region. Regions correspond to individual data centers, each located in a different geographical area. @@ -35,10 +53,11 @@ class Region(Base): "placement_group_limits": Property( json_object=RegionPlacementGroupLimits ), + "monitors": Property(json_object=RegionMonitors), } @property - def availability(self) -> List["RegionAvailabilityEntry"]: + def availability(self) -> list["RegionAvailabilityEntry"]: result = self._client.get( f"{self.api_endpoint}/availability", model=self ) @@ -59,6 +78,6 @@ class RegionAvailabilityEntry(JSONObject): API Documentation: https://techdocs.akamai.com/linode-api/reference/get-region-availability """ - region: Optional[str] = None - plan: Optional[str] = None + region: str | None = None + plan: str | None = None available: bool = False diff --git a/test/fixtures/monitor_dashboards.json b/test/fixtures/monitor_dashboards.json index 42de92b55..5e56923a1 100644 --- a/test/fixtures/monitor_dashboards.json +++ b/test/fixtures/monitor_dashboards.json @@ -16,7 +16,9 @@ "metric": "cpu_usage", "size": 12, "unit": "%", - "y_label": "cpu_usage" + "y_label": "cpu_usage", + "group_by": ["entity_id"], + "filters": null }, { "aggregate_function": "sum", @@ -26,7 +28,9 @@ "metric": "write_iops", "size": 6, "unit": "IOPS", - "y_label": "write_iops" + "y_label": "write_iops", + "group_by": ["entity_id"], + "filters": null } ] } diff --git a/test/fixtures/monitor_dashboards_1.json b/test/fixtures/monitor_dashboards_1.json index b78bf3447..afb5d71ee 100644 --- a/test/fixtures/monitor_dashboards_1.json +++ b/test/fixtures/monitor_dashboards_1.json @@ -14,7 +14,9 @@ "metric": "cpu_usage", "size": 12, "unit": "%", - "y_label": "cpu_usage" + "y_label": "cpu_usage", + "group_by": ["entity_id"], + "filters": null }, { "aggregate_function": "sum", @@ -24,7 +26,9 @@ "metric": "available_memory", "size": 6, "unit": "GB", - "y_label": "available_memory" + "y_label": "available_memory", + "group_by": ["entity_id"], + "filters": null } ] } \ No newline at end of file diff --git a/test/fixtures/monitor_services_dbaas_dashboards.json b/test/fixtures/monitor_services_dbaas_dashboards.json index 5fbb7e9db..e39a231b2 100644 --- a/test/fixtures/monitor_services_dbaas_dashboards.json +++ b/test/fixtures/monitor_services_dbaas_dashboards.json @@ -16,7 +16,9 @@ "metric": "cpu_usage", "size": 12, "unit": "%", - "y_label": "cpu_usage" + "y_label": "cpu_usage", + "group_by": ["entity_id"], + "filters": null }, { "aggregate_function": "sum", @@ -26,7 +28,16 @@ "metric": "memory_usage", "size": 6, "unit": "%", - "y_label": "memory_usage" + "y_label": "memory_usage", + "group_by": ["entity_id"], + "filters": [ + { + "dimension_label": "pattern", + "operator": "in", + "value": "publicout,privateout" + } + ] + } ] } diff --git a/test/fixtures/regions.json b/test/fixtures/regions.json index 5fe55e200..c3f192d47 100644 --- a/test/fixtures/regions.json +++ b/test/fixtures/regions.json @@ -125,6 +125,14 @@ "Block Storage", "Object Storage" ], + "monitors": { + "alerts": [ + "Managed Databases" + ], + "metrics": [ + "Managed Databases" + ] + }, "status": "ok", "resolvers": { "ipv4": "66.228.42.5,96.126.106.5,50.116.53.5,50.116.58.5,50.116.61.5,50.116.62.5,66.175.211.5,97.107.133.4,207.192.69.4,207.192.69.5", diff --git a/test/unit/objects/monitor_test.py b/test/unit/objects/monitor_test.py index a010514c2..86f39a6ca 100644 --- a/test/unit/objects/monitor_test.py +++ b/test/unit/objects/monitor_test.py @@ -41,6 +41,8 @@ def test_dashboard_by_ID(self): self.assertEqual(dashboard.widgets[0].size, 12) self.assertEqual(dashboard.widgets[0].unit, "%") self.assertEqual(dashboard.widgets[0].y_label, "cpu_usage") + self.assertEqual(dashboard.widgets[0].group_by, ["entity_id"]) + self.assertIsNone(dashboard.widgets[0].filters) def test_dashboard_by_service_type(self): dashboards = self.client.monitor.dashboards(service_type="dbaas") @@ -62,6 +64,21 @@ def test_dashboard_by_service_type(self): self.assertEqual(dashboards[0].widgets[0].size, 12) self.assertEqual(dashboards[0].widgets[0].unit, "%") self.assertEqual(dashboards[0].widgets[0].y_label, "cpu_usage") + self.assertEqual(dashboards[0].widgets[0].group_by, ["entity_id"]) + self.assertIsNone(dashboards[0].widgets[0].filters) + + # Test the second widget which has filters + self.assertEqual(dashboards[0].widgets[1].label, "Memory Usage") + self.assertEqual(dashboards[0].widgets[1].group_by, ["entity_id"]) + self.assertIsNotNone(dashboards[0].widgets[1].filters) + self.assertEqual(len(dashboards[0].widgets[1].filters), 1) + self.assertEqual( + dashboards[0].widgets[1].filters[0].dimension_label, "pattern" + ) + self.assertEqual(dashboards[0].widgets[1].filters[0].operator, "in") + self.assertEqual( + dashboards[0].widgets[1].filters[0].value, "publicout,privateout" + ) def test_get_all_dashboards(self): dashboards = self.client.monitor.dashboards() @@ -83,12 +100,20 @@ def test_get_all_dashboards(self): self.assertEqual(dashboards[0].widgets[0].size, 12) self.assertEqual(dashboards[0].widgets[0].unit, "%") self.assertEqual(dashboards[0].widgets[0].y_label, "cpu_usage") + self.assertEqual(dashboards[0].widgets[0].group_by, ["entity_id"]) + self.assertIsNone(dashboards[0].widgets[0].filters) def test_specific_service_details(self): data = self.client.load(MonitorService, "dbaas") self.assertEqual(data.label, "Databases") self.assertEqual(data.service_type, "dbaas") + # Test alert configuration + self.assertIsNotNone(data.alert) + self.assertEqual(data.alert.polling_interval_seconds, [300]) + self.assertEqual(data.alert.evaluation_period_seconds, [300]) + self.assertEqual(data.alert.scope, ["entity"]) + def test_metric_definitions(self): metrics = self.client.monitor.metric_definitions(service_type="dbaas") @@ -96,16 +121,16 @@ def test_metric_definitions(self): metrics[0].available_aggregate_functions, ["max", "avg", "min", "sum"], ) - self.assertEqual(metrics[0].is_alertable, True) + self.assertTrue(metrics[0].is_alertable) self.assertEqual(metrics[0].label, "CPU Usage") self.assertEqual(metrics[0].metric, "cpu_usage") self.assertEqual(metrics[0].metric_type, "gauge") self.assertEqual(metrics[0].scrape_interval, "60s") self.assertEqual(metrics[0].unit, "percent") - self.assertEqual(metrics[0].dimensions[0].dimension_label, "node_type") - self.assertEqual(metrics[0].dimensions[0].label, "Node Type") + self.assertEqual(metrics[0].dimensions[0]["dimension_label"], "node_type") + self.assertEqual(metrics[0].dimensions[0]["label"], "Node Type") self.assertEqual( - metrics[0].dimensions[0].values, ["primary", "secondary"] + metrics[0].dimensions[0]["values"], ["primary", "secondary"] ) def test_create_token(self): diff --git a/test/unit/objects/region_test.py b/test/unit/objects/region_test.py index 0bc1afa9e..f49bf2cc2 100644 --- a/test/unit/objects/region_test.py +++ b/test/unit/objects/region_test.py @@ -28,6 +28,11 @@ def test_get_region(self): region.placement_group_limits.maximum_linodes_per_pg, 5 ) + # Test monitors section + self.assertIsNotNone(region.monitors) + self.assertEqual(region.monitors.alerts, ["Managed Databases"]) + self.assertEqual(region.monitors.metrics, ["Managed Databases"]) + def test_region_availability(self): """ Tests that availability for a specific region can be listed and filtered on.