Skip to content

Commit e2652d8

Browse files
authored
adding monitors in region endpoint (#587)
* adding monitors in region endpoint * updating with review comments * fixing build issues * fixing build issues * fixing build issue * fixing build issue * fixing lint * fixing filters * adding int-test for filter and groupby
1 parent 86c26df commit e2652d8

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
@@ -49,6 +49,7 @@ class ServiceType(StrEnum):
4949
firewall = "firewall"
5050
object_storage = "object_storage"
5151
aclb = "aclb"
52+
net_load_balancer = "netloadbalancer"
5253

5354

5455
class MetricType(StrEnum):
@@ -82,6 +83,10 @@ class MetricUnit(StrEnum):
8283
RATIO = "ratio"
8384
OPS_PER_SECOND = "ops_per_second"
8485
IOPS = "iops"
86+
KILO_BYTES_PER_SECOND = "kilo_bytes_per_second"
87+
SESSIONS_PER_SECOND = "sessions_per_second"
88+
PACKETS_PER_SECOND = "packets_per_second"
89+
KILO_BITS_PER_SECOND = "kilo_bits_per_second"
8590

8691

8792
class DashboardType(StrEnum):
@@ -93,6 +98,17 @@ class DashboardType(StrEnum):
9398
custom = "custom"
9499

95100

101+
@dataclass
102+
class Filter(JSONObject):
103+
"""
104+
Represents a filter in the filters list of a dashboard widget.
105+
"""
106+
107+
dimension_label: str = ""
108+
operator: str = ""
109+
value: str = ""
110+
111+
96112
@dataclass
97113
class DashboardWidget(JSONObject):
98114
"""
@@ -107,6 +123,34 @@ class DashboardWidget(JSONObject):
107123
chart_type: ChartType = ""
108124
y_label: str = ""
109125
aggregate_function: AggregateFunction = ""
126+
group_by: Optional[List[str]] = None
127+
_filters: Optional[List[Filter]] = field(
128+
default=None, metadata={"json_key": "filters"}
129+
)
130+
131+
def __getattribute__(self, name):
132+
"""Override to handle the filters attribute specifically to avoid metaclass conflict."""
133+
if name == "filters":
134+
return object.__getattribute__(self, "_filters")
135+
return object.__getattribute__(self, name)
136+
137+
def __setattr__(self, name, value):
138+
"""Override to handle setting the filters attribute."""
139+
if name == "filters":
140+
object.__setattr__(self, "_filters", value)
141+
else:
142+
object.__setattr__(self, name, value)
143+
144+
145+
@dataclass
146+
class ServiceAlert(JSONObject):
147+
"""
148+
Represents alert configuration options for a monitor service.
149+
"""
150+
151+
polling_interval_seconds: Optional[List[int]] = None
152+
evaluation_period_seconds: Optional[List[int]] = None
153+
scope: Optional[List[str]] = None
110154

111155

112156
@dataclass
@@ -135,9 +179,7 @@ class MonitorMetricsDefinition(JSONObject):
135179
scrape_interval: int = 0
136180
is_alertable: bool = False
137181
dimensions: Optional[List[Dimension]] = None
138-
available_aggregate_functions: List[AggregateFunction] = field(
139-
default_factory=list
140-
)
182+
available_aggregate_functions: Optional[List[AggregateFunction]] = None
141183

142184

143185
class MonitorDashboard(Base):
@@ -154,7 +196,7 @@ class MonitorDashboard(Base):
154196
"label": Property(),
155197
"service_type": Property(ServiceType),
156198
"type": Property(DashboardType),
157-
"widgets": Property(List[DashboardWidget]),
199+
"widgets": Property(json_object=DashboardWidget),
158200
"updated": Property(is_datetime=True),
159201
}
160202

@@ -171,6 +213,7 @@ class MonitorService(Base):
171213
properties = {
172214
"service_type": Property(ServiceType),
173215
"label": Property(),
216+
"alert": Property(json_object=ServiceAlert),
174217
}
175218

176219

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
@@ -35,6 +35,57 @@ def test_get_all_dashboards(test_linode_client):
3535
assert dashboards_by_svc[0].service_type == get_service_type
3636

3737

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