Repository: ambari Updated Branches: refs/heads/branch-alerts-dev 26b162af8 -> 9e529eddc
http://git-wip-us.apache.org/repos/asf/ambari/blob/9e529edd/ambari-agent/src/main/python/ambari_agent/apscheduler/triggers/simple.py ---------------------------------------------------------------------- diff --git a/ambari-agent/src/main/python/ambari_agent/apscheduler/triggers/simple.py b/ambari-agent/src/main/python/ambari_agent/apscheduler/triggers/simple.py new file mode 100644 index 0000000..ea61b3f --- /dev/null +++ b/ambari-agent/src/main/python/ambari_agent/apscheduler/triggers/simple.py @@ -0,0 +1,17 @@ +from apscheduler.util import convert_to_datetime + + +class SimpleTrigger(object): + def __init__(self, run_date): + self.run_date = convert_to_datetime(run_date) + + def get_next_fire_time(self, start_date): + if self.run_date >= start_date: + return self.run_date + + def __str__(self): + return 'date[%s]' % str(self.run_date) + + def __repr__(self): + return '<%s (run_date=%s)>' % ( + self.__class__.__name__, repr(self.run_date)) http://git-wip-us.apache.org/repos/asf/ambari/blob/9e529edd/ambari-agent/src/main/python/ambari_agent/apscheduler/util.py ---------------------------------------------------------------------- diff --git a/ambari-agent/src/main/python/ambari_agent/apscheduler/util.py b/ambari-agent/src/main/python/ambari_agent/apscheduler/util.py new file mode 100644 index 0000000..dcede4c --- /dev/null +++ b/ambari-agent/src/main/python/ambari_agent/apscheduler/util.py @@ -0,0 +1,230 @@ +""" +This module contains several handy functions primarily meant for internal use. +""" + +from datetime import date, datetime, timedelta +from time import mktime +import re +import sys + +__all__ = ('asint', 'asbool', 'convert_to_datetime', 'timedelta_seconds', + 'time_difference', 'datetime_ceil', 'combine_opts', + 'get_callable_name', 'obj_to_ref', 'ref_to_obj', 'maybe_ref', + 'to_unicode', 'iteritems', 'itervalues', 'xrange') + + +def asint(text): + """ + Safely converts a string to an integer, returning None if the string + is None. + + :type text: str + :rtype: int + """ + if text is not None: + return int(text) + + +def asbool(obj): + """ + Interprets an object as a boolean value. + + :rtype: bool + """ + if isinstance(obj, str): + obj = obj.strip().lower() + if obj in ('true', 'yes', 'on', 'y', 't', '1'): + return True + if obj in ('false', 'no', 'off', 'n', 'f', '0'): + return False + raise ValueError('Unable to interpret value "%s" as boolean' % obj) + return bool(obj) + + +_DATE_REGEX = re.compile( + r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})' + r'(?: (?P<hour>\d{1,2}):(?P<minute>\d{1,2}):(?P<second>\d{1,2})' + r'(?:\.(?P<microsecond>\d{1,6}))?)?') + + +def convert_to_datetime(input): + """ + Converts the given object to a datetime object, if possible. + If an actual datetime object is passed, it is returned unmodified. + If the input is a string, it is parsed as a datetime. + + Date strings are accepted in three different forms: date only (Y-m-d), + date with time (Y-m-d H:M:S) or with date+time with microseconds + (Y-m-d H:M:S.micro). + + :rtype: datetime + """ + if isinstance(input, datetime): + return input + elif isinstance(input, date): + return datetime.fromordinal(input.toordinal()) + elif isinstance(input, basestring): + m = _DATE_REGEX.match(input) + if not m: + raise ValueError('Invalid date string') + values = [(k, int(v or 0)) for k, v in m.groupdict().items()] + values = dict(values) + return datetime(**values) + raise TypeError('Unsupported input type: %s' % type(input)) + + +def timedelta_seconds(delta): + """ + Converts the given timedelta to seconds. + + :type delta: timedelta + :rtype: float + """ + return delta.days * 24 * 60 * 60 + delta.seconds + \ + delta.microseconds / 1000000.0 + + +def time_difference(date1, date2): + """ + Returns the time difference in seconds between the given two + datetime objects. The difference is calculated as: date1 - date2. + + :param date1: the later datetime + :type date1: datetime + :param date2: the earlier datetime + :type date2: datetime + :rtype: float + """ + later = mktime(date1.timetuple()) + date1.microsecond / 1000000.0 + earlier = mktime(date2.timetuple()) + date2.microsecond / 1000000.0 + return later - earlier + + +def datetime_ceil(dateval): + """ + Rounds the given datetime object upwards. + + :type dateval: datetime + """ + if dateval.microsecond > 0: + return dateval + timedelta(seconds=1, + microseconds=-dateval.microsecond) + return dateval + + +def combine_opts(global_config, prefix, local_config={}): + """ + Returns a subdictionary from keys and values of ``global_config`` where + the key starts with the given prefix, combined with options from + local_config. The keys in the subdictionary have the prefix removed. + + :type global_config: dict + :type prefix: str + :type local_config: dict + :rtype: dict + """ + prefixlen = len(prefix) + subconf = {} + for key, value in global_config.items(): + if key.startswith(prefix): + key = key[prefixlen:] + subconf[key] = value + subconf.update(local_config) + return subconf + + +def get_callable_name(func): + """ + Returns the best available display name for the given function/callable. + """ + f_self = getattr(func, '__self__', None) or getattr(func, 'im_self', None) + + if f_self and hasattr(func, '__name__'): + if isinstance(f_self, type): + # class method + clsname = getattr(f_self, '__qualname__', None) or f_self.__name__ + return '%s.%s' % (clsname, func.__name__) + # bound method + return '%s.%s' % (f_self.__class__.__name__, func.__name__) + + if hasattr(func, '__call__'): + if hasattr(func, '__name__'): + # function, unbound method or a class with a __call__ method + return func.__name__ + # instance of a class with a __call__ method + return func.__class__.__name__ + + raise TypeError('Unable to determine a name for %s -- ' + 'maybe it is not a callable?' % repr(func)) + + +def obj_to_ref(obj): + """ + Returns the path to the given object. + """ + ref = '%s:%s' % (obj.__module__, get_callable_name(obj)) + try: + obj2 = ref_to_obj(ref) + if obj != obj2: + raise ValueError + except Exception: + raise ValueError('Cannot determine the reference to %s' % repr(obj)) + + return ref + + +def ref_to_obj(ref): + """ + Returns the object pointed to by ``ref``. + """ + if not isinstance(ref, basestring): + raise TypeError('References must be strings') + if not ':' in ref: + raise ValueError('Invalid reference') + + modulename, rest = ref.split(':', 1) + try: + obj = __import__(modulename) + except ImportError: + raise LookupError('Error resolving reference %s: ' + 'could not import module' % ref) + + try: + for name in modulename.split('.')[1:] + rest.split('.'): + obj = getattr(obj, name) + return obj + except Exception: + raise LookupError('Error resolving reference %s: ' + 'error looking up object' % ref) + + +def maybe_ref(ref): + """ + Returns the object that the given reference points to, if it is indeed + a reference. If it is not a reference, the object is returned as-is. + """ + if not isinstance(ref, str): + return ref + return ref_to_obj(ref) + + +def to_unicode(string, encoding='ascii'): + """ + Safely converts a string to a unicode representation on any + Python version. + """ + if hasattr(string, 'decode'): + return string.decode(encoding, 'ignore') + return string # pragma: nocover + + +if sys.version_info < (3, 0): # pragma: nocover + iteritems = lambda d: d.iteritems() + itervalues = lambda d: d.itervalues() + xrange = xrange + basestring = basestring +else: # pragma: nocover + iteritems = lambda d: d.items() + itervalues = lambda d: d.values() + xrange = range + basestring = str http://git-wip-us.apache.org/repos/asf/ambari/blob/9e529edd/ambari-agent/src/test/python/ambari_agent/TestAlerts.py ---------------------------------------------------------------------- diff --git a/ambari-agent/src/test/python/ambari_agent/TestAlerts.py b/ambari-agent/src/test/python/ambari_agent/TestAlerts.py new file mode 100644 index 0000000..51c3af9 --- /dev/null +++ b/ambari-agent/src/test/python/ambari_agent/TestAlerts.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python + +''' +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' + +import os +import sys +from ambari_agent.AlertSchedulerHandler import AlertSchedulerHandler +from ambari_agent.apscheduler.scheduler import Scheduler +from ambari_agent.alerts.port_alert import PortAlert +from mock.mock import patch +from unittest import TestCase + +class TestAlerts(TestCase): + + def setUp(self): + pass + + def tearDown(self): + sys.stdout == sys.__stdout__ + + @patch.object(Scheduler, "add_interval_job") + def test_build(self, aps_add_interval_job_mock): + test_file_path = os.path.join('ambari_agent', 'dummy_files', 'alert_definitions.json') + + ash = AlertSchedulerHandler(test_file_path) + + self.assertTrue(aps_add_interval_job_mock.called) + + def test_port_alert(self): + json = { "name": "namenode_process", + "service": "HDFS", + "component": "NAMENODE", + "label": "NameNode process", + "interval": 6, + "scope": "host", + "source": { + "type": "PORT", + "uri": "http://c6401.ambari.apache.org:50070", + "default_port": 50070, + "reporting": { + "ok": { + "text": "TCP OK - {0:.4f} response time on port {1}" + }, + "critical": { + "text": "Could not load process info: {0}" + } + } + } + } + + pa = PortAlert(json, json['source']) + self.assertEquals(6, pa.interval()) + + res = pa.collect() + + pass + http://git-wip-us.apache.org/repos/asf/ambari/blob/9e529edd/ambari-agent/src/test/python/ambari_agent/dummy_files/alert_definitions.json ---------------------------------------------------------------------- diff --git a/ambari-agent/src/test/python/ambari_agent/dummy_files/alert_definitions.json b/ambari-agent/src/test/python/ambari_agent/dummy_files/alert_definitions.json new file mode 100644 index 0000000..6c55966 --- /dev/null +++ b/ambari-agent/src/test/python/ambari_agent/dummy_files/alert_definitions.json @@ -0,0 +1,46 @@ +{ + "c1": [ + { + "name": "namenode_cpu", + "label": "NameNode host CPU Utilization", + "scope": "host", + "source": { + "type": "METRIC", + "jmx": "java.lang:type=OperatingSystem/SystemCpuLoad", + "host": "{{hdfs-site/dfs.namenode.secondary.http-address}}" + } + }, + { + "name": "namenode_process", + "service": "HDFS", + "component": "NAMENODE", + "label": "NameNode process", + "interval": 6, + "scope": "host", + "source": { + "type": "PORT", + "uri": "http://c6401.ambari.apache.org:50070", + "default_port": 50070, + "reporting": { + "ok": { + "text": "TCP OK - {0:.4f} response time on port {1}" + }, + "critical": { + "text": "Could not load process info: {0}" + } + } + } + }, + { + "name": "hdfs_last_checkpoint", + "label": "Last Checkpoint Time", + "interval": 1, + "scope": "service", + "enabled": false, + "source": { + "type": "SCRIPT", + "path": "scripts/alerts/last_checkpoint.py" + } + } + ] +}