def check(cls, **body): """ Post check statuses for use with monitors :param check: text for the message :type check: string :param host_name: name of the host submitting the check :type host_name: string :param status: integer for the status of the check :type status: Options: '0': OK, '1': WARNING, '2': CRITICAL, '3': UNKNOWN :param timestamp: timestamp of the event :type timestamp: POSIX timestamp :param message: description of why this status occurred :type message: string :param tags: list of tags for this check :type tags: string list :returns: Dictionary representing the API's JSON response """ # Validate checks, include only non-null values for param, value in body.items(): if param == "status" and body[param] not in CheckStatus.ALL: raise ApiError("Invalid status, expected one of: %s" % ", ".join(str(v) for v in CheckStatus.ALL)) return super(ServiceCheck, cls)._trigger_action("POST", "check_run", **body)
def query(cls, **params): """ Query metrics from Datadog :param start: query start timestamp :type start: POSIX timestamp :param end: query end timestamp :type end: POSIX timestamp :param query: metric query :type query: string query :returns: Dictionary representing the API's JSON response *start* and *end* should be less than 24 hours apart. It is *not* meant to retrieve metric data in bulk. >>> api.Metric.query(start=int(time.time()) - 3600, end=int(time.time()), query='avg:system.cpu.idle{*}') """ # Set the right endpoint cls._resource_name = cls._METRIC_QUERY_ENDPOINT # `from` is a reserved keyword in Python, therefore # `api.Metric.query(from=...)` is not permited # -> map `start` to `from` and `end` to `to` try: params['from'] = params.pop('start') params['to'] = params.pop('end') except KeyError as e: raise ApiError("The parameter '{0}' is required".format(e.args[0])) return super(Metric, cls)._search(**params)
def check(cls, **params): """ Post check statuses for use with monitors :param check: text for the message :type check: string :param host_name: name of the host submitting the check :type host_name: string :param status: integer for the status of the check :type status: Options: '0': OK, '1': WARNING, '2': CRITICAL, '3': UNKNOWN :param timestamp: timestamp of the event :type timestamp: POSIX timestamp :param message: description of why this status occurred :type message: string :param tags: list of tags for this check :type tags: string list :returns: Dictionary representing the API's JSON response """ if 'status' in params and params['status'] not in CheckStatus.ALL: raise ApiError('Invalid status, expected one of: %s' % ', '.join(str(v) for v in CheckStatus.ALL)) return super(ServiceCheck, cls)._trigger_action('POST', 'check_run', **params)
def create(cls, attach_host_name=True, **params): """ Post an event. :param title: title for the new event :type title: string :param text: event message :type text: string :param aggregation_key: key by which to group events in event stream :type aggregation_key: string :param alert_type: "error", "warning", "info" or "success". :type alert_type: string :param date_happened: when the event occurred. if unset defaults to the current time. \ (POSIX timestamp) :type date_happened: integer :param handle: user to post the event as. defaults to owner of the application key used \ to submit. :type handle: string :param priority: priority to post the event as. ("normal" or "low", defaults to "normal") :type priority: string :param related_event_id: post event as a child of the given event :type related_event_id: id :param tags: tags to post the event with :type tags: list of strings :param host: host to post the event with :type host: string :param device_name: device_name to post the event with :type device_name: list of strings :returns: Dictionary representing the API's JSON response >>> title = "Something big happened!" >>> text = 'And let me tell you all about it here!' >>> tags = ['version:1', 'application:web'] >>> api.Event.create(title=title, text=text, tags=tags) """ if params.get("alert_type"): if params["alert_type"] not in [ "error", "warning", "info", "success" ]: raise ApiError( "Parameter alert_type must be either error, warning, info or success" ) return super(Event, cls).create(attach_host_name=attach_host_name, **params)
def delete_test(cls, **body): """ Delete a test :param public_ids: list of public IDs to delete corresponding tests :type public_ids: list of strings :return: Dictionary representing the API's JSON response """ if not isinstance(body["public_ids"], list): raise ApiError("Parameter 'public_ids' must be a list") # API path = "synthetics/tests/delete" return super(Synthetics, cls)._trigger_action("POST", name="synthetics", id="tests/delete", **body)
def list(cls, from_epoch): """ Get a list of active metrics since a given time (Unix Epoc) :param from_epoch: Start time in Unix Epoc (seconds) :returns: Dictionary containing a list of active metrics """ cls._resource_name = cls._METRIC_LIST_ENDPOINT try: seconds = int(from_epoch) params = {'from': seconds} except ValueError: raise ApiError("Parameter 'from_epoch' must be an integer") return super(Metric, cls).get_all(**params)
def request(cls, method, path, body=None, attach_host_name=False, response_formatter=None, error_formatter=None, **params): """ Make an HTTP API request :param method: HTTP method to use to contact API endpoint :type method: HTTP method string :param path: API endpoint url :type path: url :param body: dictionnary to be sent in the body of the request :type body: dictionary :param response_formatter: function to format JSON response from HTTP API request :type response_formatter: JSON input function :param error_formatter: function to format JSON error response from HTTP API request :type error_formatter: JSON input function :param attach_host_name: link the new resource object to the host name :type attach_host_name: bool :param params: dictionnary to be sent in the query string of the request :type params: dictionary :returns: JSON or formated response from HTTP API request """ try: # Check if it's ok to submit if not cls._should_submit(): raise HttpBackoff( "Too many timeouts. Won't try again for {1} seconds.". format(*cls._backoff_status())) # Import API, User and HTTP settings from datadog.api import _api_key, _application_key, _api_host, \ _swallow, _host_name, _proxies, _max_retries, _timeout # Check keys and add then to params if _api_key is None: raise ApiNotInitialized( "API key is not set." " Please run 'initialize' method first.") params['api_key'] = _api_key if _application_key: params['application_key'] = _application_key # Construct the url url = "%s/api/%s/%s" % (_api_host, cls._api_version, path.lstrip("/")) # Attach host name to body if attach_host_name and body: # Is it a 'series' list of objects ? if 'series' in body: # Adding the host name to all objects for obj_params in body['series']: if 'host' not in obj_params: obj_params['host'] = _host_name else: if 'host' not in body: body['host'] = _host_name # If defined, make sure tags are defined as a comma-separated string if 'tags' in params and isinstance(params['tags'], list): params['tags'] = ','.join(params['tags']) # Process the body, if necessary headers = {} if isinstance(body, dict): body = json.dumps(body) headers['Content-Type'] = 'application/json' # Process requesting start_time = time.time() try: # Use a session to set a max_retries parameters s = requests.Session() http_adapter = requests.adapters.HTTPAdapter( max_retries=_max_retries) s.mount('https://', http_adapter) # Request result = s.request(method, url, headers=headers, params=params, data=body, timeout=_timeout, proxies=_proxies) result.raise_for_status() except requests.ConnectionError as e: raise ClientError("Could not request %s %s%s: %s" % (method, _api_host, url, e)) except requests.exceptions.Timeout as e: cls._timeout_counter += 1 raise HttpTimeout('%s %s timed out after %d seconds.' % (method, url, _timeout)) except requests.exceptions.HTTPError as e: if e.response.status_code == 404 or e.response.status_code == 400: pass else: raise except TypeError as e: raise TypeError( "Your installed version of 'requests' library seems not compatible with" "Datadog's usage. We recommand upgrading it ('pip install -U requests')." "If you need help or have any question, please contact [email protected]" ) # Request succeeded: log it and reset the timeout counter duration = round((time.time() - start_time) * 1000., 4) log.info("%s %s %s (%sms)" % (result.status_code, method, url, duration)) cls._timeout_counter = 0 # Format response content content = result.content if content: try: if is_p3k(): response_obj = json.loads(content.decode('utf-8')) else: response_obj = json.loads(content) except ValueError: raise ValueError( 'Invalid JSON response: {0}'.format(content)) if response_obj and 'errors' in response_obj: raise ApiError(response_obj) else: response_obj = None if response_formatter is None: return response_obj else: return response_formatter(response_obj) except ClientError as e: if _swallow: log.error(str(e)) if error_formatter is None: return {'errors': e.args[0]} else: return error_formatter({'errors': e.args[0]}) else: raise except ApiError as e: if _swallow: for error in e.args[0]['errors']: log.error(str(error)) if error_formatter is None: return e.args[0] else: return error_formatter(e.args[0]) else: raise
def raise_for_status(self): if not self._raise_for_status: return raise ApiError({'errors': ""})
def submit(cls, method, path, api_version=None, body=None, attach_host_name=False, response_formatter=None, error_formatter=None, suppress_response_errors_on_codes=None, compress_payload=False, **params): """ Make an HTTP API request :param method: HTTP method to use to contact API endpoint :type method: HTTP method string :param path: API endpoint url :type path: url :param api_version: The API version used :param body: dictionary to be sent in the body of the request :type body: dictionary :param response_formatter: function to format JSON response from HTTP API request :type response_formatter: JSON input function :param error_formatter: function to format JSON error response from HTTP API request :type error_formatter: JSON input function :param attach_host_name: link the new resource object to the host name :type attach_host_name: bool :param suppress_response_errors_on_codes: suppress ApiError on `errors` key in the response for the given HTTP status codes :type suppress_response_errors_on_codes: None|list(int) :param compress_payload: compress the payload using zlib :type compress_payload: bool :param params: dictionary to be sent in the query string of the request :type params: dictionary :returns: JSON or formated response from HTTP API request """ try: # Check if it's ok to submit if not cls._should_submit(): _, backoff_time_left = cls._backoff_status() raise HttpBackoff(backoff_time_left) # Import API, User and HTTP settings from datadog.api import _api_key, _application_key, _api_host, \ _mute, _host_name, _proxies, _max_retries, _timeout, \ _cacert, _return_raw_response # Check keys and add then to params if _api_key is None: raise ApiNotInitialized("API key is not set." " Please run 'initialize' method first.") # Set api and app keys in headers headers = {} headers['DD-API-KEY'] = _api_key if _application_key: headers['DD-APPLICATION-KEY'] = _application_key # Check if the api_version is provided if not api_version: api_version = _api_version # set api and app keys in params only for some endpoints and thus remove keys from headers # as they cannot be set in both params and headers if cls._set_api_and_app_keys_in_params(api_version, path): params['api_key'] = _api_key del headers['DD-API-KEY'] if _application_key: params['application_key'] = _application_key del headers['DD-APPLICATION-KEY'] # Attach host name to body if attach_host_name and body: # Is it a 'series' list of objects ? if 'series' in body: # Adding the host name to all objects for obj_params in body['series']: if obj_params.get('host', "") == "": obj_params['host'] = _host_name else: if body.get('host', "") == "": body['host'] = _host_name # If defined, make sure tags are defined as a comma-separated string if 'tags' in params and isinstance(params['tags'], list): tag_list = normalize_tags(params['tags']) params['tags'] = ','.join(tag_list) # If defined, make sure monitor_ids are defined as a comma-separated string if 'monitor_ids' in params and isinstance(params['monitor_ids'], list): params['monitor_ids'] = ','.join(str(i) for i in params['monitor_ids']) # Process the body, if necessary if isinstance(body, dict): body = json.dumps(body, sort_keys=cls._sort_keys) headers['Content-Type'] = 'application/json' if compress_payload: body = zlib.compress(body.encode("utf-8")) headers["Content-Encoding"] = "deflate" # Construct the URL url = construct_url(_api_host, api_version, path) # Process requesting start_time = time.time() result = cls._get_http_client().request( method=method, url=url, headers=headers, params=params, data=body, timeout=_timeout, max_retries=_max_retries, proxies=_proxies, verify=_cacert ) # Request succeeded: log it and reset the timeout counter duration = round((time.time() - start_time) * 1000., 4) log.info("%s %s %s (%sms)" % (result.status_code, method, url, duration)) cls._timeout_counter = 0 # Format response content content = result.content if content: try: if is_p3k(): response_obj = json.loads(content.decode('utf-8')) else: response_obj = json.loads(content) except ValueError: raise ValueError('Invalid JSON response: {0}'.format(content)) # response_obj can be a bool and not a dict if isinstance(response_obj, dict): if response_obj and 'errors' in response_obj: # suppress ApiError when specified and just return the response if not (suppress_response_errors_on_codes and result.status_code in suppress_response_errors_on_codes): raise ApiError(response_obj) else: response_obj = None if response_formatter is not None: response_obj = response_formatter(response_obj) if _return_raw_response: return response_obj, result else: return response_obj except HttpTimeout: cls._timeout_counter += 1 raise except ClientError as e: if _mute: log.error(str(e)) if error_formatter is None: return {'errors': e.args[0]} else: return error_formatter({'errors': e.args[0]}) else: raise except ApiError as e: if _mute: for error in (e.args[0].get('errors') or []): log.error(error) if error_formatter is None: return e.args[0] else: return error_formatter(e.args[0]) else: raise
def submit(cls, method, path, body=None, attach_host_name=False, response_formatter=None, error_formatter=None, **params): """ Make an HTTP API request :param method: HTTP method to use to contact API endpoint :type method: HTTP method string :param path: API endpoint url :type path: url :param body: dictionary to be sent in the body of the request :type body: dictionary :param response_formatter: function to format JSON response from HTTP API request :type response_formatter: JSON input function :param error_formatter: function to format JSON error response from HTTP API request :type error_formatter: JSON input function :param attach_host_name: link the new resource object to the host name :type attach_host_name: bool :param params: dictionary to be sent in the query string of the request :type params: dictionary :returns: JSON or formated response from HTTP API request """ try: # Check if it's ok to submit if not cls._should_submit(): _, backoff_time_left = cls._backoff_status() raise HttpBackoff(backoff_time_left) # Import API, User and HTTP settings from datadog.api import _api_key, _application_key, _api_host, \ _mute, _host_name, _proxies, _max_retries, _timeout, \ _cacert # Check keys and add then to params if _api_key is None: raise ApiNotInitialized( "API key is not set." " Please run 'initialize' method first.") params['api_key'] = _api_key if _application_key: params['application_key'] = _application_key # Attach host name to body if attach_host_name and body: # Is it a 'series' list of objects ? if 'series' in body: # Adding the host name to all objects for obj_params in body['series']: if obj_params.get('host', "") == "": obj_params['host'] = _host_name else: if body.get('host', "") == "": body['host'] = _host_name # If defined, make sure tags are defined as a comma-separated string if 'tags' in params and isinstance(params['tags'], list): params['tags'] = ','.join(params['tags']) # Process the body, if necessary headers = {} if isinstance(body, dict): body = json.dumps(body) headers['Content-Type'] = 'application/json' # Construct the URL url = "{api_host}/api/{api_version}/{path}".format( api_host=_api_host, api_version=cls._api_version, path=path.lstrip("/"), ) # Process requesting start_time = time.time() result = cls._get_http_client().request(method=method, url=url, headers=headers, params=params, data=body, timeout=_timeout, max_retries=_max_retries, proxies=_proxies, verify=_cacert) # Request succeeded: log it and reset the timeout counter duration = round((time.time() - start_time) * 1000., 4) log.info("%s %s %s (%sms)" % (result.status_code, method, url, duration)) cls._timeout_counter = 0 # Format response content content = result.content if content: try: if is_p3k(): response_obj = json.loads(content.decode('utf-8')) else: response_obj = json.loads(content) except ValueError: raise ValueError( 'Invalid JSON response: {0}'.format(content)) if response_obj and 'errors' in response_obj: raise ApiError(response_obj) else: response_obj = None if response_formatter is None: return response_obj else: return response_formatter(response_obj) except HttpTimeout: cls._timeout_counter += 1 raise except ClientError as e: if _mute: log.error(str(e)) if error_formatter is None: return {'errors': e.args[0]} else: return error_formatter({'errors': e.args[0]}) else: raise except ApiError as e: if _mute: for error in e.args[0]['errors']: log.error(str(error)) if error_formatter is None: return e.args[0] else: return error_formatter(e.args[0]) else: raise
def test_apierror_exception(self): with pytest.raises(ApiError): raise ApiError() with pytest.raises(DatadogException): raise ApiError()