Skip to content

Commit cf84a40

Browse files
authored
Merge branch 'dev' into dev
2 parents e4aae22 + e2652d8 commit cf84a40

File tree

9 files changed

+175
-11
lines changed

9 files changed

+175
-11
lines changed

linode_api4/objects/monitor.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class ServiceType(StrEnum):
5858
firewall = "firewall"
5959
object_storage = "object_storage"
6060
aclb = "aclb"
61+
net_load_balancer = "netloadbalancer"
6162

6263

6364
class MetricType(StrEnum):
@@ -100,6 +101,10 @@ class MetricUnit(StrEnum):
100101
RATIO = "ratio"
101102
OPS_PER_SECOND = "ops_per_second"
102103
IOPS = "iops"
104+
KILO_BYTES_PER_SECOND = "kilo_bytes_per_second"
105+
SESSIONS_PER_SECOND = "sessions_per_second"
106+
PACKETS_PER_SECOND = "packets_per_second"
107+
KILO_BITS_PER_SECOND = "kilo_bits_per_second"
103108

104109

105110
class DashboardType(StrEnum):
@@ -111,6 +116,17 @@ class DashboardType(StrEnum):
111116
custom = "custom"
112117

113118

119+
@dataclass
120+
class Filter(JSONObject):
121+
"""
122+
Represents a filter in the filters list of a dashboard widget.
123+
"""
124+
125+
dimension_label: str = ""
126+
operator: str = ""
127+
value: str = ""
128+
129+
114130
@dataclass
115131
class DashboardWidget(JSONObject):
116132
"""
@@ -125,6 +141,34 @@ class DashboardWidget(JSONObject):
125141
chart_type: ChartType = ""
126142
y_label: str = ""
127143
aggregate_function: AggregateFunction = ""
144+
group_by: Optional[List[str]] = None
145+
_filters: Optional[List[Filter]] = field(
146+
default=None, metadata={"json_key": "filters"}
147+
)
148+
149+
def __getattribute__(self, name):
150+
"""Override to handle the filters attribute specifically to avoid metaclass conflict."""
151+
if name == "filters":
152+
return object.__getattribute__(self, "_filters")
153+
return object.__getattribute__(self, name)
154+
155+
def __setattr__(self, name, value):
156+
"""Override to handle setting the filters attribute."""
157+
if name == "filters":
158+
object.__setattr__(self, "_filters", value)
159+
else:
160+
object.__setattr__(self, name, value)
161+
162+
163+
@dataclass
164+
class ServiceAlert(JSONObject):
165+
"""
166+
Represents alert configuration options for a monitor service.
167+
"""
168+
169+
polling_interval_seconds: Optional[List[int]] = None
170+
evaluation_period_seconds: Optional[List[int]] = None
171+
scope: Optional[List[str]] = None
128172

129173

130174
@dataclass
@@ -153,9 +197,7 @@ class MonitorMetricsDefinition(JSONObject):
153197
scrape_interval: int = 0
154198
is_alertable: bool = False
155199
dimensions: Optional[List[Dimension]] = None
156-
available_aggregate_functions: List[AggregateFunction] = field(
157-
default_factory=list
158-
)
200+
available_aggregate_functions: Optional[List[AggregateFunction]] = None
159201

160202

161203
class MonitorDashboard(Base):
@@ -172,7 +214,7 @@ class MonitorDashboard(Base):
172214
"label": Property(),
173215
"service_type": Property(ServiceType),
174216
"type": Property(DashboardType),
175-
"widgets": Property(List[DashboardWidget]),
217+
"widgets": Property(json_object=DashboardWidget),
176218
"updated": Property(is_datetime=True),
177219
}
178220

@@ -189,6 +231,7 @@ class MonitorService(Base):
189231
properties = {
190232
"service_type": Property(ServiceType),
191233
"label": Property(),
234+
"alert": Property(json_object=ServiceAlert),
192235
}
193236

194237

linode_api4/objects/region.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ class RegionPlacementGroupLimits(JSONObject):
1616
maximum_linodes_per_pg: int = 0
1717

1818

19+
@dataclass
20+
class RegionMonitors(JSONObject):
21+
"""
22+
Represents the monitor services available in a region.
23+
Lists the services in this region that support metrics and alerts
24+
use with Akamai Cloud Pulse (ACLP).
25+
"""
26+
27+
alerts: Optional[list[str]] = None
28+
metrics: Optional[list[str]] = None
29+
30+
1931
class Region(Base):
2032
"""
2133
A Region. Regions correspond to individual data centers, each located in a different geographical area.
@@ -35,6 +47,7 @@ class Region(Base):
3547
"placement_group_limits": Property(
3648
json_object=RegionPlacementGroupLimits
3749
),
50+
"monitors": Property(json_object=RegionMonitors),
3851
}
3952

4053
@property

test/fixtures/monitor_dashboards.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"metric": "cpu_usage",
1717
"size": 12,
1818
"unit": "%",
19-
"y_label": "cpu_usage"
19+
"y_label": "cpu_usage",
20+
"group_by": ["entity_id"],
21+
"filters": null
2022
},
2123
{
2224
"aggregate_function": "sum",
@@ -26,7 +28,9 @@
2628
"metric": "write_iops",
2729
"size": 6,
2830
"unit": "IOPS",
29-
"y_label": "write_iops"
31+
"y_label": "write_iops",
32+
"group_by": ["entity_id"],
33+
"filters": null
3034
}
3135
]
3236
}

test/fixtures/monitor_dashboards_1.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
"metric": "cpu_usage",
1515
"size": 12,
1616
"unit": "%",
17-
"y_label": "cpu_usage"
17+
"y_label": "cpu_usage",
18+
"group_by": ["entity_id"],
19+
"filters": null
1820
},
1921
{
2022
"aggregate_function": "sum",
@@ -24,7 +26,9 @@
2426
"metric": "available_memory",
2527
"size": 6,
2628
"unit": "GB",
27-
"y_label": "available_memory"
29+
"y_label": "available_memory",
30+
"group_by": ["entity_id"],
31+
"filters": null
2832
}
2933
]
3034
}

test/fixtures/monitor_services_dbaas_dashboards.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"metric": "cpu_usage",
1717
"size": 12,
1818
"unit": "%",
19-
"y_label": "cpu_usage"
19+
"y_label": "cpu_usage",
20+
"group_by": ["entity_id"],
21+
"filters": null
2022
},
2123
{
2224
"aggregate_function": "sum",
@@ -26,7 +28,16 @@
2628
"metric": "memory_usage",
2729
"size": 6,
2830
"unit": "%",
29-
"y_label": "memory_usage"
31+
"y_label": "memory_usage",
32+
"group_by": ["entity_id"],
33+
"filters": [
34+
{
35+
"dimension_label": "pattern",
36+
"operator": "in",
37+
"value": "publicout,privateout"
38+
}
39+
]
40+
3041
}
3142
]
3243
}

test/fixtures/regions.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@
132132
"Object Storage",
133133
"Linode Interfaces"
134134
],
135+
"monitors": {
136+
"alerts": [
137+
"Managed Databases"
138+
],
139+
"metrics": [
140+
"Managed Databases"
141+
]
142+
},
135143
"status": "ok",
136144
"resolvers": {
137145
"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",

test/integration/models/monitor/test_monitor.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,57 @@ def test_get_all_dashboards(test_linode_client):
3737
assert dashboards_by_svc[0].service_type == get_service_type
3838

3939

40+
def test_filter_and_group_by(test_linode_client):
41+
client = test_linode_client
42+
dashboards_by_svc = client.monitor.dashboards(service_type="linode")
43+
assert isinstance(dashboards_by_svc[0], MonitorDashboard)
44+
45+
# Get the first dashboard for linode service type
46+
dashboard = dashboards_by_svc[0]
47+
assert dashboard.service_type == "linode"
48+
49+
# Ensure the dashboard has widgets
50+
assert hasattr(
51+
dashboard, "widgets"
52+
), "Dashboard should have widgets attribute"
53+
assert dashboard.widgets is not None, "Dashboard widgets should not be None"
54+
assert (
55+
len(dashboard.widgets) > 0
56+
), "Dashboard should have at least one widget"
57+
58+
# Test the first widget's group_by and filters fields
59+
widget = dashboard.widgets[0]
60+
61+
# Test group_by field type
62+
group_by = widget.group_by
63+
assert group_by is None or isinstance(
64+
group_by, list
65+
), "group_by should be None or list type"
66+
if group_by is not None:
67+
for item in group_by:
68+
assert isinstance(item, str), "group_by items should be strings"
69+
70+
# Test filters field type
71+
filters = widget.filters
72+
assert filters is None or isinstance(
73+
filters, list
74+
), "filters should be None or list type"
75+
if filters is not None:
76+
from linode_api4.objects.monitor import Filter
77+
78+
for filter_item in filters:
79+
assert isinstance(
80+
filter_item, Filter
81+
), "filter items should be Filter objects"
82+
assert hasattr(
83+
filter_item, "dimension_label"
84+
), "Filter should have dimension_label"
85+
assert hasattr(
86+
filter_item, "operator"
87+
), "Filter should have operator"
88+
assert hasattr(filter_item, "value"), "Filter should have value"
89+
90+
4091
# List supported services
4192
def test_get_supported_services(test_linode_client):
4293
client = test_linode_client

test/unit/objects/monitor_test.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ def test_dashboard_by_ID(self):
4141
self.assertEqual(dashboard.widgets[0].size, 12)
4242
self.assertEqual(dashboard.widgets[0].unit, "%")
4343
self.assertEqual(dashboard.widgets[0].y_label, "cpu_usage")
44+
self.assertEqual(dashboard.widgets[0].group_by, ["entity_id"])
45+
self.assertIsNone(dashboard.widgets[0].filters)
4446

4547
def test_dashboard_by_service_type(self):
4648
dashboards = self.client.monitor.dashboards(service_type="dbaas")
@@ -62,6 +64,21 @@ def test_dashboard_by_service_type(self):
6264
self.assertEqual(dashboards[0].widgets[0].size, 12)
6365
self.assertEqual(dashboards[0].widgets[0].unit, "%")
6466
self.assertEqual(dashboards[0].widgets[0].y_label, "cpu_usage")
67+
self.assertEqual(dashboards[0].widgets[0].group_by, ["entity_id"])
68+
self.assertIsNone(dashboards[0].widgets[0].filters)
69+
70+
# Test the second widget which has filters
71+
self.assertEqual(dashboards[0].widgets[1].label, "Memory Usage")
72+
self.assertEqual(dashboards[0].widgets[1].group_by, ["entity_id"])
73+
self.assertIsNotNone(dashboards[0].widgets[1].filters)
74+
self.assertEqual(len(dashboards[0].widgets[1].filters), 1)
75+
self.assertEqual(
76+
dashboards[0].widgets[1].filters[0].dimension_label, "pattern"
77+
)
78+
self.assertEqual(dashboards[0].widgets[1].filters[0].operator, "in")
79+
self.assertEqual(
80+
dashboards[0].widgets[1].filters[0].value, "publicout,privateout"
81+
)
6582

6683
def test_get_all_dashboards(self):
6784
dashboards = self.client.monitor.dashboards()
@@ -83,20 +100,28 @@ def test_get_all_dashboards(self):
83100
self.assertEqual(dashboards[0].widgets[0].size, 12)
84101
self.assertEqual(dashboards[0].widgets[0].unit, "%")
85102
self.assertEqual(dashboards[0].widgets[0].y_label, "cpu_usage")
103+
self.assertEqual(dashboards[0].widgets[0].group_by, ["entity_id"])
104+
self.assertIsNone(dashboards[0].widgets[0].filters)
86105

87106
def test_specific_service_details(self):
88107
data = self.client.load(MonitorService, "dbaas")
89108
self.assertEqual(data.label, "Databases")
90109
self.assertEqual(data.service_type, "dbaas")
91110

111+
# Test alert configuration
112+
self.assertIsNotNone(data.alert)
113+
self.assertEqual(data.alert.polling_interval_seconds, [300])
114+
self.assertEqual(data.alert.evaluation_period_seconds, [300])
115+
self.assertEqual(data.alert.scope, ["entity"])
116+
92117
def test_metric_definitions(self):
93118

94119
metrics = self.client.monitor.metric_definitions(service_type="dbaas")
95120
self.assertEqual(
96121
metrics[0].available_aggregate_functions,
97122
["max", "avg", "min", "sum"],
98123
)
99-
self.assertEqual(metrics[0].is_alertable, True)
124+
self.assertTrue(metrics[0].is_alertable)
100125
self.assertEqual(metrics[0].label, "CPU Usage")
101126
self.assertEqual(metrics[0].metric, "cpu_usage")
102127
self.assertEqual(metrics[0].metric_type, "gauge")

test/unit/objects/region_test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ def test_get_region(self):
2727
region.placement_group_limits.maximum_linodes_per_pg, 5
2828
)
2929

30+
# Test monitors section
31+
self.assertIsNotNone(region.monitors)
32+
self.assertEqual(region.monitors.alerts, ["Managed Databases"])
33+
self.assertEqual(region.monitors.metrics, ["Managed Databases"])
34+
3035
self.assertIsNotNone(region.capabilities)
3136
self.assertIn("Linode Interfaces", region.capabilities)
3237

0 commit comments

Comments
 (0)