adding manager (manage khaganat process)

This commit is contained in:
Jerome Sagnole 2017-11-02 21:55:29 +01:00
parent 60d68bbdbb
commit bef18d2b52
8 changed files with 1362 additions and 7 deletions

179
code/khaganat/tools/client.py Executable file
View file

@ -0,0 +1,179 @@
#!/usr/bin/python3
#
# script to send command to manager khaganat process
#
# Copyright (C) 2017 AleaJactaEst
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ./client.py --server='172.17.0.2'
# ./client.py --log="debug" --show-log-console --server='172.17.0.2' --program="aes" --command="START"
# ./client.py --log="debug" --show-log-console --server='172.17.0.2' --program="aes" --command="STATUS"
# ./client.py --log="debug" --show-log-console --server='172.17.0.2' --program="aes" --command="ACTION" --action="coucou"
# ./client.py --log="debug" --show-log-console --server='172.17.0.2' --program="aes" --command="LOG" --firstline=0
# ./client.py --log="debug" --show-log-console --server='172.17.0.2' --program="aes" --command="STOP"
# ./client.py --log="debug" --show-log-console --server='172.17.0.2' --command="LIST"
# ./client.py --log="debug" --show-log-console --server='172.17.0.2' --command="SHUTDOWN"
# ./client.py --log="debug" --show-log-console --server='172.17.0.2' --command="STARTALL"
# ./client.py --log="debug" --show-log-console --server='172.17.0.2' --command="STATUSALL"
# ./client.py --key="/home/gameserver/khanat/key.pem" --cert="/home/gameserver/khanat/cert.pem" --log="debug" --show-log-console --command="STATUSALL"
import argparse
import logging
import logging.config
import http.client
import json
#ip='localhost'
def send_command(command='GET', path='/', host='localhost', port=8000):
conn = http.client.HTTPSConnection(host=host, port=port, key_file='crt/key.pem', cert_file='crt/cert.pem' )
conn.putrequest(command, path)
conn.endheaders()
response = conn.getresponse()
print(response.read())
def cmp_to_key():
'Convert a cmp= function into a key= function'
class K(object):
def __init__(self, obj, *args):
self.obj = obj
def __lt__(self, other):
try:
return int(self.obj) < int(other.obj)
except:
return self.obj < other.obj
def __gt__(self, other):
try:
return int(self.obj) > int(other.obj)
except:
return self.obj > other.obj
def __eq__(self, other):
try:
return int(self.obj) == int(other.obj)
except:
return self.obj == other.obj
def __le__(self, other):
try:
return int(self.obj) <= int(other.obj)
except:
return self.obj <= other.obj
def __ge__(self, other):
try:
return int(self.obj) >= int(other.obj)
except:
return self.obj >= other.obj
def __ne__(self, other):
try:
return int(self.obj) != int(other.obj)
except:
return self.obj != other.obj
return K
def send_json(jsonin={}, command='GET', path='/', host='localhost', port=8000, raw_data=False, remove_color=False,
key_file=None, cert_file=None):
conn = http.client.HTTPSConnection(host=host, port=port, key_file=key_file, cert_file=cert_file )
conn.putrequest(command, path)
out=json.dumps(jsonin)
conn.putheader('Content-type', 'application/json')
#length = int(self.headers.getheader('content-length'))
conn.putheader('Content-length', len(out))
conn.endheaders()
conn.send(bytes(out, "utf-8"))
response = conn.getresponse()
if raw_data:
print(response.read())
else:
if remove_color:
endText = '\x1b[0m'
else:
endText = ''
if response.status != 200:
logging.error("Error detected (html code:%d)" % response.status)
print(response.read())
return
ret = response.read().decode()
try:
msgjson = json.loads(ret)
except:
logging.error("Impossible to decode Json output")
print(ret)
return
for key in sorted(msgjson, key=cmp_to_key()):
print("%s: %s %s" % (key, msgjson[key], endText))
def main(server, command, program, action, firstline, fileLog, logLevel, show_log_console, port=8000,
raw_data=False, remove_color=False, key_file=None, cert_file=None):
# Manage log
logging.getLogger('logging')
numeric_level = getattr(logging, logLevel.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % logLevel)
handlers=[]
if show_log_console:
handlers.append(logging.StreamHandler())
if fileLog:
handlers.append(logging.FileHandler(fileLog.name))
logging.basicConfig(handlers=handlers, level=numeric_level,
format='%(asctime)s %(levelname)s [pid:%(process)d] [%(funcName)s:%(lineno)d] %(message)s')
#client(server, command, data)
#send_json({'name': 'aes', 'first-line': 0}, 'GET', '/LOG', server, port)
if command == 'START' or command == 'STOP':
send_json({'name': program}, 'POST', "/" + command, server, port)
elif command == 'STATUS':
send_json({'name': program}, 'GET', "/" + command, server, port)
elif command == 'ACTION':
send_json({'name': program, 'action' : action}, 'POST', "/" + command, server, port)
elif command == 'LOG':
send_json({'name': program, 'first-line' : firstline }, 'GET', "/" + command, server, port, raw_data, remove_color)
elif command == 'LIST':
send_json({}, 'GET', "/" + command, server, port)
elif command == 'SHUTDOWN' or command == 'STARTALL' or command == 'STOPALL':
send_json({}, 'POST', "/" + command, server, port)
elif command == 'STATUSALL':
send_json({}, 'GET', "/" + command, server, port)
else:
logging.error("command unknown (%s)" % command)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Manipulate khaganat process')
parser.add_argument('--version', action='version', version='%(prog)s 1.0')
parser.add_argument( '--show-log-console', action='store_true',
help='show message in console', default=False)
parser.add_argument('--filelog', type=argparse.FileType('wt'),
default=None, help='log file')
parser.add_argument('--log',
default='INFO', help='log level [DEBUG, INFO, WARNING, ERROR')
parser.add_argument('--key', help='key file', default=None)
parser.add_argument('--cert', help='cert file', default=None)
parser.add_argument('--server', help='server khganat', default='127.0.0.1')
parser.add_argument('--command', help='command send to khganat', default='/STATUS')
parser.add_argument('--program', help='program khaganat id ', default='aes')
parser.add_argument('--action', help='action ', default='')
parser.add_argument('--firstline', type=int,
help='define fistline read for log command', default=0)
parser.add_argument( '--raw-data', action='store_true',
help='show raw message', default=False)
parser.add_argument( '--keep-color', action='store_true',
help='some message have color define, by default we reset the color (this option keep current color state)', default=False)
args = parser.parse_args()
main(server = args.server, action = args.action, firstline = args.firstline,
command = args.command, program = args.program, fileLog = args.filelog,
logLevel=args.log, show_log_console=args.show_log_console,
raw_data = args.raw_data,
key_file=args.key, cert_file=args.cert,
remove_color=not args.keep_color)

824
code/khaganat/tools/manage.py Executable file
View file

@ -0,0 +1,824 @@
#!/usr/bin/python3
#
# script to start/stop/status/send command/read log for khaganat process
#
# Copyright (C) 2017 AleaJactaEst
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Manage all process khaganat
Launch this prorgam in background and use clientManager to manipulate process
you can launch command :
[POST] SHUTDOWN : Stop all process and stop manager
[POST] STARTALL : Start all process
[GET] STATUSALL : Get status all process
[POST] STOPALL : Stop all process
[POST] START {'name': program} : Start one program
[POST] ACTION {'name': program, 'action' : action} : Send action one program (send to input program)
[GET] STATUS {'name': program} : Get status one program
[POST] STOP {'name': program} : Stop one program
[GET] LOG {'name': program, 'first-line': firstline } : Get log for one program
Configuration File : This script need configuration file (see below for model)
------------------------------------------------------------------------------
[config]
# Define port listen (default 8000)
port = 8000
# Generate key
# openssl req -nodes -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -subj "/C=FR/ST=France/L=Paris/O=khaganat/CN=khaganat.org"
# key
keyfile = crt/key.pem
# certificate
certfile = crt/cert.pem
# address listen (default all port)
address =
# Admin Executor Service
[aes]
# command to launch the program
command = ryzom_admin_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/log/khanat --nobreak --fulladminname=admin_executor_service --shortadminname=AES
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
# buffer size (define value bufsize on subprocess.Popen, this buffer is use before read by manager)
bufsize = 100
# bms_master : backup_service
[bms_master]
# command to launch the program
command = ryzom_backup_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid -P49990
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# we keep [logsize] last number line stdout
logsize = 1000
# buffer size (define value bufsize on subprocess.Popen)
bufsize = 100
------------------------------------------------------------------------------
Example :
nohup ./manage.py --log info --filelog /home/gameserver/log/manager.log -c khaganat.cfg 2>/dev/null 1>/dev/null 0</dev/zero &
"""
# docker run -it -v $PWD:/opt/jsa servercontainer_khanat_debian_jessie_x86_64 /bin/bash
# ./manage.py --log debug --show-log-console -c test.cfg
# https://pymotw.com/2/multiprocessing/communication.html
# https://eli.thegreenplace.net/2012/01/24/distributed-computing-in-python-with-multiprocessing/
# https://stackoverflow.com/questions/375427/non-blocking-read-on-a-subprocess-pipe-in-python
import subprocess
import queue
import threading
import signal
import argparse
import configparser
import logging
import logging.config
import multiprocessing
import time
import ssl
import http.server
import json
import fcntl
import os
class ManageHttpRequest(http.server.SimpleHTTPRequestHandler):
"""
Class received all request and send to manager process
"""
def __init__(self, request, client_address, server):
""" Initialize object """
http.server.SimpleHTTPRequestHandler.__init__(self, request, client_address, server)
def log_message(self, format, *args):
""" function use to send log"""
logging.info("%s (%s) %s" %
(self.address_string(),
self.log_date_time_string(),
format%args))
def _set_headers(self):
""" Prepare header """
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
def command_log(self):
""" sub request log (send log on specific process) """
if 'content-type' in self.headers:
ctype = self.headers['content-type']
else:
ctype = 'text'
if ctype != 'application/json':
self.send_response(400)
self.end_headers()
return
if 'content-length' in self.headers:
try:
sizemsg = int(self.headers['content-length'])
except:
self.send_response(400)
self.end_headers()
return
else:
self.send_response(400)
self.end_headers()
return
msg = self.rfile.read(sizemsg)
msgjson = json.loads(msg.decode())
logging.debug(msgjson)
if 'name' not in msgjson:
self.send_error(400,'Missing param name')
logging.error("Missing param name")
return
name = msgjson['name']
if name not in self.server.listQueueIn:
self.send_error(400,'Name unknown')
logging.error("Name unknwon '%s'" % name)
return
if 'first-line' not in msgjson:
self.send_error(400,'Missing param first-line')
logging.error("Missing param first-line '%s'" % name)
return
firstLine = 0
try:
firstLine = int(msgjson['first-line'])
except:
self.send_error(400,'Impossible to read first-line')
logging.error("Impossible to read first-line '%s'" % msgjson['first-line'])
return
logging.debug("%s:%s" % (name, firstLine))
self.server.listEvent[name].set()
self.server.listQueueIn[name].put("LOG %d" % firstLine)
logging.debug("message envoye: %s" % (name))
try:
item = self.server.listQueueOut[name].get(timeout = 4)
except queue.Empty:
logging.debug("pas de message recu pour %s" % name)
return
self._set_headers()
self.wfile.write(bytes(item, "utf-8"))
logging.debug("item : %s" % item)
def send_list(self):
""" sub-request to list all program available """
outjson = {}
pos = 0
for program in self.server.listQueueIn:
outjson.setdefault(pos, program)
pos += 1
self._set_headers()
self.wfile.write(bytes(json.dumps(outjson), "utf-8"))
def send_shutdown(self):
""" Stop all program and stop manager """
for name in self.server.listQueueIn:
self.server.listEvent[name].set()
self.server.listQueueIn[name].put("SHUTDOWN")
self._set_headers()
outjson = {'shutdown':'ok'}
self.wfile.write(bytes(json.dumps(outjson), "utf-8"))
def send_command_all(self, action):
""" Send specific command on all program (start, stop, status)"""
outjson = {}
for name in self.server.listQueueIn:
self.server.listEvent[name].set()
self.server.listQueueIn[name].put(action)
try:
item = self.server.listQueueOut[name].get(timeout = 4)
except queue.Empty:
logging.debug("pas de message recu pour %s" % name)
return
outjson.setdefault(name, item)
self._set_headers()
self.wfile.write(bytes(json.dumps(outjson), "utf-8"))
def send_action(self):
""" send specific action on one program """
if 'content-type' in self.headers:
ctype = self.headers['content-type']
else:
ctype = 'text'
if ctype != 'application/json':
self.send_response(400)
self.end_headers()
return
if 'content-length' in self.headers:
try:
sizemsg = int(self.headers['content-length'])
except:
self.send_response(400)
self.end_headers()
return
else:
self.send_response(400)
self.end_headers()
return
msg = self.rfile.read(sizemsg)
msgjson = json.loads(msg.decode())
logging.debug(msgjson)
if 'name' not in msgjson:
self.send_error(400,'Missing param name')
logging.error("Missing param name")
return
name = msgjson['name']
if name not in self.server.listQueueIn:
self.send_error(400,'Name unknown')
logging.error("Name unknwon '%s'" % name)
return
if 'action' not in msgjson:
self.send_error(400,'Missing param action')
logging.error("Missing param action '%s'" % name)
return
action = ''
try:
action = msgjson['action']
except:
self.send_error(400,'Impossible to read action')
logging.error("Impossible to read first-line '%s'" % msgjson['action'])
return
logging.debug("%s:%s" % (name, action))
self.server.listEvent[name].set()
self.server.listQueueIn[name].put("ACTION %s" % action)
logging.debug("message envoye: %s" % (name))
try:
result = self.server.listQueueOut[name].get(timeout = 4)
except queue.Empty:
logging.debug("pas de message recu pour %s" % name)
return
outjson={'state': result}
self._set_headers()
self.wfile.write(bytes(json.dumps(outjson), "utf-8"))
def send_command(self, command):
""" Send specific command on one program (start, stop, status)"""
if 'content-type' in self.headers:
ctype = self.headers['content-type']
else:
ctype = 'text'
if ctype != 'application/json':
self.send_response(400)
self.end_headers()
return
if 'content-length' in self.headers:
try:
sizemsg = int(self.headers['content-length'])
except:
self.send_response(400)
self.end_headers()
return
else:
self.send_response(400)
self.end_headers()
return
msg = self.rfile.read(sizemsg)
msgjson = json.loads(msg.decode())
if 'name' not in msgjson:
self.send_error(400,'Missing param name')
logging.error("Missing param name")
return
name = msgjson['name']
if name not in self.server.listQueueIn:
self.send_error(400,'Name unknown')
logging.error("Name unknwon '%s'" % name)
return
logging.debug("[%s %s] Send command" % (command, name))
self.server.listEvent[name].set()
logging.debug("[%s %s] Sent command" % (command, name))
self.server.listQueueIn[name].put(command)
try:
result = self.server.listQueueOut[name].get(timeout = 4)
except queue.Empty:
self.send_error(500,'Missing return')
logging.debug("[%s %s] Missing return" % (command, name))
return
logging.debug("[%s %s] => %s" % (command, name, result))
outjson={'state': result}
self._set_headers()
self.wfile.write(bytes(json.dumps(outjson), "utf-8"))
def do_GET(self): # READ
""" Manage request READ
currently, we execute LOG, STATUS & LIST
"""
logging.debug('get recieved : %s' % self.path)
if self.path == '/LOG':
self.command_log()
elif self.path == '/STATUS':
self.send_command("STATUS")
elif self.path == '/LIST':
self.send_list()
elif self.path == '/STATUSALL':
self.send_command_all("STATUS")
else:
self.send_error(400,'Path unknown')
logging.error("Path unknwon '%s'" % self.path)
return
def do_POST(self): # CREATE
""" Manage request POST
currently, we execute START, STOP, ACTION & SHUTDOWN
"""
logging.debug('post recieved : %s' % self.path)
if self.path == '/START':
self.send_command("START")
elif self.path == '/STOP':
self.send_command("STOP")
elif self.path == '/ACTION':
self.send_action()
elif self.path == '/SHUTDOWN':
self.send_shutdown()
elif self.path == '/STARTALL':
self.send_command_all("START")
elif self.path == '/STOPALL':
self.send_command_all("STOP")
else:
self.send_error(400,'Path unknown')
logging.error("Path unknwon '%s'" % self.path)
return
def do_HEAD(self):
""" request HEAD received """
logging.debug('head recieved : %s' % self.path)
self.send_error(404,'File Not Found: %s' % self.path)
def do_PUT(self): # UPDATE/REPLACE
""" request PUT received """
logging.debug('put recieved!')
self.send_error(404,'File Not Found: %s' % self.path)
def do_PATCH(self): # UPDATE/MODIFY
""" request PATCH received """
logging.debug('patch recieved!')
self.send_error(404,'File Not Found: %s' % self.path)
def do_DELETE(self): # DELETE
""" request DELETE received """
logging.debug('delete recieved!')
self.send_error(404,'File Not Found: %s' % self.path)
class khaganatHTTPServer(http.server.HTTPServer):
"""
Class khaganatHTTPServer
"""
def __init__(self,
listQueueIn,
listQueueOut,
listEvent,
server_address,
RequestHandlerClass,
bind_and_activate=True):
http.server.HTTPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate)
self.listQueueIn = listQueueIn
self.listQueueOut = listQueueOut
self.listEvent = listEvent
class ServerHttp(multiprocessing.Process):
""" Initialize server HTTPS """
def __init__(self, keyfile, certfile, address = '', port=8000):
multiprocessing.Process.__init__(self)
self.listQueueIn = {}
self.listQueueOut = {}
self.listEvent = {}
self.port = port
self.keyfile = keyfile
self.certfile = certfile
self.address = address
def run(self):
server_address = (self.address, self.port)
httpd = khaganatHTTPServer(self.listQueueIn,
self.listQueueOut,
self.listEvent,
server_address,
ManageHttpRequest)
httpd.socket = ssl.wrap_socket (httpd.socket,
keyfile = self.keyfile,
certfile = self.certfile,
ca_certs=None,
server_side = True)
httpd.serve_forever()
def append(self, name, queueIn, queueOut, event):
self.listQueueIn.setdefault(name, queueIn)
self.listQueueOut.setdefault(name, queueOut)
self.listEvent.setdefault(name, event)
class ManageCommand():
"""
Thread manage all program
"""
def __init__(self, name, command, path, logsize, bufsize, queueIn, queueOut, event):
self.process = None
self.queueIn = queueIn
self.queueOut = queueOut
self.name = name
self.command = command
self.path = path
self.log = []
self.poslastlog = 0
self.maxlog = logsize
self.event = event
self.bufsize = bufsize
self.threadRead = None
self.running = False
self.state = multiprocessing.Queue()
self.pipeIn, self.pipeOut = multiprocessing.Pipe()
self.eventRunning = threading.Event()
def read_output(self):
fl = fcntl.fcntl(self.process.stdout, fcntl.F_GETFL)
fcntl.fcntl(self.process.stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
logging.debug("Start reader %s " % self.name)
while self.eventRunning.is_set():
#logging.debug("Start reader %s " % self.name)
try:
line = self.process.stdout.readline()
if not line:
time.sleep(1)
continue
now = time.strftime('%Y/%m/%d %H:%M:%S %Z')
logging.debug("line %s " % line)
self.poslastlog += 1
while len(self.log) >= self.maxlog:
self.log.pop(0)
msg = line.decode().strip()
self.log.append(now + ' ' + msg)
logging.debug("recu: '%s'" %(msg))
except:
continue
logging.debug("End reader: '%s'" % self.name)
def handler(self, signum, frame):
if self.process:
#logging.debug("Send signal %d to '%s'" %(signum, self.name))
self.process.send_signal(signum)
else:
logging.error("Impossible to send signal %d to '%s'" %(signum, self.name))
raise IOError("signal received")
def start(self):
logging.debug("start %s" % (self.name))
if self.process:
logging.debug("%s already exist" % self.name)
code = self.process.poll()
if code is None:
logging.debug("%s already exist" % self.name)
return "already-started"
else:
logging.debug("%s crashed" % self.name)
code = self.process.wait()
logging.error("%s crashed (return code:%d) - restart program" % (self.name, code))
try:
self.process = subprocess.Popen(self.command.split(),
cwd = self.path,
shell=False,
bufsize=self.bufsize,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
close_fds=True)
except FileNotFoundError as e:
logging.error("Impossible to start %s (%s)" % (self.name, e))
return "crashed"
self.eventRunning.set()
if self.threadRead:
self.eventRunning.clear()
self.threadRead.join()
self.threadRead = None
self.running = True
self.threadRead = threading.Thread(target=self.read_output)
self.threadRead.start()
return "started"
def status(self):
logging.debug("status %s" % (self.name))
if self.process:
logging.debug("status %s - check" % (self.name))
code = self.process.poll()
if code is None:
logging.debug("%s status" % (self.name))
return "started"
else:
logging.error("%s crashed (return code:%d)" % (self.name, code))
self.process = None
return "stopped"
else:
logging.debug("%s status" % (self.name))
return "stopped"
def list_thread(self):
logging.debug('list thread')
#main_thread = threading.currentThread()
for t in threading.enumerate():
logging.debug('thread %s', t.getName())
logging.debug("id %d" % t.ident)
def stop(self):
logging.debug("stop %s" % (self.name))
if not self.process:
return "stopped"
else:
code = self.process.poll()
loop = 10
while (code is None) and (loop > 0):
logging.debug("stop process %s" , self.name)
self.process.send_signal(15)
time.sleep(1)
code = self.process.poll()
loop -= 1
loop = 10
while (code is None) and (loop > 0):
logging.debug("terminate process %s" , self.name)
self.process.terminate()
time.sleep(1)
code = self.process.poll()
loop -= 1
loop = 10
while (code is None) and (loop > 0):
logging.debug("kill process %s" , self.name)
self.process.send_signal(9)
time.sleep(1)
code = self.process.poll()
loop -= 1
code = self.process.wait()
self.process = None
if self.threadRead:
self.eventRunning.clear()
self.threadRead.join()
self.threadRead = None
logging.info("%s stopped (return code:%d)" % (self.name, code))
return "stopped"
def getlog(self, firstline):
logging.debug("read log %d " % firstline)
outjson = {}
pos = self.poslastlog - len(self.log) + 1
firstlinefound = None
for line in self.log:
if pos >= firstline:
outjson.setdefault(pos, line)
if not firstlinefound:
firstlinefound = pos
pos += 1
outjson.setdefault('first-line', firstlinefound)
outjson.setdefault('last-line', pos - 1)
return json.dumps(outjson)
def action(self, action):
logging.debug("ACTION '%s'" % action)
if self.process:
code = self.process.poll()
if code is None:
if action:
self.process.stdin.write(bytes(action +'\n', 'UTF-8'))
self.process.stdin.flush()
return "ok"
return "ko"
def run(self):
loop = True
while loop:
logging.debug('wait %s' % self.name)
self.event.wait()
logging.debug('received event %s' % self.name)
try:
msg = self.queueIn.get(timeout = 4)
except queue.Empty:
self.event.clear()
logging.debug("pas de message recu pour %s" % self.name)
return
logging.debug("command : '%s'" % msg)
command = msg.split()[0]
if command == "SHUTDOWN":
loop = False
continue
elif command == "START":
self.queueOut.put(self.start())
elif command == "STATUS":
self.queueOut.put(self.status())
elif command == "STOP":
self.queueOut.put(self.stop())
elif command == "ACTION":
data = msg.split(maxsplit=1)[1]
self.queueOut.put(self.action(data))
elif command == "LOG":
try:
firstline = int(msg.split(maxsplit=1)[1])
except:
firstline = 0
self.queueOut.put(self.getlog(firstline))
else:
self.queueOut.put("error : command unknown")
self.event.clear()
self.stop()
self.event.clear()
logging.debug('end')
def runCommand(name, command, path, logsize, bufsize, queueIn, queueOut, event):
"""
Launch Manager
(thread to manage khaganat program)
"""
logging.debug("Initialize '%s'" % name)
manageCommand = ManageCommand(name=name,
command=command,
path=path,
logsize=logsize,
bufsize=bufsize,
queueIn=queueIn,
queueOut=queueOut,
event=event)
manageCommand.run()
class Manager():
def __init__(self, filecfg, launch_program):
self.threadCommand = None
self.command = []
self.launch_program = launch_program
self.param = {}
config = configparser.ConfigParser()
config.read_file(filecfg)
logging.debug("Sections :%s" % config.sections())
for name in config.sections():
if name == 'config':
logging.debug("read config '%s'" % name)
try:
port = int(config[name]['port'])
except:
port = 8000
try:
address = config[name]['address']
except:
address = ''
try:
keyfile = config[name]['keyfile']
except:
keyfile = 'crt/key.pem'
try:
certfile = config[name]['certfile']
except:
certfile = 'crt/cert.pem'
elif 'command' in config[name]:
logging.debug("read command '%s'" % name)
if 'path' in config[name]:
path = config[name]['path']
else:
path = None
if 'logsize' in config[name]:
try:
logsize = int(config[name]['logsize'])
except:
logsize = 100
logging.warning("Impossible to read param logsize (command:%s)", name)
else:
logsize = 100
if 'bufsize' in config[name]:
try:
bufsize = int(config[name]['bufsize'])
except:
bufsize = 100
logging.warning("Impossible to read param bufsize (command:%s)", name)
else:
bufsize = 100
self.param.setdefault(name, {'command': config[name]['command'], 'path': path, 'logsize': logsize, 'bufsize': bufsize})
self.serverHttp = ServerHttp(keyfile, certfile, address, port)
if filecfg is None:
raise ValueError
def launch_server_http(self):
self.serverHttp.daemon = True
self.serverHttp .start()
def launch_command(self):
for name in self.param:
logging.debug("Initialize '%s'" % name)
queueIn = multiprocessing.Queue()
queueOut = multiprocessing.Queue()
event = multiprocessing.Event()
self.serverHttp.append(name, queueIn, queueOut, event)
self.threadCommand = multiprocessing.Process(target=runCommand,
args=(name,
self.param[name]['command'],
self.param[name]['path'],
self.param[name]['logsize'],
self.param[name]['bufsize'],
queueIn,
queueOut,
event))
self.threadCommand.start()
if self.launch_program:
event.set()
queueIn.put("START")
try:
item = queueOut.get(timeout = 4)
except queue.Empty:
item = ""
logging.debug("pas de message recu pour %s" % name)
return
logging.info("%s => %s" % (name, item))
def receive_signal(self, signum, frame):
if self.threadCommand:
print(dir(self.threadCommand))
self.threadCommand.terminate()
if self.serverHttp:
self.serverHttp.terminate()
def run(self):
self.launch_command()
self.launch_server_http()
logging.info('started')
self.threadCommand.join()
logging.info('end')
signal.alarm(0)
logging.info('wait thread http')
time.sleep(1)
self.serverHttp.terminate()
self.serverHttp.join()
logging.info('end')
def main(filecfg, fileLog, logLevel, launch_program, show_log_console):
""" Main function """
# Manage log
logging.getLogger('logging')
numeric_level = getattr(logging, logLevel.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % logLevel)
handlers=[]
if show_log_console:
handlers.append(logging.StreamHandler())
if fileLog:
handlers.append(logging.FileHandler(fileLog.name))
logging.basicConfig(handlers=handlers, level=numeric_level,
format='%(asctime)s %(levelname)s [pid:%(process)d] [%(funcName)s:%(lineno)d] %(message)s')
if filecfg is None:
logging.error("Missing configuration file")
raise ValueError
manager = Manager(filecfg, launch_program)
manager.run()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Manage khaganat process')
parser.add_argument('--version', action='version', version='%(prog)s 1.0')
parser.add_argument('-c', '--conf', type=argparse.FileType('r'),
default='khaganat.cfg', help='configuration file')
parser.add_argument( '--show-log-console', action='store_true',
help='show message in console', default=False)
parser.add_argument('--filelog', type=argparse.FileType('wt'),
default=None, help='log file')
parser.add_argument('--log',
default='INFO', help='log level [DEBUG, INFO, WARNING, ERROR')
parser.add_argument( '--launch-program', action='store_true',
help='launch program when start manager', default=False)
args = parser.parse_args()
main(filecfg=args.conf,
fileLog=args.filelog,
logLevel=args.log,
launch_program=args.launch_program,
show_log_console=args.show_log_console)

View file

@ -0,0 +1,246 @@
#
# Configuration process khaganat
#
# Copyright (C) 2017 AleaJactaEst
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##############################
##############################
# Global parameter
##############################
##############################
[config]
# Define port listen (default 8000)
port = 8000
# Generate key
# openssl req -nodes -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -subj "/C=FR/ST=France/L=Paris/O=khaganat/CN=khaganat.org"
# key
keyfile = /home/gameserver/khanat/key.pem
# certificate
certfile = /home/gameserver/khanat/cert.pem
# address listen (default all port)
address =
##############################
##############################
# List all program we manage #
##############################
##############################
##############################
# Admin Executor Service
##############################
[aes]
# command to launch the program
command = ryzom_admin_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/log/khanat --nobreak --fulladminname=admin_executor_service --shortadminname=AES
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
# buffer size (define value bufsize on subprocess.Popen, this buffer is use before read by manager)
bufsize = 100
##############################
# bms_master : backup_service
##############################
[bms_master]
# command to launch the program
command = ryzom_backup_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid -P49990
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
#[bms_pd_master]
# # command to launch the program
# command = ryzom_backup_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid -P49992
# # Path : where this program is launched
# path = /home/gameserver/khanat/server/
# # size buffer log for each program launched (number line stdout)
# logsize = 1000
##############################
# egs : entities_game_service
##############################
[egs]
# command to launch the program
command = ryzom_entities_game_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# gpms : gpm_service
##############################
[gpms]
# command to launch the program
command = ryzom_gpm_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/gpms
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# ios : input_output_service
##############################
[ios]
# command to launch the program
command = ryzom_ios_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# rns : naming_service
##############################
[rns]
# command to launch the program
command = ryzom_naming_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# rws : welcome_service
##############################
[rws]
# command to launch the program
command = ryzom_welcome_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# ts : tick_service
##############################
[ts]
# command to launch the program
command = ryzom_tick_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# ms : mirror_service
##############################
[ms]
# command to launch the program
command = ryzom_mirror_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# ais_newbyland : ai_service
##############################
[ais_newbyland]
# command to launch the program
command = ryzom_ai_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid -mCommon:Newbieland:Post
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# mfs : mail_forum_service
##############################
[mfs]
# command to launch the program
command = ryzom_mail_forum_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# su : shard_unifier_service
##############################
[su]
# command to launch the program
command = ryzom_shard_unifier_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# fes : frontend_service
##############################
[fes]
# command to launch the program
command = ryzom_frontend_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# sbs : session_browser_server
##############################
[sbs]
# command to launch the program
command = ryzom_session_browser_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
##############################
# lgs : logger_service
##############################
[lgs]
# command to launch the program
command = ryzom_logger_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000
# [mos]
# # command to launch the program
# command = ryzom_monitor_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# # Path : where this program is launched
# path = /home/gameserver/khanat/server/
# # size buffer log for each program launched (number line stdout)
# logsize = 1000
# [pdss]
# # command to launch the program
# command = ryzom_pd_support_service -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# # Path : where this program is launched
# path = /home/gameserver/khanat/server/
# # size buffer log for each program launched (number line stdout)
# logsize = 1000
##############################
# ras : admin_service
##############################
[ras]
# command to launch the program
command = ryzom_admin_service --fulladminname=admin_service --shortadminname=AS -A/home/gameserver/khanat/server -C/home/gameserver/khanat/server -L/home/gameserver/khanat/server/log --nobreak --writepid
# Path : where this program is launched
path = /home/gameserver/khanat/server/
# size buffer log for each program launched (number line stdout)
logsize = 1000

View file

@ -24,6 +24,7 @@ sync
/opt/ext/servercontainer_configure_world.sh || exit 2
su -c '/opt/ext/servercontainer_configure_khanat.sh' gameserver || exit 2
su -c '/opt/ext/servercontainer_configure_patch.sh' gameserver || exit 2
su -c '/opt/ext/servercontainer_configure_launcher.sh' gameserver || exit 2
su -c 'touch /home/gameserver/khanat/step_configure.ok' gameserver || exit 2
sync
exit 0

View file

@ -0,0 +1,88 @@
#!/bin/bash
#
# Configure Launcher
#
# Copyright (C) 2017 AleaJactaEst
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
usage()
{
cat << EOF
usage:$0 [options]
Configure Launcher (certificate)
options:
-h, --help : Show this help
-d, --debug : Show debug message
EOF
}
#####################
# MAIN
#####################
source /opt/ext/servercontainer_function.sh
msg_info "$(basename $0) => START"
while test $# -gt 0
do
case "$1" in
-h|--help)
usage
exit 1
;;
-d|--debug)
set_debug 1
shift
;;
*)
msg_error "options '$1' not recoginze"
usage
exit 1
;;
esac
done
####################################
# Load Environment
####################################
msg_debug "Load environment"
if [[ ! -f /opt/khanat_config.sh ]]
then
echo "ERROR - missing /opt/khanat_config.sh"
exit 2
fi
source /opt/khanat_config.sh
if [[ ! -f /home/gameserver/.bashrc ]]
then
echo "ERROR - missing /home/gameserver/.bashrc"
exit 2
fi
source /home/gameserver/.bashrc
####################################
# Create new certificat
####################################
openssl req -nodes -x509 -newkey rsa:2048 \
-keyout "$KHANAT_PATH/key.pem" \
-out "$KHANAT_PATH/cert.pem" \
-days 365 \
-subj "/C=FR/ST=France/L=Paris/O=khaganat/CN=khaganat.org" || exit 2
####################################
# End
####################################
msg_info "$(basename $0) => END"

View file

@ -178,6 +178,11 @@ create_dir_gameserver "$KHANAT_PATH/tools/scripts/linux" || exit 2
create_recursive_link '/home/gameserver/ext/ryzom-ressources/tools/scripts/linux' "$KHANAT_PATH/tools/scripts/linux" || exit 2
####################################
# Link tools khaganat
####################################
create_link '/opt/ext/khaganat.cfg' "$KHANAT_PATH"
####################################
# End
####################################

View file

@ -32,6 +32,7 @@ options:
--show-status-nagios : show status (ater start)
--show-status : show status (ater start)
--bash-after-start : command bash after start
--start-with-manager : start khanat with manager
EOF
}
@ -191,6 +192,10 @@ do
METHOD_START=5
shift
;;
--start-with-manager)
METHOD_START=6
shift
;;
*)
msg_error "options '$1' not recoginze"
usage
@ -273,39 +278,38 @@ WHERE domain_id = 12;" || exit 2
msg_debug "Start khanat"
if [[ $METHOD_START -eq 0 ]]
then
#create_default_file_for_screen
source /home/gameserver/.bashrc; export RYZOM_PATH=$KHANAT_PATH; cd "$RYZOM_PATH"; $KHANAT_HOME/khanat/tools/scripts/linux/shard start
#sudo -u gameserver 'source /home/gameserver/.bashrc; export RYZOM_PATH=$KHANAT_PATH; echo ".$RYZOM_PATH."; $KHANAT_HOME/khanat/tools/scripts/linux/shard start'
elif [[ $METHOD_START -eq 1 ]]
then
#su -c /opt/ext/servercontainer_launch_service.sh gameserver
/opt/ext/servercontainer_launch_service.sh
sleep 10
tail -n+0 -f /home/gameserver/log/khanat/log.log
elif [[ $METHOD_START -eq 2 ]]
then
#su -c /opt/ext/servercontainer_launch_service.sh gameserver
/opt/ext/servercontainer_launch_service.sh
sleep 10
watch cat /home/gameserver/khanat/server/aes_nagios_report.txt
elif [[ $METHOD_START -eq 3 ]]
then
#su -c /opt/ext/servercontainer_launch_service.sh gameserver
/opt/ext/servercontainer_launch_service.sh
sleep 10
tail -n+0 -f /home/gameserver/log/apache2/* /home/gameserver/log/mysql/* /home/gameserver/log/khanat/*
elif [[ $METHOD_START -eq 4 ]]
then
#su -c /opt/ext/servercontainer_launch_service.sh gameserver
/opt/ext/servercontainer_launch_service.sh
sleep 10
watch /opt/ext/servercontainer_launch_status.sh --no-color
elif [[ $METHOD_START -eq 5 ]]
then
#su -c /opt/ext/servercontainer_launch_service.sh gameserver
/opt/ext/servercontainer_launch_service.sh
#sleep 10
bash
elif [[ $METHOD_START -eq 6 ]]
then
mkdir -p /home/gameserver/log/khanat
mkdir -p /home/gameserver/khanat/server/gpms
/home/gameserver/ext/khaganat/tools/manage.py --log info --show-log-console --filelog /home/gameserver/log/khanat/manager.log -c /home/gameserver/khanat/khaganat.cfg --launch-program
bash
else
msg_error 'Bad option'
exit 2

View file

@ -85,6 +85,7 @@ options:
--start-khanat-with-watch-state : start server khanat and show state (loop)
--start-khanat-with-watch-state-nagios : start server khanat and show state [nagios format] (loop)
--start-khanat-with-bash-after : start server khanat and launch bash
--start-with-manager : start khanat with manager
--ssh : connect on khanat server (with ssh) [Exclusive action, can't execute other action]
--client-version=[INTEGER] : version client khanat (we need to communicate with our server)
@ -198,6 +199,10 @@ do
METHODSTARTSERVER="--bash-after-start"
shift
;;
--start-with-manager)
METHODSTARTSERVER="--start-with-manager"
shift
;;
--client-version*)
KHANAT_CLIENT_VERSION="${1#*=}"
shift
@ -438,6 +443,7 @@ then
-v ${rootdir}/${LOCALBUILDDIR}/bin:/usr/local/bin:ro \
-v ${rootdir}/code/web/:/home/gameserver/ext/khanatweb:ro \
-v ${rootdir}/code/ryzom:/home/gameserver/ext/ryzom-ressources:ro \
-v ${rootdir}/code/khaganat:/home/gameserver/ext/khaganat:ro \
-v ${KHANAT_RESSOURCES_DIR}:/home/gameserver/ext/khanat-ressources:ro \
-v ${KHANAT_DATA_CLIENT_DIR}:/home/gameserver/ext/khanat-data-client:ro \
-v ${rootdir}/$DIRLOG:/home/gameserver/log:rw \
@ -455,6 +461,7 @@ then
-v ${rootdir}/${LOCALBUILDDIR}/bin:/usr/local/bin:ro \
-v ${rootdir}/code/web/:/home/gameserver/ext/khanatweb:ro \
-v ${rootdir}/code/ryzom:/home/gameserver/ext/ryzom-ressources:ro \
-v ${rootdir}/code/khaganat:/home/gameserver/ext/khaganat:ro \
-v ${KHANAT_RESSOURCES_DIR}:/home/gameserver/ext/khanat-ressources:ro \
-v ${KHANAT_DATA_CLIENT_DIR}:/home/gameserver/ext/khanat-data-client:ro \
-v ${rootdir}/$DIRLOG:/home/gameserver/log:rw \
@ -473,6 +480,7 @@ then
-v ${rootdir}/${LOCALBUILDDIR}/bin:/usr/local/bin:ro \
-v ${rootdir}/code/web/:/home/gameserver/ext/khanatweb:ro \
-v ${rootdir}/code/ryzom:/home/gameserver/ext/ryzom-ressources:ro \
-v ${rootdir}/code/khaganat:/home/gameserver/ext/khaganat:ro \
-v ${KHANAT_RESSOURCES_DIR}:/home/gameserver/ext/khanat-ressources:ro \
-v ${KHANAT_DATA_CLIENT_DIR}:/home/gameserver/ext/khanat-data-client:ro \
-v ${rootdir}/$DIRLOG:/home/gameserver/log:rw \