Compare commits
16 Commits
log-inogli
...
main
Author | SHA1 | Date |
---|---|---|
|
c0483c93c1 | |
|
83a25947bd | |
|
d180917d3b | |
|
8b8204d10f | |
|
25b2cb6cd8 | |
|
fc8b072860 | |
|
860fb61677 | |
|
33f65a45b7 | |
|
75d222c425 | |
|
e783d7c1fa | |
|
ee183f6ad3 | |
|
054071a5ab | |
|
95360a244e | |
|
cbfcc22d8a | |
|
9fe1b5d1d5 | |
|
565299c7c0 |
35
CHANGELOG.md
35
CHANGELOG.md
|
@ -6,6 +6,41 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [7.3.2] - 2025-08-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix syntax
|
||||
|
||||
## [7.3.1] - 2025-08-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- On user logout, write the user to the log
|
||||
- On EjecutarScript with client=true, return a job ID
|
||||
|
||||
## [7.3.0] - 2025-07-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- Wait for zombies
|
||||
|
||||
### Changed
|
||||
|
||||
- Change "jobid" for "job_id" for consistency
|
||||
|
||||
## [7.2.2] - 2025-07-30
|
||||
|
||||
### Added
|
||||
|
||||
- Add missing file stop-agent.ps1
|
||||
|
||||
## [7.2.1] - 2025-07-29
|
||||
|
||||
### Changed
|
||||
|
||||
- Run process in a new POSIX session and group, send termination signals to the whole process group
|
||||
|
||||
## [7.2.0] - 2025-07-28
|
||||
|
||||
### Added
|
||||
|
|
|
@ -1,3 +1,35 @@
|
|||
ogagent (7.3.2-1) stable; urgency=medium
|
||||
|
||||
* Fix syntax
|
||||
|
||||
-- OpenGnsys developers <info@opengnsys.es> Mon, 04 Aug 2025 14:36:47 +0200
|
||||
|
||||
ogagent (7.3.1-1) stable; urgency=medium
|
||||
|
||||
* On user logout, write the user to the log
|
||||
* On EjecutarScript with client=true, return a job ID
|
||||
|
||||
-- OpenGnsys developers <info@opengnsys.es> Mon, 04 Aug 2025 14:22:52 +0200
|
||||
|
||||
ogagent (7.3.0-1) stable; urgency=medium
|
||||
|
||||
* Wait for zombies
|
||||
* Change "jobid" for "job_id" for consistency
|
||||
|
||||
-- OpenGnsys developers <info@opengnsys.es> Thu, 31 Jul 2025 10:35:16 +0200
|
||||
|
||||
ogagent (7.2.2-1) stable; urgency=medium
|
||||
|
||||
* Add missing file stop-agent.ps1
|
||||
|
||||
-- OpenGnsys developers <info@opengnsys.es> Wed, 30 Jul 2025 10:20:21 +0200
|
||||
|
||||
ogagent (7.2.1-1) stable; urgency=medium
|
||||
|
||||
* Run process in a new POSIX session and group, send termination signals to the whole process group
|
||||
|
||||
-- OpenGnsys developers <info@opengnsys.es> Tue, 29 Jul 2025 12:52:08 +0200
|
||||
|
||||
ogagent (7.2.0-1) stable; urgency=medium
|
||||
|
||||
* Log whether we are in ogLive or not
|
||||
|
|
|
@ -1 +1 @@
|
|||
7.2.0
|
||||
7.3.2
|
||||
|
|
|
@ -110,6 +110,11 @@ class ClientProcessor(threading.Thread):
|
|||
logger.debug('Got Client message {}={}'.format(msg, REV_DICT.get(msg)))
|
||||
if self.parent.clientMessageProcessor is not None:
|
||||
self.parent.clientMessageProcessor(msg, data)
|
||||
if msg == REQ_LOGIN:
|
||||
if b',' in data:
|
||||
self.user = data.split (b',')[0]
|
||||
else:
|
||||
self.user = data
|
||||
|
||||
def run(self):
|
||||
self.running = True
|
||||
|
@ -197,8 +202,14 @@ class ClientProcessor(threading.Thread):
|
|||
logger.error('Invalid message in queue: {}'.format(e))
|
||||
|
||||
logger.debug('Client processor stopped')
|
||||
if os.path.exists ('/windows/temp'): open ('/windows/temp/ogagentuser_died', 'w').close()
|
||||
else: open ( '/tmp/ogagentuser_died', 'w').close()
|
||||
if os.path.exists ('/windows/temp'):
|
||||
fd = open ('/windows/temp/ogagentuser_died', 'wb')
|
||||
fd.write (self.user)
|
||||
fd.close()
|
||||
else:
|
||||
fd = open ('/tmp/ogagentuser_died', 'wb')
|
||||
fd.write (self.user)
|
||||
fd.close()
|
||||
try:
|
||||
self.clientSocket.close()
|
||||
except Exception:
|
||||
|
|
|
@ -23,32 +23,32 @@ class JobMgr():
|
|||
logger.debug ('args "{}"'.format (args))
|
||||
now = datetime.now (tz=timezone.utc)
|
||||
ts = now.strftime ('%Y-%m-%d %H:%M:%S.%f%z') ## '%s' doesn't work on windows
|
||||
jobid = hashlib.sha256 (now.isoformat().encode('UTF-8') + script.encode ('UTF-8')).hexdigest()[0:12]
|
||||
job_id = hashlib.sha256 (now.isoformat().encode('UTF-8') + script.encode ('UTF-8')).hexdigest()[0:12]
|
||||
p = subprocess.Popen (args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
self.jobs[jobid] = { 'p': p, 'pid': p.pid, 'starttime': ts, 'script': script, 'client': is_client, 'status': 'running', 'stdout': '', 'stderr': '' }
|
||||
self.jobs[jobid]['t1'] = threading.Thread (target=job_readstdout, args=(self.jobs[jobid],))
|
||||
self.jobs[jobid]['t2'] = threading.Thread (target=job_readstderr, args=(self.jobs[jobid],))
|
||||
self.jobs[jobid]['t1'].start()
|
||||
self.jobs[jobid]['t2'].start()
|
||||
self.jobs[job_id] = { 'p': p, 'pid': p.pid, 'starttime': ts, 'script': script, 'client': is_client, 'status': 'running', 'stdout': '', 'stderr': '' }
|
||||
self.jobs[job_id]['t1'] = threading.Thread (target=job_readstdout, args=(self.jobs[job_id],))
|
||||
self.jobs[job_id]['t2'] = threading.Thread (target=job_readstderr, args=(self.jobs[job_id],))
|
||||
self.jobs[job_id]['t1'].start()
|
||||
self.jobs[job_id]['t2'].start()
|
||||
logger.debug ('jobs "{}"'.format (self.jobs))
|
||||
return jobid
|
||||
return job_id
|
||||
|
||||
def prepare_jobs(self):
|
||||
## can't return self.jobs because the Popen object at self.jobs[id]['p'] is not serializable. So, need to create a new dict to return
|
||||
st = []
|
||||
for jobid in self.jobs:
|
||||
j = self.jobs[jobid]
|
||||
for job_id in self.jobs:
|
||||
j = self.jobs[job_id]
|
||||
entry = dict ((k, j[k]) for k in ['pid', 'starttime', 'script', 'client', 'status', 'stdout', 'stderr'])
|
||||
entry['jobid'] = jobid
|
||||
entry['job_id'] = job_id
|
||||
if j['p'].poll() is not None: ## process finished
|
||||
entry['rc'] = j['p'].returncode
|
||||
entry['status'] = 'finished'
|
||||
st.append (entry)
|
||||
return st
|
||||
|
||||
def terminate_job(self, jobid):
|
||||
if jobid not in self.jobs: return {}
|
||||
p = self.jobs[jobid]['p']
|
||||
def terminate_job(self, job_id):
|
||||
if job_id not in self.jobs: return {}
|
||||
p = self.jobs[job_id]['p']
|
||||
p.terminate()
|
||||
time.sleep (1)
|
||||
if p.poll() is not None:
|
||||
|
|
|
@ -74,10 +74,12 @@ class OGAgentSvc(Daemon, CommonService):
|
|||
while self.isAlive:
|
||||
client_died=False
|
||||
if os.path.exists ('/tmp/ogagentuser_died'):
|
||||
with open ('/tmp/ogagentuser_died', 'rb') as fd:
|
||||
u = fd.read()
|
||||
os.unlink ('/tmp/ogagentuser_died')
|
||||
client_died=True
|
||||
if client_died:
|
||||
self.notifyLogout (b'')
|
||||
self.notifyLogout (u)
|
||||
|
||||
# In milliseconds, will break
|
||||
self.doWait(1000)
|
||||
|
|
|
@ -50,13 +50,13 @@ class OpenGnSysWorker(ClientWorker):
|
|||
def process_script(self, json_params):
|
||||
script = json_params['code']
|
||||
logger.debug('Processing message: script({})'.format(script))
|
||||
self.jobmgr.launch_job (script, True)
|
||||
#self.sendServerMessage('script', {'op', 'launched'})
|
||||
job_id = self.jobmgr.launch_job (script, True)
|
||||
self.sendServerMessage('script_launched', {'op': 'launched', 'job_id': job_id})
|
||||
|
||||
def process_terminatescript(self, json_params):
|
||||
jobid = json_params['jobid']
|
||||
logger.debug('Processing terminatescript request, jobid "{}"'.format (jobid))
|
||||
self.jobmgr.terminate_job (jobid)
|
||||
job_id = json_params['job_id']
|
||||
logger.debug('Processing terminatescript request, job_id "{}"'.format (job_id))
|
||||
self.jobmgr.terminate_job (job_id)
|
||||
|
||||
def process_preparescripts(self, json_params):
|
||||
logger.debug('Processing preparescripts request')
|
||||
|
|
|
@ -369,24 +369,46 @@ class OpenGnSysWorker(ServerWorker):
|
|||
logger.debug('received script "{}"'.format(script))
|
||||
|
||||
if post_params.get('client', 'false') == 'false':
|
||||
jobid = self.jobmgr.launch_job (script, False)
|
||||
return {'op': 'launched', 'jobid': jobid}
|
||||
job_id = self.jobmgr.launch_job (script, False)
|
||||
return {'op': 'launched', 'job_id': job_id}
|
||||
|
||||
else: ## post_params.get('client') is not 'false'
|
||||
## send script as-is
|
||||
self.sendClientMessage('script', {'code': script})
|
||||
#return {'op': 'launched', 'jobid': jobid} ## TODO obtain jobid generated at the client (can it be done?)
|
||||
return {'op': 'launched'}
|
||||
|
||||
## wait for job_id generated at the client
|
||||
job_id = None
|
||||
iters = 0
|
||||
while True:
|
||||
time.sleep (0.2)
|
||||
if os.path.exists ('/tmp/EjecutarScript-jobid'):
|
||||
with open ('/tmp/EjecutarScript-jobid', 'r') as fd:
|
||||
job_id = fd.read()
|
||||
break
|
||||
iters += 1
|
||||
if iters >= 10: break
|
||||
|
||||
try: os.unlink ('/tmp/EjecutarScript-jobid')
|
||||
except: pass
|
||||
|
||||
if job_id is None: return {'op': 'launched'}
|
||||
else: return {'op': 'launched', 'job_id': job_id}
|
||||
|
||||
def process_client_script_launched(self, data):
|
||||
fd = open ('/tmp/EjecutarScript-jobid', 'w')
|
||||
fd.write (data['job_id'])
|
||||
fd.close()
|
||||
return True
|
||||
|
||||
@execution_level('full')
|
||||
@check_secret
|
||||
def process_terminatescript(self, path, get_params, post_params, server):
|
||||
jobid = post_params.get('jobid', None)
|
||||
logger.debug('Processing terminate_script request, jobid "{}"'.format (jobid))
|
||||
if jobid is None:
|
||||
job_id = post_params.get('job_id', None)
|
||||
logger.debug('Processing terminate_script request, job_id "{}"'.format (job_id))
|
||||
if job_id is None:
|
||||
return {}
|
||||
self.sendClientMessage('terminatescript', {'jobid': jobid})
|
||||
self.jobmgr.terminate_job (jobid)
|
||||
self.sendClientMessage('terminatescript', {'job_id': job_id})
|
||||
self.jobmgr.terminate_job (job_id)
|
||||
return {}
|
||||
|
||||
@execution_level('full')
|
||||
|
|
|
@ -108,10 +108,12 @@ class OGAgentSvc(win32serviceutil.ServiceFramework, CommonService):
|
|||
while self.isAlive:
|
||||
client_died=False
|
||||
if os.path.exists ('/windows/temp/ogagentuser_died'):
|
||||
with open ('/windows/temp/ogagentuser_died', 'rb') as fd:
|
||||
u = fd.read()
|
||||
os.unlink ('/windows/temp/ogagentuser_died')
|
||||
client_died=True
|
||||
if client_died:
|
||||
self.notifyLogout (b'')
|
||||
self.notifyLogout (u)
|
||||
|
||||
# Pumps & processes any waiting messages
|
||||
pythoncom.PumpWaitingMessages()
|
||||
|
|
|
@ -35,6 +35,7 @@ import re
|
|||
import time
|
||||
try: import dbus ## don't fail on windows (the worker will later refuse to load anyway)
|
||||
except: pass
|
||||
import select
|
||||
import random
|
||||
import subprocess
|
||||
import threading
|
||||
|
@ -158,10 +159,10 @@ class ogLiveWorker(ServerWorker):
|
|||
if 'thread' not in self.thread_list[job_id]: return { 'res': 2, 'der': 'Job is not running' }
|
||||
t = self.thread_list[job_id]['thread']
|
||||
pid = self.thread_list[job_id]['child_pid']
|
||||
logger.debug (f'pid ({pid})')
|
||||
logger.debug (f'pid/pgid/sid ({pid})')
|
||||
try_times = 8
|
||||
sig = signal.SIGTERM
|
||||
msg = f'could not kill pid ({pid}) after ({try_times}) tries'
|
||||
msg = f'could not killpg pid ({pid}) after ({try_times}) tries'
|
||||
success = 2 ## mimic cmd['res'] in respuestaEjecucionComando(): "1" means success, "2" means failed
|
||||
while True:
|
||||
t.join (0.05)
|
||||
|
@ -175,10 +176,10 @@ class ogLiveWorker(ServerWorker):
|
|||
## this is fine in the first iteration of the loop, before we send any signals. In the rest of iterations, after some signals were sent, msg should be 'job terminated' instead.
|
||||
if pid:
|
||||
if os.path.exists (f'/proc/{pid}'):
|
||||
logger.debug (f'sending signal ({sig}) to pid ({pid})')
|
||||
logger.debug (f'sending signal ({sig}) to process group ({pid})')
|
||||
## if the process finishes just here, nothing happens: the signal is sent to the void
|
||||
os.kill (pid, sig)
|
||||
#subprocess.run (['kill', '--signal', str(sig), str(pid)])
|
||||
os.killpg (pid, sig)
|
||||
#subprocess.run (['kill', '--signal', str(sig), f'-{pid}']) ## negative PID is used for sending signals to the process group
|
||||
else:
|
||||
msg = f'pid ({pid}) is gone, nothing to kill'
|
||||
success = 1
|
||||
|
@ -283,31 +284,35 @@ class ogLiveWorker(ServerWorker):
|
|||
proc = ['bash', '-c', '{} {}'.format (devel_bash_prefix, exe)]
|
||||
|
||||
logger.debug ('subprocess.run ("{}")'.format (' '.join (proc)))
|
||||
p = subprocess.Popen (proc, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
p = subprocess.Popen (proc, stdout=subprocess.PIPE, stderr=subprocess.PIPE, start_new_session=True)
|
||||
if self.pid_q:
|
||||
self.pid_q.put (p.pid)
|
||||
self.pid_q.put (p.pid) ## p.pid is also a session ID and a process group ID--we'll use it later to send signals to the whole group
|
||||
else:
|
||||
## esto sucede por ejemplo cuando arranca el agente, que estamos en interfaceAdmin() en el mismo hilo, sin _long_running_job ni hilo separado
|
||||
#logger.debug ('no queue--not writing any PID to it')
|
||||
pass
|
||||
|
||||
sout = serr = ''
|
||||
poll_iterations = 1
|
||||
while p.poll() is None:
|
||||
for l in iter (p.stdout.readline, b''):
|
||||
finished = False
|
||||
while True:
|
||||
try:
|
||||
p.wait (0.05)
|
||||
finished = True
|
||||
except subprocess.TimeoutExpired:
|
||||
pass
|
||||
|
||||
ready_to_read, _, _ = select.select ([p.stdout, p.stderr], [], [], 0.2)
|
||||
if p.stdout in ready_to_read:
|
||||
l = p.stdout.readline()
|
||||
partial = l.decode ('utf-8', 'ignore')
|
||||
if self.stdout_q: self.stdout_q.put (partial)
|
||||
sout += partial
|
||||
for l in iter (p.stderr.readline, b''):
|
||||
if p.stderr in ready_to_read:
|
||||
l = p.stderr.readline()
|
||||
partial = l.decode ('utf-8', 'ignore')
|
||||
serr += partial
|
||||
|
||||
## poll quickly at first, then poll less frequently
|
||||
if poll_iterations > 15: sleep_time = 1
|
||||
elif poll_iterations > 10: sleep_time = 0.2
|
||||
else: sleep_time = 0.1
|
||||
time.sleep (sleep_time)
|
||||
poll_iterations += 1
|
||||
if finished: break
|
||||
sout = sout.strip()
|
||||
serr = serr.strip()
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
get-process -name ogagentuser|stop-process
|
Loading…
Reference in New Issue