[11f7a07] | 1 | # -*- coding: utf-8 -*- |
---|
| 2 | # |
---|
| 3 | # Copyright (c) 2014 Virtual Cable S.L. |
---|
| 4 | # All rights reserved. |
---|
| 5 | # |
---|
| 6 | # Redistribution and use in source and binary forms, with or without modification, |
---|
| 7 | # are permitted provided that the following conditions are met: |
---|
| 8 | # |
---|
| 9 | # * Redistributions of source code must retain the above copyright notice, |
---|
| 10 | # this list of conditions and the following disclaimer. |
---|
| 11 | # * Redistributions in binary form must reproduce the above copyright notice, |
---|
| 12 | # this list of conditions and the following disclaimer in the documentation |
---|
| 13 | # and/or other materials provided with the distribution. |
---|
| 14 | # * Neither the name of Virtual Cable S.L. nor the names of its contributors |
---|
| 15 | # may be used to endorse or promote products derived from this software |
---|
| 16 | # without specific prior written permission. |
---|
| 17 | # |
---|
| 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
---|
| 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
---|
| 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
---|
| 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
---|
| 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
---|
| 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
---|
| 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
---|
| 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
---|
| 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
---|
| 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
---|
| 28 | |
---|
[53e7d45] | 29 | """ |
---|
[11f7a07] | 30 | @author: Adolfo Gómez, dkmaster at dkmon dot com |
---|
[53e7d45] | 31 | """ |
---|
| 32 | |
---|
[e274dc0] | 33 | import json |
---|
| 34 | import socket |
---|
| 35 | import time |
---|
[11f7a07] | 36 | |
---|
| 37 | from . import ipc |
---|
| 38 | from . import httpserver |
---|
[e274dc0] | 39 | from .config import readConfig |
---|
[11f7a07] | 40 | from .loader import loadModules |
---|
[e274dc0] | 41 | from .log import logger |
---|
| 42 | from .utils import exceptionToMessage |
---|
[11f7a07] | 43 | |
---|
| 44 | |
---|
| 45 | IPC_PORT = 10398 |
---|
| 46 | |
---|
| 47 | |
---|
| 48 | class CommonService(object): |
---|
| 49 | isAlive = True |
---|
| 50 | ipc = None |
---|
| 51 | httpServer = None |
---|
| 52 | modules = None |
---|
| 53 | |
---|
| 54 | def __init__(self): |
---|
| 55 | logger.info('----------------------------------------') |
---|
| 56 | logger.info('Initializing OpenGnsys Agent') |
---|
| 57 | |
---|
| 58 | # Read configuration file before proceding & ensures minimal config is there |
---|
| 59 | self.config = readConfig() |
---|
[e274dc0] | 60 | # Get opengnsys section as dict |
---|
[11f7a07] | 61 | cfg = dict(self.config.items('opengnsys')) |
---|
| 62 | |
---|
| 63 | # Set up log level |
---|
| 64 | logger.setLevel(cfg.get('log', 'INFO')) |
---|
| 65 | |
---|
| 66 | logger.debug('Loaded configuration from opengnsys.cfg:') |
---|
| 67 | for section in self.config.sections(): |
---|
| 68 | logger.debug('Section {} = {}'.format(section, self.config.items(section))) |
---|
[e274dc0] | 69 | |
---|
[11f7a07] | 70 | if logger.logger.isWindows(): |
---|
| 71 | # Logs will also go to windows event log for services |
---|
| 72 | logger.logger.serviceLogger = True |
---|
| 73 | |
---|
| 74 | self.address = (cfg.get('address', '0.0.0.0'), int(cfg.get('port', '10997'))) |
---|
| 75 | self.ipcport = int(cfg.get('ipc_port', IPC_PORT)) |
---|
| 76 | |
---|
| 77 | self.timeout = int(cfg.get('timeout', '20')) |
---|
| 78 | |
---|
| 79 | logger.debug('Socket timeout: {}'.format(self.timeout)) |
---|
| 80 | socket.setdefaulttimeout(self.timeout) |
---|
| 81 | |
---|
| 82 | # Now load modules |
---|
| 83 | self.modules = loadModules(self) |
---|
| 84 | logger.debug('Modules: {}'.format(list(v.name for v in self.modules))) |
---|
| 85 | |
---|
| 86 | def stop(self): |
---|
[e274dc0] | 87 | """ |
---|
[11f7a07] | 88 | Requests service termination |
---|
[e274dc0] | 89 | """ |
---|
[11f7a07] | 90 | self.isAlive = False |
---|
| 91 | |
---|
| 92 | # ******************************** |
---|
| 93 | # * Internal messages processors * |
---|
| 94 | # ******************************** |
---|
[e274dc0] | 95 | def notifyLogin(self, data): |
---|
| 96 | username = data.decode('utf-8') |
---|
[11f7a07] | 97 | for v in self.modules: |
---|
| 98 | try: |
---|
| 99 | logger.debug('Notifying login of user {} to module {}'.format(username, v.name)) |
---|
| 100 | v.onLogin(username) |
---|
| 101 | except Exception as e: |
---|
| 102 | logger.error('Got exception {} processing login message on {}'.format(e, v.name)) |
---|
| 103 | |
---|
[e274dc0] | 104 | def notifyLogout(self, data): |
---|
| 105 | username = data.decode('utf-8') |
---|
[11f7a07] | 106 | for v in self.modules: |
---|
| 107 | try: |
---|
| 108 | logger.debug('Notifying logout of user {} to module {}'.format(username, v.name)) |
---|
| 109 | v.onLogout(username) |
---|
| 110 | except Exception as e: |
---|
| 111 | logger.error('Got exception {} processing logout message on {}'.format(e, v.name)) |
---|
| 112 | |
---|
| 113 | def notifyMessage(self, data): |
---|
[e274dc0] | 114 | module, message, data = data.decode('utf-8').split('\0') |
---|
[11f7a07] | 115 | for v in self.modules: |
---|
| 116 | if v.name == module: # Case Sensitive!!!! |
---|
| 117 | try: |
---|
| 118 | logger.debug('Notifying message {} to module {} with json data {}'.format(message, v.name, data)) |
---|
| 119 | v.processClientMessage(message, json.loads(data)) |
---|
| 120 | return |
---|
| 121 | except Exception as e: |
---|
| 122 | logger.error('Got exception {} processing generic message on {}'.format(e, v.name)) |
---|
| 123 | |
---|
| 124 | logger.error('Module {} not found, messsage {} not sent'.format(module, message)) |
---|
| 125 | |
---|
| 126 | def clientMessageProcessor(self, msg, data): |
---|
[e274dc0] | 127 | """ |
---|
[11f7a07] | 128 | Callback, invoked from IPC, on its own thread (not the main thread). |
---|
| 129 | This thread will "block" communication with agent untill finished, but this should be no problem |
---|
[e274dc0] | 130 | """ |
---|
[11f7a07] | 131 | logger.debug('Got message {}'.format(msg)) |
---|
| 132 | |
---|
| 133 | if msg == ipc.REQ_LOGIN: |
---|
| 134 | self.notifyLogin(data) |
---|
| 135 | elif msg == ipc.REQ_LOGOUT: |
---|
| 136 | self.notifyLogout(data) |
---|
| 137 | elif msg == ipc.REQ_MESSAGE: |
---|
| 138 | self.notifyMessage(data) |
---|
| 139 | |
---|
| 140 | def initialize(self): |
---|
[e274dc0] | 141 | """ |
---|
| 142 | Initialize listeners, modules, etc... |
---|
| 143 | """ |
---|
[11f7a07] | 144 | logger.debug('Starting IPC listener at {}'.format(IPC_PORT)) |
---|
| 145 | self.ipc = ipc.ServerIPC(self.ipcport, clientMessageProcessor=self.clientMessageProcessor) |
---|
| 146 | self.ipc.start() |
---|
| 147 | |
---|
| 148 | # And http threaded server |
---|
| 149 | self.httpServer = httpserver.HTTPServerThread(self.address, self) |
---|
| 150 | self.httpServer.start() |
---|
| 151 | |
---|
| 152 | # And lastly invoke modules activation |
---|
| 153 | validMods = [] |
---|
| 154 | for mod in self.modules: |
---|
| 155 | try: |
---|
| 156 | logger.debug('Activating module {}'.format(mod.name)) |
---|
| 157 | mod.activate() |
---|
| 158 | validMods.append(mod) |
---|
| 159 | except Exception as e: |
---|
| 160 | logger.exception() |
---|
| 161 | logger.error("Activation of {} failed: {}".format(mod.name, exceptionToMessage(e))) |
---|
| 162 | |
---|
| 163 | self.modules[:] = validMods # copy instead of assignment |
---|
| 164 | |
---|
| 165 | logger.debug('Modules after activation: {}'.format(list(v.name for v in self.modules))) |
---|
| 166 | |
---|
| 167 | def terminate(self): |
---|
| 168 | # First invoke deactivate on modules |
---|
| 169 | for mod in reversed(self.modules): |
---|
| 170 | try: |
---|
| 171 | logger.debug('Deactivating module {}'.format(mod.name)) |
---|
| 172 | mod.deactivate() |
---|
| 173 | except Exception as e: |
---|
| 174 | logger.exception() |
---|
| 175 | logger.error("Deactivation of {} failed: {}".format(mod.name, exceptionToMessage(e))) |
---|
| 176 | |
---|
| 177 | # Remove IPC threads |
---|
| 178 | if self.ipc is not None: |
---|
| 179 | try: |
---|
| 180 | self.ipc.stop() |
---|
| 181 | except Exception: |
---|
| 182 | logger.error('Couln\'t stop ipc server') |
---|
| 183 | |
---|
| 184 | if self.httpServer is not None: |
---|
| 185 | try: |
---|
| 186 | self.httpServer.stop() |
---|
| 187 | except Exception: |
---|
| 188 | logger.error('Couln\'t stop RESTApi server') |
---|
| 189 | |
---|
| 190 | self.notifyStop() |
---|
| 191 | |
---|
| 192 | # **************************************** |
---|
| 193 | # Methods that CAN BE overridden by agents |
---|
| 194 | # **************************************** |
---|
| 195 | def doWait(self, miliseconds): |
---|
[e274dc0] | 196 | """ |
---|
[11f7a07] | 197 | Invoked to wait a bit |
---|
| 198 | CAN be OVERRIDDEN |
---|
[e274dc0] | 199 | """ |
---|
[11f7a07] | 200 | time.sleep(float(miliseconds) / 1000) |
---|
| 201 | |
---|
| 202 | def notifyStop(self): |
---|
[e274dc0] | 203 | """ |
---|
[11f7a07] | 204 | Overridden to log stop |
---|
[e274dc0] | 205 | """ |
---|
[11f7a07] | 206 | logger.info('Service is being stopped') |
---|
| 207 | |
---|
| 208 | # *************************************************** |
---|
| 209 | # * Helpers, convenient methods to facilitate comms * |
---|
| 210 | # *************************************************** |
---|
| 211 | def sendClientMessage(self, toModule, message, data): |
---|
[e274dc0] | 212 | """ |
---|
[11f7a07] | 213 | Sends a message to the clients using IPC |
---|
| 214 | The data is converted to json, so ensure that it is serializable. |
---|
| 215 | All IPC is asynchronous, so if you expect a response, this will be sent by client using another message |
---|
[e274dc0] | 216 | |
---|
[11f7a07] | 217 | @param toModule: Module that will receive this message |
---|
| 218 | @param message: Message to send |
---|
[e274dc0] | 219 | @param data: data to send |
---|
| 220 | """ |
---|
[11f7a07] | 221 | self.ipc.sendMessageMessage('\0'.join((toModule, message, json.dumps(data)))) |
---|
| 222 | |
---|
| 223 | def sendScriptMessage(self, script): |
---|
[e274dc0] | 224 | """ |
---|
[11f7a07] | 225 | Sends an script to be executed by client |
---|
[e274dc0] | 226 | """ |
---|
[11f7a07] | 227 | self.ipc.sendScriptMessage(script) |
---|
| 228 | |
---|
| 229 | def sendLogoffMessage(self): |
---|
[e274dc0] | 230 | """ |
---|
[11f7a07] | 231 | Sends a logoff message to client |
---|
[e274dc0] | 232 | """ |
---|
[11f7a07] | 233 | self.ipc.sendLoggofMessage() |
---|
[1deb0d1] | 234 | |
---|
| 235 | def sendPopupMessage(self, title, message): |
---|
[e274dc0] | 236 | """ |
---|
| 237 | Sends a popup box to be displayed by client |
---|
| 238 | """ |
---|
[1deb0d1] | 239 | self.ipc.sendPopupMessage(title, message) |
---|