def test_set(self): conf = Configuration() self.assertFalse(conf.get('Logging', 'trace')) conf.set('trace', True, 'Logging') self.assertTrue(conf.get('Logging', 'trace')) conf.set('trace', False, 'Logging') self.assertFalse(conf.get('Logging', 'trace'))
def _log_line_count(self): conf = Configuration() if os.path.isfile(conf.get('Logging', 'file')): fil = open(conf.get('Logging', 'file'), 'r') filContent = fil.read().splitlines() fil.close() del fil ctn = 0 for line in filContent: ctn += 1 del filContent del conf return ctn else: del conf return 0
def test_defaults(self): conf = Configuration() self.assertEqual( conf.get('Connectivity', 'MongoDB').get('host'), 'localhost') self.assertEqual( conf.get('Connectivity', 'MongoDB').get('port'), 27017) self.assertEqual(conf.get('Logging', 'mode'), 'filesystem') self.assertEqual(conf.get('Logging', 'verbose'), False) self.assertEqual(conf.get('Logging', 'file'), conf.greaseDir + 'log' + conf.fs_sep + 'grease.log') self.assertEqual(conf.get('Configuration', 'dir'), conf.greaseDir + 'etc' + conf.fs_sep) self.assertEqual(conf.get('Sourcing', 'dir'), conf.greaseDir + 'etc' + conf.fs_sep)
def test_initialization(self): conf = Configuration() self.assertTrue(conf.get('Connectivity', 'MongoDB'))
def test_config_is_false_before_startup(self): self.assertFalse(Configuration.get('MongoDB', 'host'))
def test_get_section(self): conf = Configuration() # test for section that should have stuff self.assertTrue(conf.get('Notifications')) # Section that is empty self.assertFalse(conf.get('Additional'))
def test_default(self): conf = Configuration() self.assertTrue(conf.get('FakeSection', default=True)) self.assertTrue(conf.get('Connectivity', 'MongoDB'))
def test_no_key(self): conf = Configuration() self.assertDictEqual({'MongoDB': { 'host': 'localhost', 'port': 27017 }}, conf.get('Connectivity'))
class Logging(object): """Application Logging for GREASE This is the primary configuration source for GREASE logging. All log information will be passed here to enable centralized log aggregation Attributes: _conf (Configuration): This is an instance of the Config to enable configuring loggers _logger (logging.Logger): This is the actual logger for GREASE _formatter (logging.Formatter): This is the log formatter _notifications (Notifications): Notifications instance foreground (bool): If set will override config and print all log messages """ _conf = None _logger = None _formatter = None _notifications = None foreground = False def __init__(self, Config=None): if isinstance(Config, Configuration): self._conf = Config else: self._conf = Configuration() self._notifications = Notifications(self.getConfig()) self.ProvisionLoggers() def getConfig(self): """Getter for Configuration Returns: Configuration: The loaded configuration object """ return self._conf def getNotification(self): """Get Notification Class Returns: Notifications: The current Notifications instance """ return self._notifications def TriageMessage(self, message, additional=None, verbose=False, trace=False, notify=False, level=logging.DEBUG): """Central message handler Args: message (str): Message to Log additional (object): Additional information to log verbose (bool): To be printed if verbose is enabled trace (bool): To be printed if trace is enabled notify (bool): If true will pass through notification system level (int): Log Level Returns: bool: Log Success """ # first prevent verbose processing if verbose and not self._conf.get('Logging', 'verbose'): return True # prevent trace processing if trace and not self._conf.get('Logging', 'trace'): return True # create a pre-message if level is 0: preMsg = "TRACE" elif level is logging.DEBUG: preMsg = "DEBUG" elif level is logging.INFO: preMsg = "INFO" elif level is logging.WARNING: preMsg = "WARNING" elif level is logging.ERROR: preMsg = "ERROR" elif level is logging.CRITICAL: preMsg = "CRITICAL" else: preMsg = "UNSET" if verbose: preMsg = "VERBOSE::{0}".format(preMsg) if trace: preMsg = "TRACE::{0}".format(preMsg) if additional: message = "{0}::{1}::{2}::{3}".format(preMsg, self._conf.NodeIdentity, message, additional) else: message = "{0}::{1}::{2}".format(preMsg, self._conf.NodeIdentity, message) # Foreground mode print log messages if self._conf.get('Logging', 'foreground') or self.foreground: print("{0}::{1}".format(datetime.utcnow(), message)) # actually log the message if level is 0: self._logger.log(logging.DEBUG, message) else: self._logger.log(level, message) # notify if needed if notify: return bool(self._notifications.SendMessage(message, level)) return True def trace(self, message, additional=None, verbose=False, trace=True, notify=False): """Trace Messages Use this method for logging tracing (enhanced debug) statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=0 )) def debug(self, message, additional=None, verbose=False, trace=False, notify=False): """Debug Messages Use this method for logging debug statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=logging.DEBUG )) def info(self, message, additional=None, verbose=False, trace=False, notify=False): """Info Messages Use this method for logging info statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=logging.INFO )) def warning(self, message, additional=None, verbose=False, trace=False, notify=False): """Warning Messages Use this method for logging warning statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=logging.WARNING )) def error(self, message, additional=None, verbose=False, trace=False, notify=True): """Error Messages Use this method for logging error statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=logging.ERROR )) def critical(self, message, additional=None, verbose=False, trace=False, notify=True): """Critical Messages Use this method for logging critical statements Args: message (str): Message to log additional (object): Additional information to log. Note: object must be able to transform to string verbose (bool): Print only if verbose mode trace (bool): Print only if trace mode notify (bool): Run through the notification management system Returns: bool: Message is logged """ return bool(self.TriageMessage( message, additional=additional, verbose=verbose, trace=trace, notify=notify, level=logging.CRITICAL )) def ProvisionLoggers(self): """Loads Log Handler & Config Returns: None: Simple loader, Nothing needed """ if self._conf.get('Logging', 'ConfigurationFile'): if os.path.isfile(self._conf.get('Logging', 'ConfigurationFile')): config.fileConfig(self._conf.get('Logging', 'ConfigurationFile')) self._logger = logging.getLogger('GREASE') else: self.DefaultLogger() else: self.DefaultLogger() def DefaultLogger(self): """Default Logging Provisioning Returns: None: void method to provision class internals """ global GREASE_LOG_HANDLER self._logger = logging.getLogger('GREASE') self._logger.setLevel(logging.DEBUG) self._formatter = logging.Formatter( "{" "\"timestamp\": \"%(asctime)s.%(msecs)03d\", " "\"thread\": \"%(threadName)s\", " "\"level\" : \"%(levelname)s\", " "\"message\" : \"%(message)s\"}", "%Y-%m-%d %H:%M:%S" ) self._formatter.converter = time.gmtime if not GREASE_LOG_HANDLER: if os.path.isdir(self._conf.greaseDir): GREASE_LOG_HANDLER = logging.FileHandler(self._conf.get('Logging', 'file')) GREASE_LOG_HANDLER.setLevel(logging.DEBUG) GREASE_LOG_HANDLER.setFormatter(self._formatter) self._logger.addHandler(GREASE_LOG_HANDLER) else: GREASE_LOG_HANDLER = logging.StreamHandler() GREASE_LOG_HANDLER.setLevel(logging.DEBUG) GREASE_LOG_HANDLER.setFormatter(self._formatter) self._logger.addHandler(GREASE_LOG_HANDLER)
class Notifications(object): """Notification Router for third party resources This is the class to handle all notifications to third party resources Attributes: _conf (Configuration): Configuration Object hipchat_url (str): This is the hipchat API url hipchat_token (str): set this to override the config for the auth token hipchat_room (str): set this to override the config for the room """ _conf = None # HipChat Configuration hipchat_url = "https://api.hipchat.com/v2/room/" hipchat_token = None hipchat_room = None def __init__(self, Config=None): if Config and isinstance(Config, Configuration): self._conf = Config else: self._conf = Configuration() def SendMessage(self, message, level=DEBUG, channel=None): """Send Message to configured channels This method is the main point of contact with Notifications in GREASE. This will handle routing to all configured channels. Use `level` to define what level the message is. This can impact whether a message is sent as well as if the message sent will have special attributes (EX: red text). Use `channel` to route around sending to multiple channels if the message traditionally would go to multiple, instead going only to the selected one. Note: if you use the channel argument and the channel is not found you will receive False back Args: message (str): Message to send level (int): Level of message to be sent channel (str): Specific channel to notify Returns: bool: Success of sending """ if channel \ and 'enabled' in self._conf.get('Notifications', channel, {}) \ and self._conf.get('Notifications', channel, {}).get('enabled'): return bool(self._route_notification(channel, message, level)) else: # Capture object for notification channel statuses NotificationStatus = [] NotificationChannels = self._conf.get('Notifications', default={}) # type: dict # Loop through those channels for Notifier, Config in NotificationChannels.items(): # ensure channel is enabled if 'enabled' in Config and Config.get('enabled'): # loop through the channels NotificationStatus.append(bool(self._route_notification(Notifier, message, level))) # make the list unique NotificationStatus = list(set(NotificationStatus)) if len(NotificationStatus) > 1: # we got at least one true and at least one false return False elif len(NotificationStatus) is 1: # return what the categorical state was return bool(NotificationStatus[0]) else: # nothing was configured to run return true return True def _route_notification(self, channel, message, level): """Handle actual calling of notification channels Args: channel (str): Channel to notify message (str): Message to send level (int): Level to send at Returns: bool: Method success status """ if channel == "HipChat": return self.send_hipchat_message(message, level) elif channel == "Slack": return self.send_slack_message(message) else: return False def send_hipchat_message(self, message, level, color=None): """Send a hipchat message Args: message (str): Message to send to hipchat level (int): message level color (str): color of message Returns: bool: API response status """ if not color: if level is DEBUG: color = 'grey' elif level is INFO: color = 'purple' elif level is WARNING: color = 'yellow' elif level is ERROR: color = 'red' elif level is CRITICAL: color = 'red' else: color = 'grey' if self.hipchat_room and self.hipchat_token: url = "{0}{1}/notification?auth_token={2}".format( self.hipchat_url, self.hipchat_room, self.hipchat_token ) else: url = "{0}{1}/notification?auth_token={2}".format( self.hipchat_url, self._conf.get('Notifications', 'HipChat').get('room'), self._conf.get('Notifications', 'HipChat').get('token') ) try: response = requests.post( url=url, data={ 'message': message, 'color': color }, verify=False ) if response.status_code == 204: return True else: return False except HTTPError: return False def send_slack_message(self, message): """Send a slack message to slack channel using webhook url in the configuration Args: message (str): Message to send to Slack Returns: bool: API response status """ webhook_url = self._conf.get('Notifications', 'Slack').get('webhookURL') slack_data = {'text': message} try: response = requests.post( webhook_url, data=json.dumps(slack_data), headers={'Content-Type': 'application/json'} ) return True except HTTPError: return False
class Mongo(object): """MongoDB Connection Class Attributes: _client (pymongo.MongoClient): The actual PyMongo Connection _config (Configuration): Configuration Object """ _client = None _config = None def __init__(self, Config=None): if Config and isinstance(Config, Configuration): self._config = Config else: self._config = Configuration() self._client = self._generate_client() def Client(self): """get the connection client Returns: pymongo.MongoClient: Returns the mongoDB connection client """ return self._client def Close(self): """Close PyMongo Connection Returns: None: Void Method to close connection """ self._client.close() def _generate_client(self): """Creates a PyMongo Client Returns: pymongo.MongoClient: Mongo Connection """ mongoConf = self._config.get('Connectivity', 'MongoDB') # type: dict if mongoConf.get('username') and mongoConf.get('password'): return pymongo.MongoClient( "mongodb://{0}:{1}@{2}:{3}/{4}".format( mongoConf.get('username', ''), mongoConf.get('password', ''), mongoConf.get('host', 'localhost'), mongoConf.get('port', 27017), mongoConf.get('db', 'grease') ), w=1 ) else: return pymongo.MongoClient( host=mongoConf.get('host', 'localhost'), port=mongoConf.get('port', 27017), w=1 )