advancing on actor

This commit is contained in:
Adolfo Gómez García 2019-12-11 13:05:10 +01:00
parent 91272fcbb8
commit 2509f41c85
9 changed files with 128 additions and 113 deletions

View File

@ -80,13 +80,17 @@ class UDSActorClient(threading.Thread):
self._running = True
time.sleep(0.4) # Wait a bit before sending login
# Notify loging and mark it
self.api.login(platform.operations.getCurrentUser())
while self._running:
time.sleep(1.1) # Sleeps between loop iterations
try:
# Notify loging and mark it
self.api.login(platform.operations.getCurrentUser())
self.api.logout(platform.operations.getCurrentUser())
while self._running:
time.sleep(1.1) # Sleeps between loop iterations
self.api.logout(platform.operations.getCurrentUser())
except Exception as e:
logger.error('Error on client loop: %s', e)
self._listener.stop() # async listener for service
@ -117,8 +121,11 @@ class UDSActorClient(threading.Thread):
ba = QByteArray()
buffer = QBuffer(ba)
buffer.open(QIODevice.WriteOnly)
pixmap.save(buffer)
return bytes(ba.toBase64()).decode() # 'result' of JSON will contain base64 of screen
pixmap.save(buffer, 'PNG')
buffer.close()
scrBase64 = bytes(ba.toBase64()).decode()
logger.debug('Screenshot length: %s', len(scrBase64))
return scrBase64 # 'result' of JSON will contain base64 of screen
def script(self, script: str) -> typing.Any:
tools.ScriptExecutorThread(script).start()

View File

@ -139,7 +139,10 @@ class HTTPServerThread(threading.Thread):
def stop(self) -> None:
if self._server:
logger.debug('Stopping Http-client Service')
self._app.api.unregister(self.url)
try:
self._app.api.unregister(self.url)
except Exception as e:
logger.error('Error unregistering on actor service: %s', e)
self._server.shutdown()
self._server = None
@ -153,6 +156,9 @@ class HTTPServerThread(threading.Thread):
# Register using app api
logger.debug('Registered %s', self.url)
self._app.api.register(self.url)
try:
self._app.api.register(self.url)
except Exception as e:
logger.error('Error registering on actor service: %s', e)
self._server.serve_forever()

View File

@ -148,6 +148,7 @@ class HTTPServerThread(threading.Thread):
os.unlink(self._certFile)
except Exception as e:
logger.error('Error removing certificate file: %s', e)
logger.debug('Http-server stopped')
def run(self):
HTTPServerHandler._service = self._service # pylint: disable=protected-access

View File

@ -32,7 +32,7 @@ import sys
import os
import time
import atexit
from signal import SIGTERM
from signal import SIGTERM, SIGKILL
from udsactor.log import logger
@ -143,9 +143,14 @@ class Daemon:
# Try killing the daemon process
try:
while True:
cnt = 10
while cnt:
cnt -= 1
os.kill(pid, SIGTERM)
time.sleep(1)
if not cnt:
os.kill(pid, SIGKILL)
except OSError as err:
if err.errno == 3: # No such process
if os.path.exists(self.pidfile):

View File

@ -83,7 +83,7 @@ class Logger:
if self.remoteLogger:
self.remoteLogger.log(self.own_token, level, msg)
except Exception as e:
self.localLogger.log(FATAL, 'Error notifying log to broker: {}'.format(e))
self.localLogger.log(DEBUG, 'Log to broker: {}'.format(e))
self.localLogger.log(level, msg)

View File

@ -180,7 +180,7 @@ class UDSServerApi(UDSApi):
if result.ok:
return result.json()['result']
except requests.ConnectionError as e:
raise RESTConnectionError(str(e))
raise RESTConnectionError(e)
except RESTError:
raise
except Exception as e:

View File

@ -124,6 +124,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self._rebootRequested = True
def setReady(self) -> None:
if not self._isAlive:
return
# First, if postconfig is available, execute it and disable it
if self._cfg.post_command:
self.execute(self._cfg.post_command, 'postConfig')
@ -140,7 +143,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
try:
self._certificate = self._api.ready(self._cfg.own_token, self._secret, srvInterface.ip, rest.LISTEN_PORT)
except rest.RESTConnectionError as e:
logger.info('Error connecting with UDS Broker: %s', e)
logger.info('Error connecting with UDS Broker')
self.doWait(5000)
continue
except Exception as e:
@ -151,18 +154,24 @@ class CommonService: # pylint: disable=too-many-instance-attributes
platform.operations.reboot() # On too many errors, simply reboot
# Success or any error that is not recoverable (retunerd by UDS). if Error, service will be cleaned in a while.
break
else:
logger.error('Could not locate IP address!!!. (Not registered with UDS)')
# Do not continue if not alive...
if not self._isAlive:
return
# Cleans sensible data
if self._cfg.config:
self._cfg = self._cfg._replace(config=self._cfg.config._replace(os=None), data=None)
platform.store.writeConfig(self._cfg)
logger.debug('Done setReady')
self._startHttpServer()
def configureMachine(self) -> bool:
if not self._isAlive:
return False
# First, if runonce is present, honor it and remove it from config
# Return values is "True" for keep service (or daemon) running, False if Stop it.
if self._cfg.runonce_command:
@ -205,7 +214,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
return True
def initialize(self) -> bool:
if not self._cfg.host: # Not configured
if not self._cfg.host or not self._isAlive: # Not configured or not running
return False
# Force time sync, just in case...
@ -345,7 +354,12 @@ class CommonService: # pylint: disable=too-many-instance-attributes
Invoked to wait a bit
CAN be OVERRIDEN
'''
time.sleep(float(miliseconds) / 1000)
seconds = miliseconds / 1000.0
# So it can be broken by "stop"
while self._isAlive and seconds > 1:
time.sleep(1)
seconds -= 1
time.sleep(seconds)
def notifyStop(self) -> None:
'''

View File

@ -30,13 +30,10 @@
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import json
import logging
import random
import typing
import requests
from django.utils.translation import ugettext as _
from django.db.models import Q
from django.db import transaction

View File

@ -10,116 +10,101 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__)
TIMEOUT = 2
def notifyPreconnect(userService: 'UserService', userName: str, protocol: str) -> None:
'''
Notifies a preconnect to an user service
'''
proxy = userService.deployed_service.proxy
class NoActorComms(Exception):
pass
class OldActorVersion(NoActorComms):
pass
def _requestActor(
userService: 'UserService',
method: str,
data: typing.Optional[typing.MutableMapping[str, typing.Any]] = None,
minVersion: typing.Optional[str] = None
) -> typing.Any:
"""
Makes a request to actor using "method"
if data is None, request is done using GET, else POST
if no communications url is provided or no min version, raises a "NoActorComms" exception (or OldActorVersion, derived from NoActorComms)
Returns request response value interpreted as json
"""
url = userService.getCommsUrl()
ip, hostname = userService.getConnectionSource()
if not url:
logger.debug('No notification is made because agent does not supports notifications')
return
logger.warning('No notification is made because agent does not supports notifications: %s', userService.friendly_name)
raise NoActorComms('No notification urls for {}'.format(userService.friendly_name))
url += '/preConnect'
minVersion = minVersion or '2.0.0'
version = userService.getProperty('actor_version') or '0.0.0'
if '-' in version or version < minVersion:
logger.warning('Pool %s has old actors (%s)', userService.deployed_service.name, version)
raise OldActorVersion('Old actor version {} for {}'.format(version, userService.friendly_name))
url += '/' + method
proxy = userService.deployed_service.proxy
try:
data = {'user': userName, 'protocol': protocol, 'ip': ip, 'hostname': hostname}
if proxy is not None:
r = proxy.doProxyRequest(url=url, data=data, timeout=2)
r = proxy.doProxyRequest(url=url, data=data, timeout=TIMEOUT)
else:
r = requests.post(
url,
data=json.dumps(data),
headers={'content-type': 'application/json'},
verify=False,
timeout=2
)
r = json.loads(r.content)
logger.debug('Sent pre-connection to client using %s: %s', url, r)
# In fact we ignore result right now
except Exception as e:
logger.info('preConnection failed: %s. Check connection on destination machine: %s', e, url)
def checkUuid(userService: 'UserService') -> bool:
'''
Checks if the uuid of the service is the same of our known uuid on DB
'''
proxy = userService.deployed_service.proxy
url = userService.getCommsUrl()
if not url:
logger.debug('No uuid to retrieve because agent does not supports notifications')
return True # UUid is valid because it is not supported checking it
version = userService.getProperty('actor_version') or ''
# Just for 2.0 or newer, previous actors will not support this method.
# Also externally supported agents will not support this method (as OpenGnsys)
if '-' in version or version < '2.0.0':
return True
url += '/uuid'
try:
if proxy:
r = proxy.doProxyRequest(url=url, timeout=5)
else:
r = requests.get(url, verify=False, timeout=5)
if version >= '3.0.0': # New type of response: {'result': uuid}
uuid = r.json()['result']
else:
uuid = r.json()
if uuid != userService.uuid:
logger.info('The requested machine has uuid %s and the expected was %s', uuid, userService.uuid)
return False
logger.debug('Got uuid from machine: %s %s %s', url, uuid, userService.uuid)
# In fact we ignore result right now
except Exception as e:
logger.error('Get uuid failed: %s. Check connection on destination machine: %s', e, url)
return True
def requestScreenshot(userService: 'UserService') -> bytes:
"""
Returns an screenshot in PNG format (bytes) or empty png if not supported
"""
png = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
proxy = userService.deployed_service.proxy
url = userService.getCommsUrl()
version = userService.getProperty('actor_version') or ''
# Just for 3.0 or newer, previous actors will not support this method.
# Also externally supported agents will not support this method (as OpenGnsys)
if '-' in version or version < '3.0.0':
url = ''
if url:
try:
data: typing.Dict[str, str] = {}
if proxy is not None:
r = proxy.doProxyRequest(url=url, data=data, timeout=2)
if data is None:
r = requests.get(url, verify=False, timeout=TIMEOUT)
else:
r = requests.post(
url,
data=json.dumps(data),
headers={'content-type': 'application/json'},
verify=False,
timeout=2
timeout=TIMEOUT
)
png = json.loads(r.content)['result']
js = r.json()
# In fact we ignore result right now
except Exception as e:
logger.error('Get uuid failed: %s. Check connection on destination machine: %s', e, url)
if version >= '3.0.0':
js = js['result']
logger.debug('Requested %s to actor. Url=%s, Result=%s', method, url, js)
# In fact we ignore result right now
except Exception as e:
logger.warning('Request %s failed: %s. Check connection on destination machine: %s', method, e, url)
js = None
return base64.b64decode(png)
return js
def notifyPreconnect(userService: 'UserService', userName: str, protocol: str) -> None:
'''
Notifies a preconnect to an user service
'''
ip, hostname = userService.getConnectionSource()
try:
_requestActor(userService, 'preConnect', {'user': userName, 'protocol': protocol, 'ip': ip, 'hostname': hostname})
except NoActorComms:
pass # If no preconnect, warning will appear on UDS log
def checkUuid(userService: 'UserService') -> bool:
'''
Checks if the uuid of the service is the same of our known uuid on DB
'''
try:
uuid = _requestActor(userService, 'uuid')
if uuid != userService.uuid:
logger.info('Machine %s do not have expected uuid %s, instead has %s', userService.friendly_name, userService.uuid, uuid)
return False
except NoActorComms:
pass
return True # Actor does not supports checking
def requestScreenshot(userService: 'UserService') -> bytes:
"""
Returns an screenshot in PNG format (bytes) or empty png if not supported
"""
emptyPng = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
try:
png = _requestActor(userService, 'screenshot', minVersion='3.0.0') # First valid version with screenshot is 3.0
except NoActorComms:
png = None
return base64.b64decode(png or emptyPng)
def sendScript(userService: 'UserService', script: str, forUser: bool = False) -> None: