# -*- coding: utf-8 -*- # # Copyright (c) 2014 Virtual Cable S.L. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of Virtual Cable S.L. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ @author: Adolfo Gómez, dkmaster at dkmon dot com """ import json import socket import time from . import ipc from . import httpserver from .config import readConfig from .loader import loadModules from .log import logger from .utils import exceptionToMessage IPC_PORT = 10398 class CommonService(object): isAlive = True ipc = None httpServer = None modules = None def __init__(self): logger.info('----------------------------------------') logger.info('Initializing OpenGnsys Agent') # Read configuration file before proceding & ensures minimal config is there self.config = readConfig() # Get opengnsys section as dict cfg = dict(self.config.items('opengnsys')) # Set up log level logger.setLevel(cfg.get('log', 'INFO')) logger.debug('Loaded configuration from opengnsys.cfg:') for section in self.config.sections(): logger.debug('Section {} = {}'.format(section, self.config.items(section))) if logger.logger.isWindows(): # Logs will also go to windows event log for services logger.logger.serviceLogger = True self.address = (cfg.get('address', '0.0.0.0'), int(cfg.get('port', '10997'))) self.ipcport = int(cfg.get('ipc_port', IPC_PORT)) self.timeout = int(cfg.get('timeout', '20')) logger.debug('Socket timeout: {}'.format(self.timeout)) socket.setdefaulttimeout(self.timeout) # Now load modules self.modules = loadModules(self) logger.debug('Modules: {}'.format(list(v.name for v in self.modules))) def stop(self): """ Requests service termination """ self.isAlive = False # ******************************** # * Internal messages processors * # ******************************** def notifyLogin(self, data): username = data.decode('utf-8') for v in self.modules: try: logger.debug('Notifying login of user {} to module {}'.format(username, v.name)) v.onLogin(username) except Exception as e: logger.error('Got exception {} processing login message on {}'.format(e, v.name)) def notifyLogout(self, data): username = data.decode('utf-8') for v in self.modules: try: logger.debug('Notifying logout of user {} to module {}'.format(username, v.name)) v.onLogout(username) except Exception as e: logger.error('Got exception {} processing logout message on {}'.format(e, v.name)) def notifyMessage(self, data): module, message, data = data.decode('utf-8').split('\0') for v in self.modules: if v.name == module: # Case Sensitive!!!! try: logger.debug('Notifying message {} to module {} with json data {}'.format(message, v.name, data)) v.processClientMessage(message, json.loads(data)) return except Exception as e: logger.error('Got exception {} processing generic message on {}'.format(e, v.name)) logger.error('Module {} not found, messsage {} not sent'.format(module, message)) def clientMessageProcessor(self, msg, data): """ Callback, invoked from IPC, on its own thread (not the main thread). This thread will "block" communication with agent untill finished, but this should be no problem """ logger.debug('Got message {}'.format(msg)) if msg == ipc.REQ_LOGIN: self.notifyLogin(data) elif msg == ipc.REQ_LOGOUT: self.notifyLogout(data) elif msg == ipc.REQ_MESSAGE: self.notifyMessage(data) def initialize(self): """ Initialize listeners, modules, etc... """ logger.debug('Starting IPC listener at {}'.format(IPC_PORT)) self.ipc = ipc.ServerIPC(self.ipcport, clientMessageProcessor=self.clientMessageProcessor) self.ipc.start() # And http threaded server self.httpServer = httpserver.HTTPServerThread(self.address, self) self.httpServer.start() # And lastly invoke modules activation validMods = [] for mod in self.modules: try: logger.debug('Activating module {}'.format(mod.name)) mod.activate() validMods.append(mod) except Exception as e: logger.exception() logger.debug("Activation of {} failed: {}".format(mod.name, exceptionToMessage(e))) self.modules[:] = validMods # copy instead of assignment logger.debug('Modules after activation: {}'.format(list(v.name for v in self.modules))) def terminate(self): # First invoke deactivate on modules for mod in reversed(self.modules): try: logger.debug('Deactivating module {}'.format(mod.name)) mod.deactivate() except Exception as e: logger.exception() logger.error("Deactivation of {} failed: {}".format(mod.name, exceptionToMessage(e))) # Remove IPC threads if self.ipc is not None: try: self.ipc.stop() except Exception: logger.error('Couln\'t stop ipc server') if self.httpServer is not None: try: self.httpServer.stop() except Exception: logger.error('Couln\'t stop RESTApi server') self.notifyStop() # **************************************** # Methods that CAN BE overridden by agents # **************************************** def doWait(self, miliseconds): """ Invoked to wait a bit CAN be OVERRIDDEN """ time.sleep(float(miliseconds) / 1000) def notifyStop(self): """ Overridden to log stop """ logger.info('Service is being stopped') # *************************************************** # * Helpers, convenient methods to facilitate comms * # *************************************************** def sendClientMessage(self, toModule, message, data): """ Sends a message to the clients using IPC The data is converted to json, so ensure that it is serializable. All IPC is asynchronous, so if you expect a response, this will be sent by client using another message @param toModule: Module that will receive this message @param message: Message to send @param data: data to send """ self.ipc.sendMessageMessage('\0'.join((toModule, message, json.dumps(data)))) def sendScriptMessage(self, script): """ Sends an script to be executed by client """ self.ipc.sendScriptMessage(script) def sendLogoffMessage(self): """ Sends a logoff message to client """ self.ipc.sendLoggofMessage() def sendPopupMessage(self, title, message): """ Sends a popup box to be displayed by client """ self.ipc.sendPopupMessage(title, message)