source: ogAgent-Git/src/opengnsys/service.py @ 67d5e81

configure-ptt-chedecorare-oglive-methodsejecutarscript-b64fix-cfg2objfixes-winlgromero-filebeatmainmodulesnew-browserno-ptt-paramogadmcliogadmclient-statusogcore1oglogoglog2override-moduleping1ping2ping3ping4report-progresstlsunification2unification3
Last change on this file since 67d5e81 was e274dc0, checked in by Ramón M. Gómez <ramongomez@…>, 5 years ago

#940: Fix string and byte conversions

  • Property mode set to 100644
File size: 8.8 KB
Line 
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
29"""
30@author: Adolfo Gómez, dkmaster at dkmon dot com
31"""
32
33import json
34import socket
35import time
36
37from . import ipc
38from . import httpserver
39from .config import readConfig
40from .loader import loadModules
41from .log import logger
42from .utils import exceptionToMessage
43
44
45IPC_PORT = 10398
46
47
48class 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()
60        # Get opengnsys section as dict
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)))
69
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):
87        """
88        Requests service termination
89        """
90        self.isAlive = False
91       
92    # ********************************
93    # * Internal messages processors *
94    # ********************************
95    def notifyLogin(self, data):
96        username = data.decode('utf-8')
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   
104    def notifyLogout(self, data):
105        username = data.decode('utf-8')
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):
114        module, message, data = data.decode('utf-8').split('\0')
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):
127        """
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
130        """
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):
141        """
142        Initialize listeners, modules, etc...
143        """
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):
196        """
197        Invoked to wait a bit
198        CAN be OVERRIDDEN
199        """
200        time.sleep(float(miliseconds) / 1000)
201
202    def notifyStop(self):
203        """
204        Overridden to log stop
205        """
206        logger.info('Service is being stopped')
207       
208    # ***************************************************
209    # * Helpers, convenient methods to facilitate comms *
210    # ***************************************************
211    def sendClientMessage(self, toModule, message, data):
212        """
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
216
217        @param toModule: Module that will receive this message
218        @param message: Message to send
219        @param data: data to send
220        """
221        self.ipc.sendMessageMessage('\0'.join((toModule, message, json.dumps(data))))
222       
223    def sendScriptMessage(self, script):
224        """
225        Sends an script to be executed by client
226        """
227        self.ipc.sendScriptMessage(script)
228       
229    def sendLogoffMessage(self):
230        """
231        Sends a logoff message to client
232        """
233        self.ipc.sendLoggofMessage()
234       
235    def sendPopupMessage(self, title, message):
236        """
237        Sends a popup box to be displayed by client
238        """
239        self.ipc.sendPopupMessage(title, message)
Note: See TracBrowser for help on using the repository browser.