Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
slapos.core
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Carlos Ramos Carreño
slapos.core
Commits
198c2d6a
Commit
198c2d6a
authored
Sep 06, 2024
by
Carlos Ramos Carreño
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Accept `pathlib.Path` objects as a software URL.
parent
f4ceaaa3
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
271 additions
and
193 deletions
+271
-193
slapos/testing/check_software.py
slapos/testing/check_software.py
+239
-171
slapos/testing/testcase.py
slapos/testing/testcase.py
+32
-22
No files found.
slapos/testing/check_software.py
View file @
198c2d6a
...
@@ -26,141 +26,177 @@
...
@@ -26,141 +26,177 @@
#
#
##############################################################################
##############################################################################
from
__future__
import
annotations
import
fnmatch
import
fnmatch
import
glob
import
glob
import
os
import
os
import
re
import
re
import
sys
import
warnings
import
warnings
import
pkg_resources
import
pkg_resources
import
requests
import
requests
from
six.moves.configparser
import
ConfigParser
from
configparser
import
ConfigParser
import
six
try
:
import
subprocess
import
subprocess32
as
subprocess
except
ImportError
:
import
subprocess
# type: ignore
subprocess
# pyflakes
try
:
from
typing
import
Collection
,
Dict
,
Iterable
,
List
,
Mapping
,
Sequence
,
TYPE_CHECKING
from
typing
import
Dict
,
Iterable
,
List
except
ImportError
:
pass
from
..slap.standalone
import
StandaloneSlapOS
from
..slap.standalone
import
StandaloneSlapOS
from
..grid.utils
import
md5digest
from
..grid.utils
import
md5digest
if
TYPE_CHECKING
:
if
sys
.
version_info
>=
(
3
,
8
):
from
typing
import
TypedDict
else
:
from
typing_extensions
import
TypedDict
class
SafetyDBEntry
(
TypedDict
):
advisory
:
str
cve
:
str
id
:
str
more_info_path
:
str
specs
:
Sequence
[
str
]
v
:
str
def
checkSoftware
(
slap
,
software_url
):
# type: (StandaloneSlapOS, str) -> None
def
checkSoftware
(
"""Check software installation.
slap
:
StandaloneSlapOS
,
software_url
:
str
|
os
.
PathLike
,
)
->
None
:
"""
Check software installation.
This perform a few basic static checks for common problems
This perform a few basic static checks for common problems
with software installations.
with software installations.
Args:
slap: The SlapOS system.
software_url: URL or path to the software to install.
"""
"""
# Check that all components set rpath correctly and we don't have miss linking any libraries.
# Check that all components set rpath correctly and we don't have miss
# Also check that they are not linked against system libraries, except a list of core
# linking any libraries.
# system libraries.
# Also check that they are not linked against system libraries, except a
system_lib_allowed_list
=
set
((
# list of core system libraries.
'libanl'
,
system_lib_allowed_list
=
{
'libc'
,
"libanl"
,
'libcrypt'
,
"libc"
,
'libdl'
,
"libcrypt"
,
'libgcc_s'
,
"libdl"
,
'libgomp'
,
"libgcc_s"
,
'libm'
,
"libgomp"
,
'libmvec'
,
"libm"
,
'libnsl'
,
"libmvec"
,
'libpthread'
,
"libnsl"
,
'libthread_db'
,
"libpthread"
,
'libresolv'
,
"libthread_db"
,
'librt'
,
"libresolv"
,
'libstdc++'
,
"librt"
,
'libquadmath'
,
"libstdc++"
,
'libutil'
,
"libquadmath"
,
))
"libutil"
,
}
# Some libraries might also not be found statically but provided at run time.
# Some libraries might also not be found statically but provided at run time.
missing_lib_allowed_list
=
set
((
missing_lib_allowed_list
=
{
# references to liblibgolang.so from projects outside of pygolang are resolved at runtime for now.
# references to liblibgolang.so from projects outside of pygolang are
# https://github.com/mdavidsaver/setuptools_dso/issues/11#issuecomment-808258994
# resolved at runtime for now.
'liblibgolang'
,
# https://github.com/mdavidsaver/setuptools_dso/issues/11#issuecomment-808258994
))
"liblibgolang"
,
}
# we also ignore a few patterns for part that are known to be binary distributions,
# for which we generate LD_LIBRARY_PATH wrappers or we don't use directly.
# we also ignore a few patterns for part that are known to be binary
ignored_file_patterns
=
set
((
# distributions, for which we generate LD_LIBRARY_PATH wrappers or we don't
'*/parts/java-re*/*'
,
# use directly.
'*/parts/firefox*/*'
,
ignored_file_patterns
=
{
'*/parts/chromium-*/*'
,
"*/parts/java-re*/*"
,
'*/parts/chromedriver*/*'
,
"*/parts/firefox*/*"
,
'*/parts/libreoffice-bin/*'
,
"*/parts/chromium-*/*"
,
'*/parts/wkhtmltopdf/*'
,
"*/parts/chromedriver*/*"
,
# R uses wrappers to relocate symbols
"*/parts/libreoffice-bin/*"
,
'*/r-language/lib*/R/*'
,
"*/parts/wkhtmltopdf/*"
,
# pulp is a git checkout with some executables
# R uses wrappers to relocate symbols
'*/pulp-repository.git/src/pulp/solverdir/cbc*'
,
"*/r-language/lib*/R/*"
,
# nss is not a binary distribution, but for some reason it has invalid rpath, but it does
# pulp is a git checkout with some executables
# not seem to be a problem in our use cases.
"*/pulp-repository.git/src/pulp/solverdir/cbc*"
,
'*/parts/nss/*'
,
# nss is not a binary distribution, but for some reason it has invalid
# npm packages containing binaries
# rpath, but it does not seem to be a problem in our use cases.
'*/node_modules/phantomjs*/*'
,
"*/parts/nss/*"
,
'*/grafana/tools/phantomjs/*'
,
# npm packages containing binaries
'*/node_modules/puppeteer/*'
,
"*/node_modules/phantomjs*/*"
,
# left over of compilation failures
"*/grafana/tools/phantomjs/*"
,
'*/*__compile__/*'
,
"*/node_modules/puppeteer/*"
,
# build dir for packages built in-place
# left over of compilation failures
'*/parts/wendelin.core/build/*'
,
"*/*__compile__/*"
,
# the depot_tools package used to build Chromium installs some
# build dir for packages built in-place
# Python libraries lacking an rpath; these are not actually used
"*/parts/wendelin.core/build/*"
,
# by Chromium itself
# the depot_tools package used to build Chromium installs some
'*/.vpython-root/*'
,
# Python libraries lacking an rpath; these are not actually used
# this library is not readable by group/other
# by Chromium itself
'*/libexec/ssh-keysign'
,
"*/.vpython-root/*"
,
# this library is just a test loading foo.so which doesn't exist
# this library is not readable by group/other
'*/test/ELF/Inputs/version-use.so'
,
"*/libexec/ssh-keysign"
,
# this library is just a text file containing "GROUP( libtinfo.so )" so it is not a dynamic executable
# this library is just a test loading foo.so which doesn't exist
'*/lib/libtermcap.so'
,
"*/test/ELF/Inputs/version-use.so"
,
# this binary is not compiled but downloaded directly from internet (and we use a wrapper with LD_LIBRARY_PATH to use it)
# this library is just a text file containing "GROUP( libtinfo.so )" so
'*/bin/phantomjs'
,
# it is not a dynamic executable
))
"*/lib/libtermcap.so"
,
# this binary is not compiled but downloaded directly from internet (and
software_hash
=
md5digest
(
software_url
)
# we use a wrapper with LD_LIBRARY_PATH to use it)
error_list
=
[]
"*/bin/phantomjs"
,
warning_list
=
[]
}
software_hash
=
md5digest
(
os
.
fspath
(
software_url
))
error_list
:
List
[
str
]
=
[]
warning_list
:
List
[
str
]
=
[]
ldd_so_resolved_re
=
re
.
compile
(
ldd_so_resolved_re
=
re
.
compile
(
r'\t(?P<library_name>.*) => (?P<library_path>.*) \
(
0x'
)
r"\t(?P<library_name>.*) => (?P<library_path>.*) \
(
0x"
,
ldd_already_loaded_re
=
re
.
compile
(
r'\t(?P<library_name>.*) \
(
0x'
)
)
warning_execution_permission_re
=
re
.
compile
(
r'ldd: warning: you do not have execution permission for (?P<library_path>.*)'
)
ldd_already_loaded_re
=
re
.
compile
(
r"\t(?P<library_name>.*) \
(
0x"
)
ldd_not_found_re
=
re
.
compile
(
r'\t(?P<library_name>.*) => not found.*'
)
warning_execution_permission_re
=
re
.
compile
(
r"ldd: warning: you do not have execution permission for (?P<library_path>.*)"
,
)
ldd_not_found_re
=
re
.
compile
(
r"\t(?P<library_name>.*) => not found.*"
)
class
DynamicLibraryNotFound
(
Exception
):
class
DynamicLibraryNotFound
(
Exception
):
"""Exception raised when ldd cannot resolve a library.
"""Exception raised when ldd cannot resolve a library."""
def
getLddOutput
(
path
:
str
)
->
Mapping
[
str
,
str
]:
"""
"""
def
getLddOutput
(
path
):
Get the output of ldd.
# type: (str) -> Dict[str, str]
"""Parse ldd output on shared object/executable as `path` and returns a mapping
Parse ldd output on shared object/executable as ``path`` and returns a
of library paths or None when library is not found, keyed by library so name.
mapping of library paths or None when library is not found, keyed by
library so name.
Raises a `DynamicLibraryNotFound` if any dynamic library is not found.
Raises a `DynamicLibraryNotFound` if any dynamic library is not found.
Special entries, like VDSO ( linux-vdso.so.1 ) or ELF interpreter
Special entries, like VDSO ( linux-vdso.so.1 ) or ELF interpreter
( /lib64/ld-linux-x86-64.so.2 ) are ignored.
( /lib64/ld-linux-x86-64.so.2 ) are ignored.
Args:
path: Path to shared object passed to ldd.
Returns:
A mapping of library so name to its path (or to `Ǹone`` if it is not
found).
"""
"""
libraries
=
{}
# type: Dict[str, str]
libraries
:
Dict
[
str
,
str
]
=
{}
try
:
try
:
ldd_output
=
subprocess
.
check_output
(
ldd_output
=
subprocess
.
check_output
(
(
'ldd'
,
path
),
(
"ldd"
,
path
),
stderr
=
subprocess
.
STDOUT
,
stderr
=
subprocess
.
STDOUT
,
universal_newlines
=
True
,
universal_newlines
=
True
,
)
)
except
subprocess
.
CalledProcessError
as
e
:
except
subprocess
.
CalledProcessError
as
e
:
if
e
.
output
not
in
(
'
\
t
not a dynamic executable
\
n
'
,):
if
e
.
output
not
in
(
"
\
t
not a dynamic executable
\
n
"
,):
raise
raise
return
libraries
return
libraries
if
ldd_output
==
'
\
t
statically linked
\
n
'
:
if
ldd_output
==
"
\
t
statically linked
\
n
"
:
return
libraries
return
libraries
not_found
=
[]
not_found
=
[]
...
@@ -170,32 +206,45 @@ def checkSoftware(slap, software_url):
...
@@ -170,32 +206,45 @@ def checkSoftware(slap, software_url):
warning_execution_permission_match
=
warning_execution_permission_re
.
match
(
line
)
warning_execution_permission_match
=
warning_execution_permission_re
.
match
(
line
)
not_found_match
=
ldd_not_found_re
.
match
(
line
)
not_found_match
=
ldd_not_found_re
.
match
(
line
)
if
resolved_so_match
:
if
resolved_so_match
:
libraries
[
resolved_so_match
.
group
(
libraries
[
resolved_so_match
.
group
(
"library_name"
)]
=
resolved_so_match
.
group
(
'library_name'
)]
=
resolved_so_match
.
group
(
'library_path'
)
"library_path"
)
elif
ldd_already_loaded_match
:
elif
ldd_already_loaded_match
:
# VDSO or ELF, ignore . See https://stackoverflow.com/a/35805410/7294664 for more about this
# VDSO or ELF, ignore .
# See https://stackoverflow.com/a/35805410/7294664 for more about this
pass
pass
elif
warning_execution_permission_match
:
elif
warning_execution_permission_match
:
# for now, ignore permission warnings
# for now, ignore permission warnings
pass
pass
elif
not_found_match
:
elif
not_found_match
:
library_name
=
not_found_match
.
group
(
'library_name'
)
library_name
=
not_found_match
.
group
(
"library_name"
)
if
library_name
.
split
(
'.'
)[
0
]
not
in
missing_lib_allowed_list
:
if
library_name
.
split
(
"."
)[
0
]
not
in
missing_lib_allowed_list
:
not_found
.
append
(
line
)
not_found
.
append
(
line
)
else
:
else
:
raise
RuntimeError
(
'Unknown ldd line %s for %s.'
%
(
line
,
path
)
)
raise
RuntimeError
(
f"Unknown ldd line
{
line
}
for
{
path
}
."
)
if
not_found
:
if
not_found
:
not_found_text
=
'
\
n
'
.
join
(
not_found
)
not_found_text
=
"
\
n
"
.
join
(
not_found
)
raise
DynamicLibraryNotFound
(
raise
DynamicLibraryNotFound
(
'{path} has some not found libraries:
\
n
{not_found_text}'
.
format
(
f"
{
path
}
has some not found libraries:
\
n
{
not_found_text
}
"
,
**
locals
())
)
)
return
libraries
return
libraries
def
checkExecutableLink
(
paths_to_check
,
valid_paths_for_libs
):
def
checkExecutableLink
(
# type: (Iterable[str], Iterable[str]) -> List[str]
paths_to_check
:
Collection
[
str
],
"""Check shared libraries linked with executables in `paths_to_check`.
valid_paths_for_libs
:
Collection
[
str
],
)
->
Sequence
[
str
]:
"""
Check shared libraries linked with executables in `paths_to_check`.
Only libraries from `valid_paths_for_libs` are accepted.
Only libraries from `valid_paths_for_libs` are accepted.
Returns a list of error messages.
Args:
paths_to_check: Paths to executables to check.
valid_paths_for_libs: Paths to libraries that can be linked.
Returns:
A list of error messages.
"""
"""
valid_paths_for_libs
=
[
os
.
path
.
realpath
(
x
)
for
x
in
valid_paths_for_libs
]
valid_paths_for_libs
=
[
os
.
path
.
realpath
(
x
)
for
x
in
valid_paths_for_libs
]
executable_link_error_list
=
[]
executable_link_error_list
=
[]
...
@@ -203,82 +252,97 @@ def checkSoftware(slap, software_url):
...
@@ -203,82 +252,97 @@ def checkSoftware(slap, software_url):
for
root
,
dirs
,
files
in
os
.
walk
(
path
):
for
root
,
dirs
,
files
in
os
.
walk
(
path
):
for
f
in
files
:
for
f
in
files
:
f
=
os
.
path
.
join
(
root
,
f
)
f
=
os
.
path
.
join
(
root
,
f
)
if
any
(
fnmatch
.
fnmatch
(
f
,
ignored_pattern
)
if
any
(
for
ignored_pattern
in
ignored_file_patterns
):
fnmatch
.
fnmatch
(
f
,
ignored_pattern
)
for
ignored_pattern
in
ignored_file_patterns
):
continue
continue
if
os
.
access
(
f
,
os
.
X_OK
)
or
fnmatch
.
fnmatch
(
f
,
'*.so'
):
if
os
.
access
(
f
,
os
.
X_OK
)
or
fnmatch
.
fnmatch
(
f
,
"*.so"
):
try
:
try
:
libs
=
getLddOutput
(
f
)
libs
=
getLddOutput
(
f
)
except
DynamicLibraryNotFound
as
e
:
except
DynamicLibraryNotFound
as
e
:
executable_link_error_list
.
append
(
str
(
e
))
executable_link_error_list
.
append
(
str
(
e
))
else
:
else
:
for
lib
,
lib_path
in
libs
.
items
():
for
lib
,
lib_path
in
libs
.
items
():
if
lib
.
split
(
'.'
)[
0
]
in
system_lib_allowed_list
:
if
lib
.
split
(
"."
)[
0
]
in
system_lib_allowed_list
:
continue
continue
lib_path
=
os
.
path
.
realpath
(
lib_path
)
lib_path
=
os
.
path
.
realpath
(
lib_path
)
# dynamically linked programs can only be linked with libraries
# dynamically linked programs can only be linked with libraries
# present in software or in shared parts repository.
# present in software or in shared parts repository.
if
any
(
lib_path
.
startswith
(
valid_path
)
if
any
(
for
valid_path
in
valid_paths_for_libs
):
lib_path
.
startswith
(
valid_path
)
for
valid_path
in
valid_paths_for_libs
):
continue
continue
executable_link_error_list
.
append
(
executable_link_error_list
.
append
(
'{f} uses system library {lib_path} for {lib}'
.
format
(
f"
{
f
}
uses system library
{
lib_path
}
for
{
lib
}
"
,
**
locals
())
)
)
return
executable_link_error_list
return
executable_link_error_list
software_directory
=
os
.
path
.
join
(
slap
.
software_directory
,
software_hash
)
software_directory
=
os
.
path
.
join
(
slap
.
software_directory
,
software_hash
)
paths_to_check
=
set
((
software_directory
,
))
paths_to_check
=
{
software_directory
}
# Compute the paths to check by inspecting buildout installed database
# Compute the paths to check by inspecting buildout installed database
# for this software. We are looking for shared parts installed by recipes.
# for this software. We are looking for shared parts installed by recipes.
config_parser
=
ConfigParser
()
config_parser
=
ConfigParser
()
config_parser
.
read
(
os
.
path
.
join
(
software_directory
,
'.installed.cfg'
))
config_parser
.
read
(
os
.
path
.
join
(
software_directory
,
".installed.cfg"
))
for
section_name
in
config_parser
.
sections
():
for
section_name
in
config_parser
.
sections
():
for
option_name
in
'location'
,
'__buildout_installed__'
:
for
option_name
in
"location"
,
"__buildout_installed__"
:
if
config_parser
.
has_option
(
section_name
,
option_name
):
if
config_parser
.
has_option
(
section_name
,
option_name
):
for
section_path
in
config_parser
.
get
(
section_name
,
option_name
).
splitlines
():
for
section_path
in
config_parser
.
get
(
section_name
,
option_name
).
splitlines
():
if
section_path
and
not
section_path
.
startswith
(
software_directory
):
if
section_path
and
not
section_path
.
startswith
(
software_directory
):
paths_to_check
.
add
(
section_path
)
paths_to_check
.
add
(
section_path
)
error_list
.
extend
(
error_list
.
extend
(
checkExecutableLink
(
checkExecutableLink
(
paths_to_check
,
paths_to_check
,
tuple
(
paths_to_check
)
+
tuple
(
slap
.
_shared_part_list
),
tuple
(
paths_to_check
)
+
tuple
(
slap
.
_shared_part_list
),
))
)
)
# check this software is not referenced in any shared parts.
# check this software is not referenced in any shared parts.
for
signature_file
in
glob
.
glob
(
for
signature_file
in
glob
.
glob
(
os
.
path
.
join
(
os
.
path
.
join
(
slap
.
shared_directory
,
slap
.
shared_directory
,
'*'
,
"*"
,
'*'
,
"*"
,
'.buildout-shared.json'
,
".buildout-shared.json"
,
)):
)
):
with
open
(
signature_file
)
as
f
:
with
open
(
signature_file
)
as
f
:
signature_content
=
f
.
read
()
signature_content
=
f
.
read
()
if
software_hash
in
signature_content
:
if
software_hash
in
signature_content
:
error_list
.
append
(
error_list
.
append
(
"Shared part is referencing non shared part or software {}
\
n
{}
\
n
"
.
format
(
f"Shared part is referencing non shared part or "
signature_file
,
signature_content
))
f"software
{
signature_file
}\
n
{
signature_content
}\
n
"
,
)
def
checkEggsVersionsKnownVulnerabilities
(
def
checkEggsVersionsKnownVulnerabilities
(
egg_directories
,
egg_directories
:
Sequence
[
str
],
safety_db
=
requests
.
get
(
safety_db
:
Mapping
[
str
,
Sequence
[
SafetyDBEntry
]]
=
requests
.
get
(
'https://raw.githubusercontent.com/pyupio/safety-db/master/data/insecure_full.json'
"https://raw.githubusercontent.com/pyupio/safety-db/master/data/insecure_full.json"
).
json
()):
).
json
(),
# type: (List[str], Dict) -> Iterable[str]
)
->
Iterable
[
str
]:
"""Check eggs against known vulnerabilities database from https://github.com/pyupio/safety-db
"""
"""
def
get_python_versions
():
Check eggs against known vulnerabilities.
# () -> Iterable[str]
"""Returns all python versions used in egg_directories
Args:
"""
egg_directories: Paths to the directories containing Python eggs.
safety_db: The database of known vulnerabilities. By default it uses the
database from https://github.com/pyupio/safety-db.
Yields:
A description of each vulnerability that applies to the current eggs.
"""
def
get_python_versions
()
->
Iterable
[
str
]:
"""Returns all python versions used in egg_directories."""
python_versions
=
set
()
python_versions
=
set
()
for
egg_dir
in
egg_directories
:
for
egg_dir
in
egg_directories
:
basename
,
_
=
os
.
path
.
splitext
(
os
.
path
.
basename
(
egg_dir
))
basename
,
_
=
os
.
path
.
splitext
(
os
.
path
.
basename
(
egg_dir
))
match
=
pkg_resources
.
EGG_NAME
(
basename
)
match
=
pkg_resources
.
EGG_NAME
(
basename
)
if
match
:
if
match
:
pyver
=
match
.
group
(
'pyver'
)
pyver
=
match
.
group
(
"pyver"
)
if
pyver
:
if
pyver
:
python_versions
.
add
(
pyver
)
python_versions
.
add
(
pyver
)
return
python_versions
return
python_versions
...
@@ -290,38 +354,42 @@ def checkSoftware(slap, software_url):
...
@@ -290,38 +354,42 @@ def checkSoftware(slap, software_url):
if
known_vulnerabilities
:
if
known_vulnerabilities
:
for
distribution
in
env
[
egg
]:
for
distribution
in
env
[
egg
]:
for
known_vulnerability
in
known_vulnerabilities
:
for
known_vulnerability
in
known_vulnerabilities
:
for
vulnerable_spec
in
known_vulnerability
[
'specs'
]:
for
vulnerable_spec
in
known_vulnerability
[
"specs"
]:
for
req
in
pkg_resources
.
parse_requirements
(
egg
+
for
req
in
pkg_resources
.
parse_requirements
(
vulnerable_spec
):
egg
+
vulnerable_spec
,
):
vulnerability_description
=
"
\
n
"
.
join
(
vulnerability_description
=
"
\
n
"
.
join
(
u"{}: {}"
.
format
(
*
item
)
f"
{
key
}
:
{
value
}
"
for
key
,
value
in
known_vulnerability
.
items
(
)
for
item
in
known_vulnerability
.
items
()
)
)
if
distribution
in
req
:
if
distribution
in
req
:
yield
(
yield
(
u"{egg} use vulnerable version {distribution.version} because {vulnerable_spec}.
\
n
"
f"
{
egg
}
use vulnerable version
{
distribution
.
version
}
"
"{vulnerability_description}
\
n
"
.
format
(
**
locals
()))
f"because
{
vulnerable_spec
}
.
\
n
"
f"
{
vulnerability_description
}\
n
"
)
warning_list
.
extend
(
warning_list
.
extend
(
checkEggsVersionsKnownVulnerabilities
(
checkEggsVersionsKnownVulnerabilities
(
glob
.
glob
(
glob
.
glob
(
os
.
path
.
join
(
os
.
path
.
join
(
slap
.
software_directory
,
slap
.
software_directory
,
software_hash
,
software_hash
,
'eggs'
,
"eggs"
,
'*'
,
"*"
,
))
+
glob
.
glob
(
)
os
.
path
.
join
(
)
slap
.
software_directory
,
+
glob
.
glob
(
software_hash
,
os
.
path
.
join
(
'develop-eggs'
,
slap
.
software_directory
,
'*'
,
software_hash
,
))))
"develop-eggs"
,
"*"
,
)
)
)
)
if
warning_list
:
if
warning_list
:
if
six
.
PY2
:
warnings
.
warn
(
"
\
n
"
.
join
(
warning_list
))
# https://bugs.python.org/issue34752
warnings
.
warn
(
'
\
n
'
.
join
(
warning_list
).
encode
(
'utf-8'
))
else
:
warnings
.
warn
(
'
\
n
'
.
join
(
warning_list
))
if
error_list
:
if
error_list
:
raise
RuntimeError
(
'
\
n
'
.
join
(
error_list
))
raise
RuntimeError
(
"
\
n
"
.
join
(
error_list
))
slapos/testing/testcase.py
View file @
198c2d6a
...
@@ -37,8 +37,6 @@ import sqlite3
...
@@ -37,8 +37,6 @@ import sqlite3
import
unittest
import
unittest
import
warnings
import
warnings
from
six.moves.urllib.parse
import
urlparse
from
netaddr
import
valid_ipv6
from
netaddr
import
valid_ipv6
from
.utils
import
getPortFromPath
from
.utils
import
getPortFromPath
...
@@ -54,6 +52,7 @@ from .check_software import checkSoftware
...
@@ -54,6 +52,7 @@ from .check_software import checkSoftware
from
..proxy.db_version
import
DB_VERSION
from
..proxy.db_version
import
DB_VERSION
from
os
import
fspath
,
PathLike
from
typing
import
(
from
typing
import
(
Callable
,
Callable
,
ClassVar
,
ClassVar
,
...
@@ -67,6 +66,8 @@ from typing import (
...
@@ -67,6 +66,8 @@ from typing import (
Type
,
Type
,
TypeVar
,
TypeVar
,
)
)
from
urllib.parse
import
urlparse
ManagedResourceType
=
TypeVar
(
"ManagedResourceType"
,
bound
=
ManagedResource
)
ManagedResourceType
=
TypeVar
(
"ManagedResourceType"
,
bound
=
ManagedResource
)
IPV4_ADDRESS_DEFAULT
:
str
=
os
.
environ
[
'SLAPOS_TEST_IPV4'
]
IPV4_ADDRESS_DEFAULT
:
str
=
os
.
environ
[
'SLAPOS_TEST_IPV4'
]
...
@@ -95,7 +96,7 @@ SNAPSHOT_DIRECTORY_DEFAULT: str | None = os.environ.get(
...
@@ -95,7 +96,7 @@ SNAPSHOT_DIRECTORY_DEFAULT: str | None = os.environ.get(
)
)
def
makeModuleSetUpAndTestCaseClass
(
def
makeModuleSetUpAndTestCaseClass
(
software_url
:
str
,
software_url
:
str
|
PathLike
[
str
]
,
*
,
*
,
base_directory
:
str
|
None
=
None
,
base_directory
:
str
|
None
=
None
,
ipv4_address
:
str
=
IPV4_ADDRESS_DEFAULT
,
ipv4_address
:
str
=
IPV4_ADDRESS_DEFAULT
,
...
@@ -125,7 +126,7 @@ def makeModuleSetUpAndTestCaseClass(
...
@@ -125,7 +126,7 @@ def makeModuleSetUpAndTestCaseClass(
See https://lab.nexedi.com/kirr/slapns for a solution to this problem.
See https://lab.nexedi.com/kirr/slapns for a solution to this problem.
Args:
Args:
software_url: The URL of the software to test.
software_url: The URL o
r path o
f the software to test.
base_directory: The base directory used for SlapOS.
base_directory: The base directory used for SlapOS.
By default, it will use the value in the environment variable
By default, it will use the value in the environment variable
``SLAPOS_TEST_WORKING_DIR``.
``SLAPOS_TEST_WORKING_DIR``.
...
@@ -189,7 +190,7 @@ def makeModuleSetUpAndTestCaseClass(
...
@@ -189,7 +190,7 @@ def makeModuleSetUpAndTestCaseClass(
)
)
if
not
software_id
:
if
not
software_id
:
software_id
=
urlparse
(
software_url
).
path
.
split
(
'/'
)[
-
2
]
software_id
=
urlparse
(
fspath
(
software_url
)
).
path
.
split
(
'/'
)[
-
2
]
logging
.
basicConfig
(
logging
.
basicConfig
(
level
=
logging
.
DEBUG
,
level
=
logging
.
DEBUG
,
...
@@ -253,31 +254,40 @@ def makeModuleSetUpAndTestCaseClass(
...
@@ -253,31 +254,40 @@ def makeModuleSetUpAndTestCaseClass(
return
setUpModule
,
SlapOSInstanceTestCase_
return
setUpModule
,
SlapOSInstanceTestCase_
def
installSoftwareUrlList
(
cls
,
software_url_list
,
max_retry
=
10
,
debug
=
False
):
def
installSoftwareUrlList
(
# type: (Type[SlapOSInstanceTestCase], Iterable[str], int, bool) -> None
cls
:
Type
[
SlapOSInstanceTestCase
],
"""Install softwares on the current testing slapos, for use in `setUpModule`.
software_url_list
:
Sequence
[
str
|
PathLike
[
str
]],
max_retry
:
int
=
10
,
debug
:
bool
=
False
,
)
->
None
:
"""
Install softwares on the current testing slapos, for use in `setUpModule`.
This also check softwares with `checkSoftware`.
Args:
cls: The test case class used for the installation.
software_url_list: List of URLs or paths to install.
max_retry: Number of times that the installation will be retried if there
is an error.
debug: If set to ``True`` the software will not be automatically removed
if there is an error during the installation process, in order to
facilitate inspection during debug.
This also check softwares with `checkSoftware`
"""
"""
def
_storeSoftwareSnapshot
(
name
)
:
def
_storeSoftwareSnapshot
(
name
:
str
)
->
None
:
for
path
in
glob
.
glob
(
os
.
path
.
join
(
for
path
in
glob
.
glob
(
os
.
path
.
join
(
cls
.
_base_directory
,
cls
.
_base_directory
,
'var'
,
"var/log/*"
,
'log'
,
'*'
,
))
+
glob
.
glob
(
os
.
path
.
join
(
))
+
glob
.
glob
(
os
.
path
.
join
(
cls
.
slap
.
software_directory
,
cls
.
slap
.
software_directory
,
'*'
,
"*/*.cfg"
,
'*.cfg'
,
))
+
glob
.
glob
(
os
.
path
.
join
(
))
+
glob
.
glob
(
os
.
path
.
join
(
cls
.
slap
.
software_directory
,
cls
.
slap
.
software_directory
,
'*'
,
"*/.installed.cfg"
,
'.installed.cfg'
,
))
+
glob
.
glob
(
os
.
path
.
join
(
))
+
glob
.
glob
(
os
.
path
.
join
(
cls
.
slap
.
shared_directory
,
cls
.
slap
.
shared_directory
,
'*'
,
"*/*/.slapos.recipe.cmmi.signature"
,
'*'
,
'.slapos.recipe.cmmi.signature'
,
)):
)):
cls
.
_copySnapshot
(
path
,
name
)
cls
.
_copySnapshot
(
path
,
name
)
...
@@ -286,7 +296,7 @@ def installSoftwareUrlList(cls, software_url_list, max_retry=10, debug=False):
...
@@ -286,7 +296,7 @@ def installSoftwareUrlList(cls, software_url_list, max_retry=10, debug=False):
cls
.
slap
.
start
()
cls
.
slap
.
start
()
for
software_url
in
software_url_list
:
for
software_url
in
software_url_list
:
cls
.
logger
.
debug
(
"Supplying %s"
,
software_url
)
cls
.
logger
.
debug
(
"Supplying %s"
,
software_url
)
cls
.
slap
.
supply
(
software_url
)
cls
.
slap
.
supply
(
fspath
(
software_url
)
)
cls
.
logger
.
debug
(
"Waiting for slapos node software to build"
)
cls
.
logger
.
debug
(
"Waiting for slapos node software to build"
)
cls
.
slap
.
waitForSoftware
(
max_retry
=
max_retry
,
debug
=
debug
,
install_all
=
not
cls
.
_skip_software_rebuild
)
cls
.
slap
.
waitForSoftware
(
max_retry
=
max_retry
,
debug
=
debug
,
install_all
=
not
cls
.
_skip_software_rebuild
)
_storeSoftwareSnapshot
(
'setupModule'
)
_storeSoftwareSnapshot
(
'setupModule'
)
...
@@ -305,7 +315,7 @@ def installSoftwareUrlList(cls, software_url_list, max_retry=10, debug=False):
...
@@ -305,7 +315,7 @@ def installSoftwareUrlList(cls, software_url_list, max_retry=10, debug=False):
try
:
try
:
for
software_url
in
software_url_list
:
for
software_url
in
software_url_list
:
cls
.
logger
.
debug
(
"Removing %s"
,
software_url
)
cls
.
logger
.
debug
(
"Removing %s"
,
software_url
)
cls
.
slap
.
supply
(
software_url
,
state
=
"destroyed"
)
cls
.
slap
.
supply
(
fspath
(
software_url
)
,
state
=
"destroyed"
)
cls
.
logger
.
debug
(
"Waiting for slapos node software to remove"
)
cls
.
logger
.
debug
(
"Waiting for slapos node software to remove"
)
cls
.
slap
.
waitForSoftware
(
max_retry
=
max_retry
,
debug
=
debug
)
cls
.
slap
.
waitForSoftware
(
max_retry
=
max_retry
,
debug
=
debug
)
except
BaseException
:
except
BaseException
:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment