From 45a5792f09acf0cde0e6d956f3e1a15a64269c76 Mon Sep 17 00:00:00 2001 From: "A. Jesse Jiryu Davis" Date: Sun, 5 Mar 2023 17:23:54 -0500 Subject: [PATCH] Add start/end to get_measurement_for_hosts() --- atlasapi/atlas.py | 30 ++++++++++++++++++++---------- atlasapi/network.py | 16 ++++++++++++++++ atlasapi/settings.py | 3 +-- tests/test_monitoring.py | 12 ++++++++++++ 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/atlasapi/atlas.py b/atlasapi/atlas.py index 3e1fc1a..792f05f 100644 --- a/atlasapi/atlas.py +++ b/atlasapi/atlas.py @@ -17,7 +17,7 @@ Core module which provides access to MongoDB Atlas Cloud Provider APIs """ -from atlasapi.network import Network +from atlasapi.network import Network, atlas_encode_params from atlasapi.errors import * from pprint import pprint from json import loads @@ -605,7 +605,8 @@ def host_list_secondaries(self) -> Iterable[Host]: def get_measurement_for_hosts(self, granularity: Optional[AtlasGranularities] = None, period: Optional[AtlasPeriods] = None, - measurement: Optional[AtlasMeasurementTypes] = None, return_data: bool = False): + measurement: Optional[AtlasMeasurementTypes] = None, return_data: bool = False, + start: Optional[datetime] = None, end: Optional[datetime] = None): """Get measurement(s) for all hosts in the host_list @@ -613,6 +614,9 @@ def get_measurement_for_hosts(self, granularity: Optional[AtlasGranularities] = Multiple calls will append additional metrics to the same host object. + You must provide both `start` and `end` or neither. You cannot provide `period` + with `start` and `end`. + Please note that using the `return_data` param will also return the updated host objects, which may unnecessarily consume memory. @@ -620,7 +624,8 @@ def get_measurement_for_hosts(self, granularity: Optional[AtlasGranularities] = granularity (AtlasGranularities): the desired granularity period (AtlasPeriods): The desired period measurement (AtlasMeasurementTypes) : The desired measurement or Measurement class - + start (datetime): The start of the desired period + end (datetime): The end of the desired period :param return_data: @@ -643,6 +648,7 @@ def get_measurement_for_hosts(self, granularity: Optional[AtlasGranularities] = logger.debug(f'The data type of measurement is is {type(measurement)}') returned_data = self._get_measurement_for_host(each_host, granularity=granularity, period=period, + start=start, end=end, measurement=measurement) each_host.measurements = list(returned_data) except Exception as e: @@ -790,7 +796,9 @@ def get_logs_for_cluster(self, def _get_measurement_for_host(self, host_obj: Host, granularity: Optional[AtlasGranularities] = None, period: Optional[AtlasPeriods] = None, - measurement: Optional[AtlasMeasurementTypes] = None + measurement: Optional[AtlasMeasurementTypes] = None, + start: Optional[datetime] = None, + end: Optional[datetime] = None ) -> Iterable[AtlasMeasurement]: """Get measurement(s) for a host @@ -814,6 +822,8 @@ def _get_measurement_for_host(self, host_obj: Host, pageNum (int): Page number itemsPerPage (int): Number of Users per Page iterable (bool): To return an iterable high level object instead of a low level API response + start (datetime): Start of the desired period + end (datetime): End of the desired period Returns: Iterable[AtlasMeasurement] or dict: Iterable object representing this function OR Response payload @@ -832,7 +842,7 @@ def _get_measurement_for_host(self, host_obj: Host, # Set default measurement, period and granularity if none are sent if measurement is None: measurement = AtlasMeasurementTypes.Cache.dirty - if period is None: + if period is None and start is None and end is None: period = AtlasPeriods.WEEKS_1 if granularity is None: granularity = AtlasGranularities.HOUR @@ -864,11 +874,11 @@ def _get_measurement_for_host(self, host_obj: Host, uri = Settings.api_resources["Monitoring and Logs"]["Get measurement for host"].format( group_id=self.atlas.group, host=host_obj.hostname, - port=host_obj.port, - granularity=granularity, - period=period, - measurement=measurement - ) + port=host_obj.port + ) + "?" + atlas_encode_params({ + "granularity": granularity, "m": measurement, + "period": period, "start": start, "end": end}) + logger.debug(f'The URI used will be {uri}') # Build the request return_val = self.atlas.network.get(Settings.BASE_URL + uri) diff --git a/atlasapi/network.py b/atlasapi/network.py index 02c5db5..19ad06e 100644 --- a/atlasapi/network.py +++ b/atlasapi/network.py @@ -18,12 +18,15 @@ Module which handles the basic network operations with the Atlas API> """ +import datetime from math import ceil import requests from requests.auth import HTTPDigestAuth, HTTPBasicAuth from atlasapi.settings import Settings from atlasapi.errors import * import logging +import urllib.parse +from dateutil.tz import UTC from json import dumps from io import BytesIO from typing import Union @@ -36,6 +39,19 @@ def merge(dict1, dict2): return dict2.update(dict1) +def atlas_encode_dt(dt: datetime.datetime) -> str: + """Encode a datetime (must already be UTC!) for the Atlas API.""" + # Atlas requires "Z" suffix, not Python's usual "+00:00" for UTC. + return dt.replace(tzinfo=None).isoformat() + 'Z' + + +def atlas_encode_params(params: dict) -> str: + # Use "safe" to permit ":" in ISO8601 datetimes. + return urllib.parse.urlencode( + {k: atlas_encode_dt(v) if isinstance(v, datetime.datetime) else v + for k, v in params.items() if v is not None}, safe=':') + + class Network: """Network constructor diff --git a/atlasapi/settings.py b/atlasapi/settings.py index 04f2ff8..3fac788 100644 --- a/atlasapi/settings.py +++ b/atlasapi/settings.py @@ -38,8 +38,7 @@ class Settings: "Get information for process in group": URI_STUB + "/groups/%s/processes/%s:&s?pageNum=%d" "&itemsPerPage=%d", "Get measurement for host": URI_STUB + "/groups/{group_id}/processes/{host}:{" - "port}/measurements?granularity={granularity}&period={period}" - "&m={measurement}", + "port}/measurements", "Get list of databases for host": "/api/atlas/v1.0/groups/{GROUP-ID}/processes/{HOST}:{PORT}/databases", "Get measurements of database for host.": "/api/atlas/v1.0/groups/{GROUP-ID}/processes/{HOST}:{" "PORT}/databases/{DATABASE-NAME}/measurements", diff --git a/tests/test_monitoring.py b/tests/test_monitoring.py index 618f1c2..3979a89 100644 --- a/tests/test_monitoring.py +++ b/tests/test_monitoring.py @@ -371,3 +371,15 @@ def test_26_issue_114_add_ten_second_granularity(self): # This test requires a cluster of m40 or higher , so will not run this in the automated suite. test_26_issue_114_add_ten_second_granularity.basic = False + + def test_27_get_measurements_start_end(self): + self.a.Hosts.fill_host_list() + self.assertGreaterEqual(len(self.a.Hosts.host_list), 2) + end = datetime.utcnow() + start = end - timedelta(minutes=10) + self.a.Hosts.get_measurement_for_hosts(measurement=AtlasMeasurementTypes.connections, + start=start, end=end) + + self.assertGreaterEqual(len(self.a.Hosts.host_list_with_measurements), 1) + + test_27_get_measurements_start_end.basic = True