Tests: isolation check moved to the pytest_sessionstart().

This change eliminates the need for some classes
to run Unit one more time before running tests.
This commit is contained in:
Andrei Zeliankou
2020-12-09 16:15:50 +00:00
parent 783cdc2a3d
commit 4c846ae693
10 changed files with 197 additions and 259 deletions

View File

@@ -0,0 +1,158 @@
import json
import os
from unit.applications.lang.go import TestApplicationGo
from unit.applications.lang.java import TestApplicationJava
from unit.applications.lang.node import TestApplicationNode
from unit.applications.proto import TestApplicationProto
from unit.http import TestHTTP
from unit.option import option
from unit.utils import getns
allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net']
http = TestHTTP()
def check_isolation():
test_conf = {"namespaces": {"credential": True}}
available = option.available
conf = ''
if 'go' in available['modules']:
TestApplicationGo().prepare_env('empty', 'app')
conf = {
"listeners": {"*:7080": {"pass": "applications/empty"}},
"applications": {
"empty": {
"type": "external",
"processes": {"spare": 0},
"working_directory": option.test_dir + "/go/empty",
"executable": option.temp_dir + "/go/app",
"isolation": {"namespaces": {"credential": True}},
},
},
}
elif 'python' in available['modules']:
conf = {
"listeners": {"*:7080": {"pass": "applications/empty"}},
"applications": {
"empty": {
"type": "python",
"processes": {"spare": 0},
"path": option.test_dir + "/python/empty",
"working_directory": option.test_dir + "/python/empty",
"module": "wsgi",
"isolation": {"namespaces": {"credential": True}},
}
},
}
elif 'php' in available['modules']:
conf = {
"listeners": {"*:7080": {"pass": "applications/phpinfo"}},
"applications": {
"phpinfo": {
"type": "php",
"processes": {"spare": 0},
"root": option.test_dir + "/php/phpinfo",
"working_directory": option.test_dir + "/php/phpinfo",
"index": "index.php",
"isolation": {"namespaces": {"credential": True}},
}
},
}
elif 'ruby' in available['modules']:
conf = {
"listeners": {"*:7080": {"pass": "applications/empty"}},
"applications": {
"empty": {
"type": "ruby",
"processes": {"spare": 0},
"working_directory": option.test_dir + "/ruby/empty",
"script": option.test_dir + "/ruby/empty/config.ru",
"isolation": {"namespaces": {"credential": True}},
}
},
}
elif 'java' in available['modules']:
TestApplicationJava().prepare_env('empty')
conf = {
"listeners": {"*:7080": {"pass": "applications/empty"}},
"applications": {
"empty": {
"unit_jars": option.current_dir + "/build",
"type": "java",
"processes": {"spare": 0},
"working_directory": option.test_dir + "/java/empty/",
"webapp": option.temp_dir + "/java",
"isolation": {"namespaces": {"credential": True}},
}
},
}
elif 'node' in available['modules']:
TestApplicationNode().prepare_env('basic')
conf = {
"listeners": {"*:7080": {"pass": "applications/basic"}},
"applications": {
"basic": {
"type": "external",
"processes": {"spare": 0},
"working_directory": option.temp_dir + "/node",
"executable": "app.js",
"isolation": {"namespaces": {"credential": True}},
}
},
}
elif 'perl' in available['modules']:
conf = {
"listeners": {"*:7080": {"pass": "applications/body_empty"}},
"applications": {
"body_empty": {
"type": "perl",
"processes": {"spare": 0},
"working_directory": option.test_dir
+ "/perl/body_empty",
"script": option.test_dir + "/perl/body_empty/psgi.pl",
"isolation": {"namespaces": {"credential": True}},
}
},
}
else:
return
resp = http.put(
url='/config',
sock_type='unix',
addr=option.temp_dir + '/control.unit.sock',
body=json.dumps(conf),
)
if 'success' not in resp:
return
userns = getns('user')
if not userns:
return
available['features']['isolation'] = {'user': userns}
unp_clone_path = '/proc/sys/kernel/unprivileged_userns_clone'
if os.path.exists(unp_clone_path):
with open(unp_clone_path, 'r') as f:
if str(f.read()).rstrip() == '1':
available['features']['isolation'][
'unprivileged_userns_clone'
] = True
for ns in allns:
ns_value = getns(ns)
if ns_value:
available['features']['isolation'][ns] = ns_value

View File

@@ -1,160 +0,0 @@
import os
from unit.applications.lang.go import TestApplicationGo
from unit.applications.lang.java import TestApplicationJava
from unit.applications.lang.node import TestApplicationNode
from unit.applications.proto import TestApplicationProto
from unit.option import option
class TestFeatureIsolation(TestApplicationProto):
allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net']
def check(self, available, temp_dir):
test_conf = {"namespaces": {"credential": True}}
conf = ''
if 'go' in available['modules']:
TestApplicationGo().prepare_env('empty', 'app')
conf = {
"listeners": {"*:7080": {"pass": "applications/empty"}},
"applications": {
"empty": {
"type": "external",
"processes": {"spare": 0},
"working_directory": option.test_dir + "/go/empty",
"executable": option.temp_dir + "/go/app",
"isolation": {"namespaces": {"credential": True}},
},
},
}
elif 'python' in available['modules']:
conf = {
"listeners": {"*:7080": {"pass": "applications/empty"}},
"applications": {
"empty": {
"type": "python",
"processes": {"spare": 0},
"path": option.test_dir + "/python/empty",
"working_directory": option.test_dir + "/python/empty",
"module": "wsgi",
"isolation": {"namespaces": {"credential": True}},
}
},
}
elif 'php' in available['modules']:
conf = {
"listeners": {"*:7080": {"pass": "applications/phpinfo"}},
"applications": {
"phpinfo": {
"type": "php",
"processes": {"spare": 0},
"root": option.test_dir + "/php/phpinfo",
"working_directory": option.test_dir + "/php/phpinfo",
"index": "index.php",
"isolation": {"namespaces": {"credential": True}},
}
},
}
elif 'ruby' in available['modules']:
conf = {
"listeners": {"*:7080": {"pass": "applications/empty"}},
"applications": {
"empty": {
"type": "ruby",
"processes": {"spare": 0},
"working_directory": option.test_dir + "/ruby/empty",
"script": option.test_dir + "/ruby/empty/config.ru",
"isolation": {"namespaces": {"credential": True}},
}
},
}
elif 'java' in available['modules']:
TestApplicationJava().prepare_env('empty')
conf = {
"listeners": {"*:7080": {"pass": "applications/empty"}},
"applications": {
"empty": {
"unit_jars": option.current_dir + "/build",
"type": "java",
"processes": {"spare": 0},
"working_directory": option.test_dir + "/java/empty/",
"webapp": option.temp_dir + "/java",
"isolation": {"namespaces": {"credential": True}},
}
},
}
elif 'node' in available['modules']:
TestApplicationNode().prepare_env('basic')
conf = {
"listeners": {"*:7080": {"pass": "applications/basic"}},
"applications": {
"basic": {
"type": "external",
"processes": {"spare": 0},
"working_directory": option.temp_dir + "/node",
"executable": "app.js",
"isolation": {"namespaces": {"credential": True}},
}
},
}
elif 'perl' in available['modules']:
conf = {
"listeners": {"*:7080": {"pass": "applications/body_empty"}},
"applications": {
"body_empty": {
"type": "perl",
"processes": {"spare": 0},
"working_directory": option.test_dir
+ "/perl/body_empty",
"script": option.test_dir + "/perl/body_empty/psgi.pl",
"isolation": {"namespaces": {"credential": True}},
}
},
}
else:
return
if 'success' not in self.conf(conf):
return
userns = self.getns('user')
if not userns:
return
available['features']['isolation'] = {'user': userns}
unp_clone_path = '/proc/sys/kernel/unprivileged_userns_clone'
if os.path.exists(unp_clone_path):
with open(unp_clone_path, 'r') as f:
if str(f.read()).rstrip() == '1':
available['features']['isolation'][
'unprivileged_userns_clone'
] = True
for ns in self.allns:
ns_value = self.getns(ns)
if ns_value:
available['features']['isolation'][ns] = ns_value
def getns(self, nstype):
# read namespace id from symlink file:
# it points to: '<nstype>:[<ns id>]'
# # eg.: 'pid:[4026531836]'
nspath = '/proc/self/ns/' + nstype
data = None
if os.path.exists(nspath):
data = int(os.readlink(nspath)[len(nstype) + 2 : -1])
return data

View File

@@ -4,39 +4,33 @@ from unit.option import option
class TestUnit():
@classmethod
def setup_class(cls, complete_check=True):
def check():
missed = []
def setup_class(cls):
missed = []
# check modules
# check modules
if 'modules' in cls.prerequisites:
available_modules = list(option.available['modules'].keys())
if 'modules' in cls.prerequisites:
available_modules = list(option.available['modules'].keys())
for module in cls.prerequisites['modules']:
if module in available_modules:
continue
for module in cls.prerequisites['modules']:
if module in available_modules:
continue
missed.append(module)
missed.append(module)
if missed:
pytest.skip('Unit has no ' + ', '.join(missed) + ' module(s)')
if missed:
pytest.skip('Unit has no ' + ', '.join(missed) + ' module(s)')
# check features
# check features
if 'features' in cls.prerequisites:
available_features = list(option.available['features'].keys())
if 'features' in cls.prerequisites:
available_features = list(option.available['features'].keys())
for feature in cls.prerequisites['features']:
if feature in available_features:
continue
for feature in cls.prerequisites['features']:
if feature in available_features:
continue
missed.append(feature)
missed.append(feature)
if missed:
pytest.skip(', '.join(missed) + ' feature(s) not supported')
if complete_check:
check()
else:
return check
if missed:
pytest.skip(', '.join(missed) + ' feature(s) not supported')

View File

@@ -48,3 +48,15 @@ def waitforsocket(port):
pytest.fail('Can\'t connect to the 127.0.0.1:' + port)
def getns(nstype):
# read namespace id from symlink file:
# it points to: '<nstype>:[<ns id>]'
# # eg.: 'pid:[4026531836]'
nspath = '/proc/self/ns/' + nstype
data = None
if os.path.exists(nspath):
data = int(os.readlink(nspath)[len(nstype) + 2 : -1])
return data