adding http (issue khaganat/mmorpg_khanat/opennel-pymanager#3)
This commit is contained in:
parent
336b77db09
commit
a9d4bbffcd
7 changed files with 149 additions and 23 deletions
|
@ -146,6 +146,17 @@ def cmp_to_key():
|
||||||
return K
|
return K
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPConnection(http.client.HTTPConnection):
|
||||||
|
"""
|
||||||
|
Class HTTPConnection
|
||||||
|
"""
|
||||||
|
def __init__(self,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
timeout=10):
|
||||||
|
http.client.HTTPConnection.__init__(self, host, port, timeout)
|
||||||
|
|
||||||
|
|
||||||
class HTTPSConnectionCertificate(http.client.HTTPConnection):
|
class HTTPSConnectionCertificate(http.client.HTTPConnection):
|
||||||
""" Class HTTP connection with check certificate (if certicate is defined) """
|
""" Class HTTP connection with check certificate (if certicate is defined) """
|
||||||
def __init__(self, key_file, cert_file, ca_cert, host='localhost', port=8000, timeout=10):
|
def __init__(self, key_file, cert_file, ca_cert, host='localhost', port=8000, timeout=10):
|
||||||
|
@ -194,6 +205,7 @@ class Client():
|
||||||
self.certfile = None
|
self.certfile = None
|
||||||
self.ca_cert = None
|
self.ca_cert = None
|
||||||
self.timeout = 10
|
self.timeout = 10
|
||||||
|
self.method = 'http'
|
||||||
if username:
|
if username:
|
||||||
self.username = username
|
self.username = username
|
||||||
if not password_comand_line:
|
if not password_comand_line:
|
||||||
|
@ -249,6 +261,10 @@ class Client():
|
||||||
self.timeout = config[name]['timeout']
|
self.timeout = config[name]['timeout']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
try:
|
||||||
|
self.method = config[name]['method']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
def send_json(self,
|
def send_json(self,
|
||||||
jsonin={},
|
jsonin={},
|
||||||
|
@ -265,12 +281,20 @@ class Client():
|
||||||
:param bool raw_data: show result without analyze
|
:param bool raw_data: show result without analyze
|
||||||
:param bool remove_color: at end string send color to disable background & color
|
:param bool remove_color: at end string send color to disable background & color
|
||||||
"""
|
"""
|
||||||
conn = HTTPSConnectionCertificate(host=self.address,
|
if self.method == 'http':
|
||||||
port=self.port,
|
conn = HTTPConnection(host=self.address,
|
||||||
key_file=self.keyfile,
|
port=self.port,
|
||||||
cert_file=self.certfile,
|
timeout=self.timeout)
|
||||||
ca_cert=self.ca_cert,
|
elif self.method == 'https':
|
||||||
timeout=self.timeout)
|
conn = HTTPSConnectionCertificate(host=self.address,
|
||||||
|
port=self.port,
|
||||||
|
key_file=self.keyfile,
|
||||||
|
cert_file=self.certfile,
|
||||||
|
ca_cert=self.ca_cert,
|
||||||
|
timeout=self.timeout)
|
||||||
|
else:
|
||||||
|
logging.error("Bad value 'method' (%s)" % str(self.method))
|
||||||
|
raise ValueError
|
||||||
conn.putrequest(command, path)
|
conn.putrequest(command, path)
|
||||||
out = json.dumps(jsonin)
|
out = json.dumps(jsonin)
|
||||||
if self.username:
|
if self.username:
|
||||||
|
|
|
@ -42,6 +42,9 @@ This script need configuration file (see below for model)::
|
||||||
# address listen (default all port)
|
# address listen (default all port)
|
||||||
address =
|
address =
|
||||||
|
|
||||||
|
# method : http or https
|
||||||
|
method = https
|
||||||
|
|
||||||
|
|
||||||
# Admin Executor Service
|
# Admin Executor Service
|
||||||
[command:aes]
|
[command:aes]
|
||||||
|
@ -139,7 +142,12 @@ import json
|
||||||
import fcntl
|
import fcntl
|
||||||
import os
|
import os
|
||||||
import base64
|
import base64
|
||||||
import bcrypt
|
|
||||||
|
try:
|
||||||
|
import bcrypt
|
||||||
|
__DISABLE_BCRYPT__ = False
|
||||||
|
except ImportError:
|
||||||
|
__DISABLE_BCRYPT__ = True
|
||||||
|
|
||||||
__VERSION__ = '1.0'
|
__VERSION__ = '1.0'
|
||||||
|
|
||||||
|
@ -346,6 +354,9 @@ class ManageHttpRequest(http.server.SimpleHTTPRequestHandler):
|
||||||
def check_authentication(self):
|
def check_authentication(self):
|
||||||
if not self.server.authentification:
|
if not self.server.authentification:
|
||||||
return True
|
return True
|
||||||
|
if __DISABLE_BCRYPT__:
|
||||||
|
logging.error("Error module python bcrypt not installed")
|
||||||
|
return False
|
||||||
try:
|
try:
|
||||||
auth_header = self.headers['Authorization'].split()
|
auth_header = self.headers['Authorization'].split()
|
||||||
if auth_header[0] != 'Basic':
|
if auth_header[0] != 'Basic':
|
||||||
|
@ -460,7 +471,7 @@ class ServerHttp(multiprocessing.Process):
|
||||||
* define Dictionnary queueIn & queueOut (with key as section's name in configuration)
|
* define Dictionnary queueIn & queueOut (with key as section's name in configuration)
|
||||||
"""
|
"""
|
||||||
def __init__(self, keyfile, certfile, ca_cert, address='',
|
def __init__(self, keyfile, certfile, ca_cert, address='',
|
||||||
port=8000, authentification=True, users={}):
|
port=8000, authentification=True, method='http', users={}):
|
||||||
multiprocessing.Process.__init__(self)
|
multiprocessing.Process.__init__(self)
|
||||||
self.listQueueIn = {}
|
self.listQueueIn = {}
|
||||||
self.listQueueOut = {}
|
self.listQueueOut = {}
|
||||||
|
@ -472,6 +483,7 @@ class ServerHttp(multiprocessing.Process):
|
||||||
self.address = address
|
self.address = address
|
||||||
self.authentification = authentification
|
self.authentification = authentification
|
||||||
self.users = users
|
self.users = users
|
||||||
|
self.method = method
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
server_address = (self.address, self.port)
|
server_address = (self.address, self.port)
|
||||||
|
@ -482,20 +494,26 @@ class ServerHttp(multiprocessing.Process):
|
||||||
ManageHttpRequest,
|
ManageHttpRequest,
|
||||||
self.authentification,
|
self.authentification,
|
||||||
self.users)
|
self.users)
|
||||||
if self.ca_cert:
|
if self.method == 'http':
|
||||||
httpd.socket = ssl.wrap_socket(httpd.socket,
|
logging.info('http listen')
|
||||||
keyfile=self.key_file,
|
elif self.method == 'https':
|
||||||
certfile=self.cert_file,
|
if self.ca_cert:
|
||||||
ca_certs=self.ca_cert,
|
httpd.socket = ssl.wrap_socket(httpd.socket,
|
||||||
cert_reqs=ssl.CERT_REQUIRED,
|
keyfile=self.key_file,
|
||||||
ssl_version=ssl.PROTOCOL_TLSv1_2,
|
certfile=self.cert_file,
|
||||||
server_side=True)
|
ca_certs=self.ca_cert,
|
||||||
|
cert_reqs=ssl.CERT_REQUIRED,
|
||||||
|
ssl_version=ssl.PROTOCOL_TLSv1_2,
|
||||||
|
server_side=True)
|
||||||
|
else:
|
||||||
|
httpd.socket = ssl.wrap_socket(httpd.socket,
|
||||||
|
keyfile=self.key_file,
|
||||||
|
certfile=self.cert_file,
|
||||||
|
server_side=True)
|
||||||
|
logging.info('https listen')
|
||||||
else:
|
else:
|
||||||
httpd.socket = ssl.wrap_socket(httpd.socket,
|
logging.error("Bad value 'method' (%s)" % str(self.method))
|
||||||
keyfile=self.key_file,
|
raise ValueError
|
||||||
certfile=self.cert_file,
|
|
||||||
server_side=True)
|
|
||||||
logging.info('https listen')
|
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
|
|
||||||
def append(self, name, queueIn, queueOut, event):
|
def append(self, name, queueIn, queueOut, event):
|
||||||
|
@ -823,6 +841,10 @@ class Manager():
|
||||||
self.passwordfile = config[name]['passwordfile']
|
self.passwordfile = config[name]['passwordfile']
|
||||||
except (TypeError, KeyError):
|
except (TypeError, KeyError):
|
||||||
self.passwordfile = None
|
self.passwordfile = None
|
||||||
|
try:
|
||||||
|
self.method = config[name]['method']
|
||||||
|
except (TypeError, KeyError):
|
||||||
|
self.method = 'http'
|
||||||
else:
|
else:
|
||||||
head, value = name.split(':', maxsplit=1)
|
head, value = name.split(':', maxsplit=1)
|
||||||
if head == 'command' and 'command' in config[name]:
|
if head == 'command' and 'command' in config[name]:
|
||||||
|
@ -862,6 +884,7 @@ class Manager():
|
||||||
self.address,
|
self.address,
|
||||||
self.port,
|
self.port,
|
||||||
authentification=self.authentification,
|
authentification=self.authentification,
|
||||||
|
method=self.method,
|
||||||
users=self.users)
|
users=self.users)
|
||||||
|
|
||||||
def runCommand(self, name, command, path, logsize, bufsize, queueIn, queueOut, event):
|
def runCommand(self, name, command, path, logsize, bufsize, queueIn, queueOut, event):
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
# Define port listen (default 8000)
|
# Define port listen (default 8000)
|
||||||
port = 8000
|
port = 8000
|
||||||
|
|
||||||
|
# Method : http or https
|
||||||
|
method = https
|
||||||
|
|
||||||
# key
|
# key
|
||||||
keyfile = /home/gameserver/ca/appli/private/serverkey.pem
|
keyfile = /home/gameserver/ca/appli/private/serverkey.pem
|
||||||
|
|
||||||
|
@ -41,6 +44,7 @@ passwordfile = /home/gameserver/passwordfile
|
||||||
|
|
||||||
[config:client]
|
[config:client]
|
||||||
port = 8000
|
port = 8000
|
||||||
|
method = https
|
||||||
keyfile = /home/gameserver/ca/appli/private/clientkey.pem
|
keyfile = /home/gameserver/ca/appli/private/clientkey.pem
|
||||||
certfile = /home/gameserver/ca/appli/certs/clientcert.pem
|
certfile = /home/gameserver/ca/appli/certs/clientcert.pem
|
||||||
ca_cert = /home/gameserver/ca/appli/certs/cachaincert.pem
|
ca_cert = /home/gameserver/ca/appli/certs/cachaincert.pem
|
||||||
|
|
|
@ -107,12 +107,14 @@ class TestClient(unittest.TestCase):
|
||||||
config.set('config:server', 'ca_cert', '/home/gameserver/ca/appli/certs/cachaincert.pem')
|
config.set('config:server', 'ca_cert', '/home/gameserver/ca/appli/certs/cachaincert.pem')
|
||||||
config.set('config:server', 'address', '')
|
config.set('config:server', 'address', '')
|
||||||
config.set('config:server', 'authentification', 'yes')
|
config.set('config:server', 'authentification', 'yes')
|
||||||
|
config.set('config:server', 'method', 'https')
|
||||||
config.add_section('config:client')
|
config.add_section('config:client')
|
||||||
config.set('config:client', 'port', '8000')
|
config.set('config:client', 'port', '8000')
|
||||||
config.set('config:client', 'keyfile', '/home/gameserver/ca/appli/private/clientkey.pem')
|
config.set('config:client', 'keyfile', '/home/gameserver/ca/appli/private/clientkey.pem')
|
||||||
config.set('config:client', 'certfile', '/home/gameserver/ca/appli/certs/clientcert.pem')
|
config.set('config:client', 'certfile', '/home/gameserver/ca/appli/certs/clientcert.pem')
|
||||||
config.set('config:client', 'ca_cert', '/home/gameserver/ca/appli/certs/cachaincert.pem')
|
config.set('config:client', 'ca_cert', '/home/gameserver/ca/appli/certs/cachaincert.pem')
|
||||||
config.set('config:client', 'address', '127.0.0.1')
|
config.set('config:client', 'address', '127.0.0.1')
|
||||||
|
config.set('config:client', 'method', 'https')
|
||||||
config.add_section('command:test')
|
config.add_section('command:test')
|
||||||
config.set('command:test', 'path', '/home/gameserver')
|
config.set('command:test', 'path', '/home/gameserver')
|
||||||
config.set('command:test', 'command', '/bin/sleep 10')
|
config.set('command:test', 'command', '/bin/sleep 10')
|
||||||
|
@ -199,12 +201,14 @@ class TestClient(unittest.TestCase):
|
||||||
config.set('config:server', 'keyfile', os.path.join(workdir_cert_appli, 'private', 'serverkey.pem'))
|
config.set('config:server', 'keyfile', os.path.join(workdir_cert_appli, 'private', 'serverkey.pem'))
|
||||||
config.set('config:server', 'certfile', os.path.join(workdir_cert_appli, 'certs', 'servercert.pem'))
|
config.set('config:server', 'certfile', os.path.join(workdir_cert_appli, 'certs', 'servercert.pem'))
|
||||||
config.set('config:server', 'ca_cert', os.path.join(workdir_cert_appli, 'certs', 'cachaincert.pem'))
|
config.set('config:server', 'ca_cert', os.path.join(workdir_cert_appli, 'certs', 'cachaincert.pem'))
|
||||||
|
config.set('config:server', 'method', 'https')
|
||||||
config.add_section('config:client')
|
config.add_section('config:client')
|
||||||
config.set('config:client', 'port', '8000')
|
config.set('config:client', 'port', '8000')
|
||||||
config.set('config:client', 'keyfile', os.path.join(workdir_cert_appli, 'private', 'clientkey.pem'))
|
config.set('config:client', 'keyfile', os.path.join(workdir_cert_appli, 'private', 'clientkey.pem'))
|
||||||
config.set('config:client', 'certfile', os.path.join(workdir_cert_appli, 'certs', 'clientcert.pem'))
|
config.set('config:client', 'certfile', os.path.join(workdir_cert_appli, 'certs', 'clientcert.pem'))
|
||||||
config.set('config:client', 'ca_cert', os.path.join(workdir_cert_appli, 'certs', 'cachaincert.pem'))
|
config.set('config:client', 'ca_cert', os.path.join(workdir_cert_appli, 'certs', 'cachaincert.pem'))
|
||||||
config.set('config:client', 'address', '127.0.0.1')
|
config.set('config:client', 'address', '127.0.0.1')
|
||||||
|
config.set('config:client', 'method', 'https')
|
||||||
config.add_section('config:user')
|
config.add_section('config:user')
|
||||||
config.set('config:user', 'usename', 'filter_all, filter_admin')
|
config.set('config:user', 'usename', 'filter_all, filter_admin')
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -70,12 +70,14 @@ class TestManager(unittest.TestCase):
|
||||||
config.set('config:server', 'keyfile', os.path.join(workdir_cert_appli, 'private', 'serverkey.pem'))
|
config.set('config:server', 'keyfile', os.path.join(workdir_cert_appli, 'private', 'serverkey.pem'))
|
||||||
config.set('config:server', 'certfile', os.path.join(workdir_cert_appli, 'certs', 'servercert.pem'))
|
config.set('config:server', 'certfile', os.path.join(workdir_cert_appli, 'certs', 'servercert.pem'))
|
||||||
config.set('config:server', 'ca_cert', os.path.join(workdir_cert_appli, 'certs', 'cachaincert.pem'))
|
config.set('config:server', 'ca_cert', os.path.join(workdir_cert_appli, 'certs', 'cachaincert.pem'))
|
||||||
|
config.set('config:server', 'method', 'https')
|
||||||
config.add_section('config:client')
|
config.add_section('config:client')
|
||||||
config.set('config:server', 'port', str(port))
|
config.set('config:server', 'port', str(port))
|
||||||
config.set('config:client', 'keyfile', os.path.join(workdir_cert_appli, 'private', 'clientkey.pem'))
|
config.set('config:client', 'keyfile', os.path.join(workdir_cert_appli, 'private', 'clientkey.pem'))
|
||||||
config.set('config:client', 'certfile', os.path.join(workdir_cert_appli, 'certs', 'clientcert.pem'))
|
config.set('config:client', 'certfile', os.path.join(workdir_cert_appli, 'certs', 'clientcert.pem'))
|
||||||
config.set('config:client', 'ca_cert', os.path.join(workdir_cert_appli, 'certs', 'cachaincert.pem'))
|
config.set('config:client', 'ca_cert', os.path.join(workdir_cert_appli, 'certs', 'cachaincert.pem'))
|
||||||
config.set('config:client', 'address', '127.0.0.1')
|
config.set('config:client', 'address', '127.0.0.1')
|
||||||
|
config.set('config:client', 'method', 'https')
|
||||||
config.add_section('command:test')
|
config.add_section('command:test')
|
||||||
config.set('command:test', 'path', workdir)
|
config.set('command:test', 'path', workdir)
|
||||||
config.set('command:test', 'command', self.program)
|
config.set('command:test', 'command', self.program)
|
||||||
|
|
63
tests/test_http.cfg
Normal file
63
tests/test_http.cfg
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
#
|
||||||
|
# Configuration management program 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 : use bu ymanager
|
||||||
|
#
|
||||||
|
[config:server]
|
||||||
|
# Define port listen (default 8000)
|
||||||
|
port = 8000
|
||||||
|
method = http
|
||||||
|
|
||||||
|
# address listen (default all network)
|
||||||
|
address =
|
||||||
|
|
||||||
|
# activate authentification (yes or no)
|
||||||
|
authentification = no
|
||||||
|
|
||||||
|
[config:client]
|
||||||
|
# you can use curl as client
|
||||||
|
# like: curl -XGET http://127.0.0.1:8000/STATUSALL
|
||||||
|
# curl -XPOST http://127.0.0.1:8000/STARTALL
|
||||||
|
port = 8000
|
||||||
|
method = http
|
||||||
|
address = 127.0.0.1
|
||||||
|
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# List all program we manage #
|
||||||
|
##############################
|
||||||
|
|
||||||
|
[command:test1]
|
||||||
|
# command to launch the program
|
||||||
|
command = /home/gameserver/test1
|
||||||
|
|
||||||
|
# Admin Executor Service
|
||||||
|
[command:test2]
|
||||||
|
# command to launch the program
|
||||||
|
command = /home/gameserver/test2
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Admin Executor Service
|
||||||
|
[command:sleep]
|
||||||
|
# command to launch the program
|
||||||
|
command = sleep 10
|
||||||
|
|
|
@ -79,12 +79,14 @@ class TestManager(unittest.TestCase):
|
||||||
config.set('config:server', 'ca_cert', '/home/gameserver/ca/appli/certs/cachaincert.pem')
|
config.set('config:server', 'ca_cert', '/home/gameserver/ca/appli/certs/cachaincert.pem')
|
||||||
config.set('config:server', 'address', '')
|
config.set('config:server', 'address', '')
|
||||||
config.set('config:server', 'authentification', 'yes')
|
config.set('config:server', 'authentification', 'yes')
|
||||||
|
config.set('config:server', 'method', 'https')
|
||||||
config.add_section('config:client')
|
config.add_section('config:client')
|
||||||
config.set('config:client', 'port', '8000')
|
config.set('config:client', 'port', '8000')
|
||||||
config.set('config:client', 'keyfile', '/home/gameserver/ca/appli/private/clientkey.pem')
|
config.set('config:client', 'keyfile', '/home/gameserver/ca/appli/private/clientkey.pem')
|
||||||
config.set('config:client', 'certfile', '/home/gameserver/ca/appli/certs/clientcert.pem')
|
config.set('config:client', 'certfile', '/home/gameserver/ca/appli/certs/clientcert.pem')
|
||||||
config.set('config:client', 'ca_cert', '/home/gameserver/ca/appli/certs/cachaincert.pem')
|
config.set('config:client', 'ca_cert', '/home/gameserver/ca/appli/certs/cachaincert.pem')
|
||||||
config.set('config:client', 'address', '127.0.0.1')
|
config.set('config:client', 'address', '127.0.0.1')
|
||||||
|
config.set('config:client', 'method', 'https')
|
||||||
config.add_section('command:test')
|
config.add_section('command:test')
|
||||||
config.set('command:test', 'path', '/home/gameserver')
|
config.set('command:test', 'path', '/home/gameserver')
|
||||||
config.set('command:test', 'command', '/bin/sleep 10')
|
config.set('command:test', 'command', '/bin/sleep 10')
|
||||||
|
@ -142,6 +144,7 @@ class TestManager(unittest.TestCase):
|
||||||
config.set('config:client', 'certfile', '/home/gameserver/ca/appli/certs/clientcert.pem')
|
config.set('config:client', 'certfile', '/home/gameserver/ca/appli/certs/clientcert.pem')
|
||||||
config.set('config:client', 'ca_cert', '/home/gameserver/ca/appli/certs/cachaincert.pem')
|
config.set('config:client', 'ca_cert', '/home/gameserver/ca/appli/certs/cachaincert.pem')
|
||||||
config.set('config:client', 'address', '127.0.0.1')
|
config.set('config:client', 'address', '127.0.0.1')
|
||||||
|
config.set('config:client', 'method', 'https')
|
||||||
config.add_section('config:user')
|
config.add_section('config:user')
|
||||||
config.add_section('command:test')
|
config.add_section('command:test')
|
||||||
config.set('command:test', 'command', '/bin/sleep 10')
|
config.set('command:test', 'command', '/bin/sleep 10')
|
||||||
|
@ -312,6 +315,9 @@ class TestManager(unittest.TestCase):
|
||||||
queueOut,
|
queueOut,
|
||||||
event,
|
event,
|
||||||
maxWaitEnd = 2)
|
maxWaitEnd = 2)
|
||||||
|
except:
|
||||||
|
self.fail('Error initialize object ManageCommand')
|
||||||
|
try:
|
||||||
res = manageCommand.start()
|
res = manageCommand.start()
|
||||||
self.assertEqual(res, 'started')
|
self.assertEqual(res, 'started')
|
||||||
wait = True
|
wait = True
|
||||||
|
@ -332,8 +338,8 @@ class TestManager(unittest.TestCase):
|
||||||
res = manageCommand.status()
|
res = manageCommand.status()
|
||||||
self.assertEqual(res, 'stopped')
|
self.assertEqual(res, 'stopped')
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
except:
|
except Exception as e:
|
||||||
self.fail('Error initialize object ManageCommand')
|
self.fail('Error when run test', e)
|
||||||
|
|
||||||
def test_execute_command_crashed(self):
|
def test_execute_command_crashed(self):
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in a new issue