Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
140
Merge Requests
140
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
Jobs
Commits
Open sidebar
nexedi
erp5
Commits
169ecf6f
Commit
169ecf6f
authored
Oct 12, 2022
by
Thomas Gambier
🚴🏼
Committed by
Lu Xu
Mar 28, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
erp5.util: add a new test type to do real requests to the slapos master
This was previously known as "SlapOS Agent".
parent
a57e0e2f
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
467 additions
and
58 deletions
+467
-58
erp5/util/taskdistribution/__init__.py
erp5/util/taskdistribution/__init__.py
+6
-0
erp5/util/testnode/RealRequestRunner.py
erp5/util/testnode/RealRequestRunner.py
+390
-0
erp5/util/testnode/ScalabilityTestRunner.py
erp5/util/testnode/ScalabilityTestRunner.py
+12
-9
erp5/util/testnode/SlapOSMasterCommunicator.py
erp5/util/testnode/SlapOSMasterCommunicator.py
+28
-16
erp5/util/testnode/UnitTestRunner.py
erp5/util/testnode/UnitTestRunner.py
+3
-6
erp5/util/testnode/Utils.py
erp5/util/testnode/Utils.py
+6
-0
erp5/util/testnode/testnode.py
erp5/util/testnode/testnode.py
+22
-27
No files found.
erp5/util/taskdistribution/__init__.py
View file @
169ecf6f
...
@@ -489,6 +489,12 @@ class TaskDistributor(RPCRetry):
...
@@ -489,6 +489,12 @@ class TaskDistributor(RPCRetry):
"""
"""
return
self
.
_retryRPC
(
'getSlaposHateoasUrl'
)
return
self
.
_retryRPC
(
'getSlaposHateoasUrl'
)
def
getServiceList
(
self
,
test_suite_title
):
"""
Returns the list of services needed to run the test
"""
return
self
.
_retryRPC
(
'getServiceList'
,
(
test_suite_title
,))
def
createTestResult
(
self
,
revision
,
test_name_list
,
node_title
,
def
createTestResult
(
self
,
revision
,
test_name_list
,
node_title
,
allow_restart
=
False
,
test_title
=
None
,
project_title
=
None
):
allow_restart
=
False
,
test_title
=
None
,
project_title
=
None
):
"""
"""
...
...
erp5/util/testnode/RealRequestRunner.py
0 → 100644
View file @
169ecf6f
##############################################################################
#
# Copyright (c) 2011 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import
datetime
import
os
import
subprocess
import
sys
import
time
import
glob
from
.
import
SlapOSControler
,
SlapOSMasterCommunicator
import
json
import
time
import
shutil
import
logging
import
string
import
random
from
six.moves.urllib.parse
import
urlparse
import
base64
from
six.moves
import
http_client
as
httplib
from
.Utils
import
createFolder
,
deunicodeData
,
dealShebang
from
slapos.grid.utils
import
md5digest
import
requests
import
slapos.slap
from
.ProcessManager
import
SubprocessError
,
ProcessManager
,
CancellationError
,
format_command
from
subprocess
import
CalledProcessError
from
.Updater
import
Updater
from
erp5.util
import
taskdistribution
from
erp5.util.benchmark.thread
import
TestThread
# for dummy slapos answer
import
signal
from
.
import
logger
from
six.moves
import
range
# max time to generate frontend instance: 1.5 hour
MAX_FRONTEND_TIME
=
60
*
90
# max time to register instance to slapOSMaster: 5 minutes
MAX_CREATION_INSTANCE_TIME
=
60
*
10
# max time for a test: 20 minutes
MAX_TEST_CASE_TIME
=
60
*
20
# max time to prepare SlapOS for testsuite (software installation, instances requests, etc.)
MAX_PREPARE_TEST_SUITE
=
3600
*
10
*
1.0
# 10 hours
# max time for a test line creation: 5 minutes
MAX_CREATION_TEST_LINE
=
60
*
10
# max time for bootstrapping an instance site
MAX_BOOTSRAPPING_TIME
=
60
*
30
# max time to get a connection
MAX_CONNECTION_TIME
=
60
*
5
# time to check site bootstrap
CHECK_BOOSTRAPPING_TIME
=
60
*
2
# runner names
PERFORMANCE_RUNNER_SCRIPT
=
"performance_tester_erp5"
REQUEST_URL_SCRIPT
=
"requestUrl"
SCALABILITY_TEST
=
"scalability_test"
TEST_SUITE_INIT
=
"__init__.py"
# access SR by password
TESTNODE_USER
=
"testnode"
HTACCESS
=
"/.htaccess"
HTPASSWD
=
"/.htpasswd"
PASSWORD_FILE
=
"/sr_pass"
PASSWORD_LENGTH
=
10
HOSTFILE
=
"/hosts"
SR_DICT
=
"frontend_software_dict"
INSTANCE_DICT
=
"instances_dict"
class
RealRequestRunner
():
def
__init__
(
self
,
testnode
):
self
.
testnode
=
testnode
self
.
slapos_controler
=
SlapOSControler
.
SlapOSControler
(
self
.
testnode
.
working_directory
,
self
.
testnode
.
config
)
# Create the slapos account configuration file and dir
key
=
self
.
testnode
.
taskdistribution
.
getSlaposAccountKey
()
certificate
=
self
.
testnode
.
taskdistribution
.
getSlaposAccountCertificate
()
# Get Slapos Master Url
self
.
slapos_url
=
''
try
:
self
.
slapos_url
=
self
.
testnode
.
taskdistribution
.
getSlaposUrl
()
if
not
self
.
slapos_url
:
self
.
slapos_url
=
self
.
testnode
.
config
[
'server_url'
]
except
Exception
:
self
.
slapos_url
=
self
.
testnode
.
config
[
'server_url'
]
# Get Slapos Master url used for api rest (using hateoas)
self
.
slapos_api_rest_url
=
self
.
testnode
.
taskdistribution
.
getSlaposHateoasUrl
()
logger
.
info
(
"SlapOS Master url is: %s"
,
self
.
slapos_url
)
logger
.
info
(
"SlapOS Master hateoas url is: %s"
,
self
.
slapos_api_rest_url
)
self
.
key_path
,
self
.
cert_path
,
config_path
=
self
.
slapos_controler
.
createSlaposConfigurationFileAccount
(
key
,
certificate
,
self
.
slapos_url
,
self
.
testnode
.
config
)
self
.
slapos_communicator_list
=
[]
# Dict containing all info about SlapOS instances requested for the test
# looks like
# {
# instance1_title: {
#
#
# }
# }
self
.
instances_parameters_dict
=
{}
def
_generateInstanceTitle
(
self
,
service_title
):
"""
Generate an instance title using various parameter
TODO : add some verification (to don't use unexisting variables)
"""
instance_title
=
"E2E.Amarisoft-"
instance_title
+=
"("
+
service_title
+
")-"
instance_title
+=
str
(
datetime
.
datetime
.
now
().
isoformat
())
+
"-"
instance_title
+=
"timestamp="
+
str
(
time
.
time
())
return
instance_title
def
getDictionaryFromFile
(
self
,
dict_file
):
dictionary
=
{}
if
os
.
path
.
isfile
(
dict_file
):
with
open
(
dict_file
,
'r'
)
as
file
:
dictionary
=
json
.
loads
(
file
.
read
())
return
dictionary
def
updateDictionaryFile
(
self
,
dict_file
,
dictionary
):
with
open
(
dict_file
,
'w'
)
as
file
:
file
.
write
(
json
.
dumps
(
dictionary
))
def
_prepareSlapOS
(
self
,
working_directory
,
slapos_instance
,
create_partition
=
1
,
software_path_list
=
None
,
use_local_shared_part
=
False
,
**
kw
):
"""
Launch slapos to build software and partitions
"""
slapproxy_log
=
os
.
path
.
join
(
self
.
testnode
.
config
[
'log_directory'
],
'slapproxy.log'
)
logger
.
debug
(
'Configured slapproxy log to %r'
,
slapproxy_log
)
reset_software
=
slapos_instance
.
retry_software_count
>
10
if
reset_software
:
slapos_instance
.
retry_software_count
=
0
logger
.
info
(
'testnode, retry_software_count: %r'
,
slapos_instance
.
retry_software_count
)
self
.
slapos_controler
.
initializeSlapOSControler
(
slapproxy_log
=
slapproxy_log
,
process_manager
=
self
.
testnode
.
process_manager
,
reset_software
=
reset_software
,
software_path_list
=
software_path_list
)
self
.
testnode
.
process_manager
.
supervisord_pid_file
=
os
.
path
.
join
(
\
self
.
slapos_controler
.
instance_root
,
'var'
,
'run'
,
'supervisord.pid'
)
method_list
=
[
"runSoftwareRelease"
]
if
create_partition
:
method_list
.
append
(
"runComputerPartition"
)
for
method_name
in
method_list
:
slapos_method
=
getattr
(
self
.
slapos_controler
,
method_name
)
logger
.
debug
(
"Before status_dict = slapos_method(...)"
)
status_dict
=
slapos_method
(
self
.
testnode
.
config
,
environment
=
self
.
testnode
.
config
[
'environment'
],
**
kw
)
logger
.
info
(
status_dict
)
logger
.
debug
(
"After status_dict = slapos_method(...)"
)
if
status_dict
[
'status_code'
]
!=
0
:
slapos_instance
.
retry
=
True
slapos_instance
.
retry_software_count
+=
1
raise
SubprocessError
(
status_dict
)
else
:
slapos_instance
.
retry_software_count
=
0
return
status_dict
def
prepareSlapOSForTestNode
(
self
,
test_node_slapos
):
"""
We will build slapos software needed by the testnode itself,
like the building of selenium-runner by default
"""
return
{
'status_code'
:
0
}
def
_initializeSlapOSConnection
(
self
):
"""
Initialize communication with slapos
"""
slap
=
slapos
.
slap
.
slap
()
retry
=
0
while
True
:
# wait until _hateoas_navigator is loaded.
if
retry
>
100
:
break
slap
.
initializeConnection
(
self
.
slapos_url
,
self
.
key_path
,
self
.
cert_path
,
timeout
=
120
,
slapgrid_rest_uri
=
self
.
slapos_api_rest_url
)
if
getattr
(
slap
,
'_hateoas_navigator'
,
None
)
is
None
:
retry
+=
1
logger
.
info
(
"Fail to load _hateoas_navigator waiting a bit and retry."
)
time
.
sleep
(
30
)
else
:
break
if
getattr
(
slap
,
'_hateoas_navigator'
,
None
)
is
None
:
raise
ValueError
(
"Fail to load _hateoas_navigator"
)
supply
=
slap
.
registerSupply
()
order
=
slap
.
registerOpenOrder
()
return
slap
,
supply
,
order
def
prepareSlapOSForTestSuite
(
self
,
node_test_suite
):
"""
Install testsuite softwares
"""
logger
.
debug
(
'Preparing SlapOS for Test Suite...'
)
logger
.
info
(
node_test_suite
)
result
=
self
.
_prepareSlapOS
(
node_test_suite
.
working_directory
,
node_test_suite
,
software_path_list
=
[
node_test_suite
.
custom_profile_path
],
cluster_configuration
=
{
'_'
:
json
.
dumps
(
node_test_suite
.
cluster_configuration
)},
use_local_shared_part
=
True
)
if
result
[
'status_code'
]
!=
0
:
return
result
slappart_directory
=
self
.
testnode
.
config
[
'srv_directory'
].
rsplit
(
"srv"
,
1
)[
0
]
instance_dict_file
=
slappart_directory
+
"var/"
+
INSTANCE_DICT
test_suite
=
node_test_suite
.
test_suite_title
# Initialize communication with slapos
slap
,
supply
,
order
=
self
.
_initializeSlapOSConnection
()
# Destroy previous instances
instance_dict
=
self
.
getDictionaryFromFile
(
instance_dict_file
)
if
test_suite
in
instance_dict
:
instance_list
=
instance_dict
[
test_suite
]
for
instance_title
,
instance_is_shared
in
list
(
instance_list
):
logger
.
info
(
"Destroying previous instance %s"
,
instance_title
)
order
.
request
(
software_release
=
"dummy_SR_just_to_destroy"
,
# XXX empty string is not supported...
partition_reference
=
instance_title
,
shared
=
instance_is_shared
,
state
=
"destroyed"
)
del
instance_list
[
0
]
instance_dict
[
test_suite
]
=
instance_list
self
.
updateDictionaryFile
(
instance_dict_file
,
instance_dict
)
# Get from ERP5 Master the configuration of the cluster for the test
service_list
=
deunicodeData
(
json
.
loads
(
self
.
testnode
.
taskdistribution
.
getServiceList
(
node_test_suite
.
test_suite_title
)))
logger
.
info
(
'List of service is %s'
,
service_list
)
instance_list
=
[]
# instance_list should already be an empty list (but it may not have been initialised if testsuite seen for the first time)
for
service
in
service_list
.
values
():
logger
.
info
(
service
)
instance_title
=
self
.
_generateInstanceTitle
(
service
[
'title'
])
software_release
=
service
[
'url'
]
logger
.
info
(
"Will request instance %s of %s (software_type %s)"
,
instance_title
,
software_release
,
service
[
'software_type'
])
s
=
SlapOSMasterCommunicator
.
SlapOSTester
(
instance_title
,
slap
,
order
,
supply
,
software_release
)
self
.
slapos_communicator_list
.
append
(
s
)
partition_parameter_kw
=
json
.
loads
(
service
[
'partition_parameter_kw'
])
if
len
(
partition_parameter_kw
)
==
1
and
'_'
in
partition_parameter_kw
:
partition_parameter_kw
[
'_'
]
=
json
.
dumps
(
partition_parameter_kw
[
'_'
])
request_kw
=
{
'partition_parameter_kw'
:
partition_parameter_kw
,
'filter_kw'
:
json
.
loads
(
service
[
'filter_kw'
])
}
s
.
requestInstanceStart
(
request_kw
=
request_kw
,
shared
=
service
[
'shared'
],
software_type
=
service
[
'software_type'
]
)
instance_list
.
append
((
instance_title
,
service
[
'shared'
]))
instance_dict
[
test_suite
]
=
instance_list
self
.
updateDictionaryFile
(
instance_dict_file
,
instance_dict
)
logger
.
debug
(
"Instances requested."
)
return
{
'status_code'
:
0
}
def
runTestSuite
(
self
,
node_test_suite
,
portal_url
):
# Wait all instances are started
for
s
in
self
.
slapos_communicator_list
:
try
:
s
.
waitInstanceStarted
()
self
.
instances_parameters_dict
[
s
.
name
]
=
s
.
getInstanceParameterDict
()
except
Exception
as
e
:
error_message
=
"Error starting instance "
+
s
.
name
+
": "
+
str
(
e
)
return
{
'status_code'
:
1
,
'error_message'
:
error_message
}
instances_parameters_file
=
os
.
path
.
join
(
self
.
testnode
.
config
[
'srv_directory'
],
"instances.json"
)
with
open
(
instances_parameters_file
,
'w'
)
as
file
:
file
.
write
(
json
.
dumps
(
self
.
instances_parameters_dict
))
logger
.
debug
(
"ALL INSTANCES CORRECTLY STARTED"
)
config
=
self
.
testnode
.
config
run_test_suite_path_list
=
glob
.
glob
(
self
.
slapos_controler
.
instance_root
+
"/*/bin/runTestSuite"
)
try
:
run_test_suite_path
=
min
(
run_test_suite_path_list
)
except
ValueError
:
raise
ValueError
(
'No runTestSuite provided in installed partitions.'
)
# Deal with Shebang size limitation
invocation_list
=
dealShebang
(
run_test_suite_path
)
invocation_list
+=
(
run_test_suite_path
,
'--master_url'
,
portal_url
,
'--revision'
,
node_test_suite
.
revision
,
'--test_node_title'
,
config
[
'test_node_title'
],
'--test_suite'
,
node_test_suite
.
test_suite
,
'--test_suite_title'
,
node_test_suite
.
test_suite_title
)
soft
=
config
[
'slapos_directory'
]
+
'/soft/'
software_list
=
[
soft
+
md5digest
(
x
)
for
x
in
config
[
'software_list'
]]
PATH
=
os
.
getenv
(
'PATH'
,
''
)
PATH
=
':'
.
join
(
x
+
'/bin'
for
x
in
software_list
)
+
(
PATH
and
':'
+
PATH
)
SLAPOS_TEST_SHARED_PART_LIST
=
os
.
pathsep
.
join
(
self
.
slapos_controler
.
shared_part_list
)
SLAPOS_TEST_LOG_DIRECTORY
=
node_test_suite
.
log_folder_path
supported_parameter_set
=
set
(
self
.
testnode
.
process_manager
.
getSupportedParameterList
(
run_test_suite_path
))
def
path
(
name
,
compat
):
# BBB
path
,
=
filter
(
os
.
path
.
exists
,
(
base
+
relative
for
relative
in
(
'/bin/'
+
name
,
'/parts/'
+
compat
)
for
base
in
software_list
))
return
path
for
option
,
value
in
(
(
'--firefox_bin'
,
lambda
:
path
(
'firefox'
,
'firefox/firefox-slapos'
)),
(
'--frontend_url'
,
lambda
:
config
[
'frontend_url'
]),
(
'--node_quantity'
,
lambda
:
config
[
'node_quantity'
]),
(
'--xvfb_bin'
,
lambda
:
path
(
'xvfb'
,
'xserver/bin/Xvfb'
)),
(
'--project_title'
,
lambda
:
node_test_suite
.
project_title
),
(
'--shared_part_list'
,
lambda
:
SLAPOS_TEST_SHARED_PART_LIST
),
(
'--log_directory'
,
lambda
:
SLAPOS_TEST_LOG_DIRECTORY
),
):
if
option
in
supported_parameter_set
:
invocation_list
+=
option
,
value
()
# TODO : include testnode correction ( b111682f14890bf )
if
hasattr
(
node_test_suite
,
'additional_bt5_repository_id'
):
additional_bt5_path
=
os
.
path
.
join
(
node_test_suite
.
working_directory
,
node_test_suite
.
additional_bt5_repository_id
)
invocation_list
.
extend
([
"--bt5_path"
,
additional_bt5_path
])
# From this point, test runner becomes responsible for updating test
# result. We only do cleanup if the test runner itself is not able
# to run.
createFolder
(
node_test_suite
.
test_suite_directory
,
clean
=
True
)
# Log the actual command with root logger
root_logger
=
logging
.
getLogger
()
root_logger
.
info
(
"Running test suite with: %s"
,
format_command
(
*
invocation_list
,
PATH
=
PATH
))
def
hide_distributor_url
(
s
):
# type: (bytes) -> bytes
return
s
.
replace
(
portal_url
.
encode
(
'utf-8'
),
b'$DISTRIBUTOR_URL'
)
self
.
testnode
.
process_manager
.
spawn
(
*
invocation_list
,
PATH
=
PATH
,
SLAPOS_TEST_SHARED_PART_LIST
=
SLAPOS_TEST_SHARED_PART_LIST
,
SLAPOS_TEST_LOG_DIRECTORY
=
SLAPOS_TEST_LOG_DIRECTORY
,
SLAPOS_INSTANCES_PARAMETERS_FILE
=
instances_parameters_file
,
cwd
=
node_test_suite
.
test_suite_directory
,
log_prefix
=
'runTestSuite'
,
output_replacers
=
(
hide_distributor_url
,),
get_output
=
False
)
return
{
'status_code'
:
0
}
def
getRelativePathUsage
(
self
):
"""
Used by the method testnode.constructProfile() to know
if the software.cfg have to use relative path or not.
"""
return
False
erp5/util/testnode/ScalabilityTestRunner.py
View file @
169ecf6f
...
@@ -386,10 +386,11 @@ Require valid-user
...
@@ -386,10 +386,11 @@ Require valid-user
self
.
error_message
=
test_configuration
[
'error_message'
]
self
.
error_message
=
test_configuration
[
'error_message'
]
self
.
randomized_path
=
test_configuration
[
'randomized_path'
]
self
.
randomized_path
=
test_configuration
[
'randomized_path'
]
if
not
self
.
launchable
:
if
not
self
.
launchable
:
logger
.
info
(
"Test suite %s is not actually launchable"
error_message
=
(
"Test suite %s is not actually launchable"
" with the current cluster configuration."
,
node_test_suite
.
test_suite_title
)
" with the current cluster configuration.
\
n
"
logger
.
info
(
"ERP5 Master indicates : %s"
,
self
.
error_message
)
"ERP5 Master indicates : %s"
%
(
node_test_suite
.
test_suite_title
,
self
.
error_message
))
return
{
'status_code'
:
1
}
logger
.
info
(
error_message
)
return
{
'status_code'
:
1
,
'error_message'
:
error_message
}
configuration_list
=
test_configuration
[
'configuration_list'
]
configuration_list
=
test_configuration
[
'configuration_list'
]
configuration
=
configuration_list
[
0
]
configuration
=
configuration_list
[
0
]
...
@@ -451,8 +452,9 @@ Require valid-user
...
@@ -451,8 +452,9 @@ Require valid-user
self
.
_comeBackFromDummySlapOS
()
self
.
_comeBackFromDummySlapOS
()
if
self
.
remainSoftwareToInstall
()
:
if
self
.
remainSoftwareToInstall
()
:
# All softwares are not installed, however maxtime is elapsed, that's a failure.
# All softwares are not installed, however maxtime is elapsed, that's a failure.
logger
.
error
(
"All softwares are not installed."
)
error_message
=
"All softwares are not installed."
return
{
'status_code'
:
1
}
logger
.
error
(
error_message
)
return
{
'status_code'
:
1
,
'error_message'
:
error_message
}
logger
.
debug
(
"All software installed."
)
logger
.
debug
(
"All software installed."
)
# even if we re-use existing setup we need proper configuration applied
# even if we re-use existing setup we need proper configuration applied
...
@@ -469,11 +471,12 @@ Require valid-user
...
@@ -469,11 +471,12 @@ Require valid-user
purge_previous_instance
=
not
self
.
use_existing_setup
)
purge_previous_instance
=
not
self
.
use_existing_setup
)
logger
.
debug
(
"Scalability instance requested."
)
logger
.
debug
(
"Scalability instance requested."
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
"Error creating instance: "
+
str
(
e
))
error_message
=
"Error creating instance: "
+
str
(
e
)
return
{
'status_code'
:
1
}
logger
.
error
(
error_message
)
return
{
'status_code'
:
1
,
'error_message'
:
error_message
}
return
{
'status_code'
:
0
}
return
{
'status_code'
:
0
}
return
{
'status_code'
:
1
}
return
{
'status_code'
:
1
,
'error_message'
:
"Software installation too long or error(s) are present during SR install."
}
def
makeSuite
(
self
,
test_suite
,
location_list
,
**
kwargs
):
def
makeSuite
(
self
,
test_suite
,
location_list
,
**
kwargs
):
import
imp
import
imp
...
...
erp5/util/testnode/SlapOSMasterCommunicator.py
View file @
169ecf6f
from
__future__
import
print_function
from
__future__
import
print_function
import
datetime
import
datetime
import
feedparser
import
json
import
json
import
traceback
import
traceback
import
time
import
time
...
@@ -71,6 +72,8 @@ class SlapOSMasterCommunicator(object):
...
@@ -71,6 +72,8 @@ class SlapOSMasterCommunicator(object):
self
.
slap_order
=
slap_order
self
.
slap_order
=
slap_order
self
.
slap_supply
=
slap_supply
self
.
slap_supply
=
slap_supply
self
.
hateoas_navigator
=
self
.
slap
.
_hateoas_navigator
self
.
hateoas_navigator
=
self
.
slap
.
_hateoas_navigator
self
.
message_history
=
[]
self
.
computer_guid
=
""
if
url
is
not
None
and
\
if
url
is
not
None
and
\
url
.
startswith
(
SOFTWARE_PRODUCT_NAMESPACE
):
url
.
startswith
(
SOFTWARE_PRODUCT_NAMESPACE
):
...
@@ -79,7 +82,7 @@ class SlapOSMasterCommunicator(object):
...
@@ -79,7 +82,7 @@ class SlapOSMasterCommunicator(object):
try
:
try
:
url
=
product
.
__getattr__
(
url
[
len
(
SOFTWARE_PRODUCT_NAMESPACE
):])
url
=
product
.
__getattr__
(
url
[
len
(
SOFTWARE_PRODUCT_NAMESPACE
):])
except
AttributeError
as
e
:
except
AttributeError
as
e
:
logger
.
warning
(
'Error on get software release: %s '
,
e
.
message
)
logger
.
warning
(
"Error on get software release: {}"
.
format
(
e
)
)
self
.
url
=
url
self
.
url
=
url
...
@@ -115,6 +118,7 @@ class SlapOSMasterCommunicator(object):
...
@@ -115,6 +118,7 @@ class SlapOSMasterCommunicator(object):
partition_reference
=
self
.
name
,
partition_reference
=
self
.
name
,
shared
=
shared
,
shared
=
shared
,
state
=
state
,
state
=
state
,
software_type
=
software_type
,
**
self
.
request_kw
)
**
self
.
request_kw
)
@
retryOnNetworkFailure
@
retryOnNetworkFailure
...
@@ -183,15 +187,17 @@ class SlapOSMasterCommunicator(object):
...
@@ -183,15 +187,17 @@ class SlapOSMasterCommunicator(object):
message_list
=
[]
message_list
=
[]
try
:
try
:
for
instance
in
self
.
getInstanceUrlList
():
for
instance
in
self
.
getInstanceUrlList
():
# we need to explicitly encode as utf-8 the unicode string we get
logger
.
info
(
'in _getInstanceState, viewing instance'
)
instance
[
"text_content"
]
=
instance
[
"text_content"
].
encode
(
'utf8'
)
logger
.
info
(
instance
)
news
=
instance
[
'SoftwareInstance_getNewsDict'
]
news
=
instance
[
'SoftwareInstance_getNewsDict'
]
state
=
INSTANCE_STATE_UNKNOWN
state
=
INSTANCE_STATE_UNKNOWN
monitor_information_dict
=
{}
monitor_information_dict
=
{}
is_slave
=
instance
[
'portal_type'
]
==
"Slave Instance"
is_slave
=
instance
[
'portal_type'
]
==
"Slave Instance"
if
is_slave
:
if
is_slave
:
if
len
(
instance
[
'getConnectionXmlAsDict'
])
>
0
:
# XXX for now consider a slave as always ready because in ORS software
# there is no information published in the slave
#if len(instance['getConnectionXmlAsDict']) > 0:
state
=
INSTANCE_STATE_STARTED
state
=
INSTANCE_STATE_STARTED
else
:
else
:
# not slave
# not slave
...
@@ -300,7 +306,6 @@ class SlapOSTester(SlapOSMasterCommunicator):
...
@@ -300,7 +306,6 @@ class SlapOSTester(SlapOSMasterCommunicator):
self
.
request_kw
=
json
.
loads
(
request_kw
)
self
.
request_kw
=
json
.
loads
(
request_kw
)
else
:
else
:
self
.
request_kw
=
request_kw
self
.
request_kw
=
request_kw
self
.
message_history
=
[]
def
getInfo
(
self
):
def
getInfo
(
self
):
info
=
""
info
=
""
...
@@ -323,12 +328,13 @@ class SlapOSTester(SlapOSMasterCommunicator):
...
@@ -323,12 +328,13 @@ class SlapOSTester(SlapOSMasterCommunicator):
def
requestInstanceStop
(
self
,
instance_title
=
None
,
request_kw
=
None
,
shared
=
False
,
software_type
=
"RootSoftwareInstance"
):
def
requestInstanceStop
(
self
,
instance_title
=
None
,
request_kw
=
None
,
shared
=
False
,
software_type
=
"RootSoftwareInstance"
):
self
.
instance
=
self
.
_request
(
INSTANCE_STATE_STOPPED
,
instance_title
,
request_kw
,
shared
,
software_type
)
self
.
instance
=
self
.
_request
(
INSTANCE_STATE_STOPPED
,
instance_title
,
request_kw
,
shared
,
software_type
)
def
requestInstanceDestroy
(
self
,
instance_title
=
None
,
request_kw
=
None
,
shared
=
False
):
def
requestInstanceDestroy
(
self
):
# TODO remove this function
self
.
destroyInstance
()
def
waitInstanceStarted
(
self
,
instance_title
=
None
):
if
not
instance_title
:
if
not
instance_title
:
instance_title
=
self
.
name
instance_title
=
self
.
name
self
.
destroyInstance
(
instance_title
)
def
waitInstanceStarted
(
self
,
instance_title
):
error_message
=
self
.
_waitInstance
(
instance_title
,
INSTANCE_STATE_STARTED
)[
"error_message"
]
error_message
=
self
.
_waitInstance
(
instance_title
,
INSTANCE_STATE_STARTED
)[
"error_message"
]
if
error_message
is
not
None
:
if
error_message
is
not
None
:
logger
.
error
(
error_message
)
logger
.
error
(
error_message
)
...
@@ -349,6 +355,13 @@ class SlapOSTester(SlapOSMasterCommunicator):
...
@@ -349,6 +355,13 @@ class SlapOSTester(SlapOSMasterCommunicator):
logger
.
error
(
error_message
)
logger
.
error
(
error_message
)
raise
ValueError
(
error_message
)
raise
ValueError
(
error_message
)
def
getInstanceParameterDict
(
self
):
for
instance
in
self
.
getInstanceUrlList
():
if
instance
[
"title"
]
==
self
.
name
:
return
instance
[
"getConnectionXmlAsDict"
]
return
{}
def
getMasterFrontendDict
(
self
):
def
getMasterFrontendDict
(
self
):
def
getInstanceGuid
():
def
getInstanceGuid
():
try
:
try
:
...
@@ -365,7 +378,7 @@ class SlapOSTester(SlapOSMasterCommunicator):
...
@@ -365,7 +378,7 @@ class SlapOSTester(SlapOSMasterCommunicator):
pass
pass
start_time
=
time
.
time
()
start_time
=
time
.
time
()
while
not
getInstanceGuid
()
and
time
.
time
()
-
start_time
<
60
*
5
:
while
not
getInstanceGuid
()
and
time
.
time
()
-
start_time
<
60
*
5
:
sleep
(
60
)
time
.
sleep
(
60
)
return
{
'instance_guid'
:
getInstanceGuid
(),
'frontend_master_ipv6'
:
frontend_master_ipv6
}
return
{
'instance_guid'
:
getInstanceGuid
(),
'frontend_master_ipv6'
:
frontend_master_ipv6
}
# XXX TODO
# XXX TODO
...
@@ -406,16 +419,15 @@ class SlapOSTester(SlapOSMasterCommunicator):
...
@@ -406,16 +419,15 @@ class SlapOSTester(SlapOSMasterCommunicator):
'frontend-url-list'
:
frontend_url_list
,
\
'frontend-url-list'
:
frontend_url_list
,
\
'balancer-user-v6'
:
balancer_user_v6
}
'balancer-user-v6'
:
balancer_user_v6
}
def
destroyInstance
(
self
,
instance_title
):
def
destroyInstance
(
self
):
self
.
name
=
instance_title
instance_url_list
=
self
.
getInstanceUrlList
()
instance_url_list
=
self
.
getInstanceUrlList
()
if
instance_url_list
:
if
instance_url_list
:
for
instance
in
instance_url_list
:
for
instance
in
instance_url_list
:
if
instance
[
"title"
]
!=
instance_titl
e
:
if
instance
[
"title"
]
!=
self
.
nam
e
:
self
.
_request
(
INSTANCE_STATE_DESTROYED
,
instance
[
"title"
])
self
.
_request
(
INSTANCE_STATE_DESTROYED
,
instance
[
"title"
])
else
:
else
:
root_instance
=
instance
root_instance
=
instance
logger
.
info
(
"Going to destroy root partition: "
+
str
(
instance_titl
e
))
logger
.
info
(
"Going to destroy root partition: "
+
str
(
self
.
nam
e
))
self
.
_request
(
INSTANCE_STATE_DESTROYED
,
root_instance
[
"title"
])
self
.
_request
(
INSTANCE_STATE_DESTROYED
,
root_instance
[
"title"
])
else
:
else
:
logger
.
info
(
"Instance not found"
)
logger
.
info
(
"Instance not found"
)
...
@@ -537,7 +549,7 @@ class SoftwareReleaseTester(SlapOSTester):
...
@@ -537,7 +549,7 @@ class SoftwareReleaseTester(SlapOSTester):
deadline
=
self
.
deadline
deadline
=
self
.
deadline
if
deadline
<
now
and
deadline
is
not
None
:
if
deadline
<
now
and
deadline
is
not
None
:
raise
TestTimeout
(
self
.
state
)
raise
Exception
(
"Test timeout (current state is {})."
.
format
(
self
.
state
)
)
_
,
_
,
next_state
,
software_state
,
instance_state
=
self
.
transition_dict
[
_
,
_
,
next_state
,
software_state
,
instance_state
=
self
.
transition_dict
[
self
.
state
]
self
.
state
]
...
...
erp5/util/testnode/UnitTestRunner.py
View file @
169ecf6f
...
@@ -32,14 +32,10 @@ import logging
...
@@ -32,14 +32,10 @@ import logging
from
.
import
logger
from
.
import
logger
from
.ProcessManager
import
SubprocessError
,
format_command
from
.ProcessManager
import
SubprocessError
,
format_command
from
.SlapOSControler
import
SlapOSControler
from
.SlapOSControler
import
SlapOSControler
from
.Utils
import
createFolder
from
.Utils
import
createFolder
,
dealShebang
from
slapos.grid.utils
import
md5digest
from
slapos.grid.utils
import
md5digest
def
dealShebang
(
run_test_suite_path
):
with
open
(
run_test_suite_path
)
as
f
:
if
f
.
read
(
2
)
==
'#!'
:
return
f
.
readline
().
split
(
None
,
1
)
return
[]
class
UnitTestRunner
(
object
):
class
UnitTestRunner
(
object
):
...
@@ -203,6 +199,7 @@ class UnitTestRunner(object):
...
@@ -203,6 +199,7 @@ class UnitTestRunner(object):
log_prefix
=
'runTestSuite'
,
log_prefix
=
'runTestSuite'
,
output_replacers
=
(
hide_distributor_url
,),
output_replacers
=
(
hide_distributor_url
,),
get_output
=
False
)
get_output
=
False
)
return
{
'status_code'
:
0
}
def
getRelativePathUsage
(
self
):
def
getRelativePathUsage
(
self
):
"""
"""
...
...
erp5/util/testnode/Utils.py
View file @
169ecf6f
...
@@ -10,6 +10,12 @@ def createFolder(folder, clean=False):
...
@@ -10,6 +10,12 @@ def createFolder(folder, clean=False):
rmtree
(
folder
)
rmtree
(
folder
)
os
.
mkdir
(
folder
)
os
.
mkdir
(
folder
)
def
dealShebang
(
run_test_suite_path
):
with
open
(
run_test_suite_path
)
as
f
:
if
f
.
read
(
2
)
==
'#!'
:
return
f
.
readline
().
split
(
None
,
1
)
return
[]
if
six
.
PY3
:
if
six
.
PY3
:
def
deunicodeData
(
data
):
def
deunicodeData
(
data
):
return
data
return
data
...
...
erp5/util/testnode/testnode.py
View file @
169ecf6f
...
@@ -38,6 +38,7 @@ from subprocess import CalledProcessError
...
@@ -38,6 +38,7 @@ from subprocess import CalledProcessError
from
.Updater
import
Updater
from
.Updater
import
Updater
from
.NodeTestSuite
import
NodeTestSuite
,
SlapOSInstance
from
.NodeTestSuite
import
NodeTestSuite
,
SlapOSInstance
from
.ScalabilityTestRunner
import
ScalabilityTestRunner
from
.ScalabilityTestRunner
import
ScalabilityTestRunner
from
.RealRequestRunner
import
RealRequestRunner
from
.UnitTestRunner
import
UnitTestRunner
from
.UnitTestRunner
import
UnitTestRunner
from
.Utils
import
deunicodeData
from
.Utils
import
deunicodeData
from
..
import
taskdistribution
from
..
import
taskdistribution
...
@@ -49,6 +50,7 @@ PROFILE_PATH_KEY = 'profile_path'
...
@@ -49,6 +50,7 @@ PROFILE_PATH_KEY = 'profile_path'
test_type_registry
=
{
test_type_registry
=
{
'UnitTest'
:
UnitTestRunner
,
'UnitTest'
:
UnitTestRunner
,
'ScalabilityTest'
:
ScalabilityTestRunner
,
'ScalabilityTest'
:
ScalabilityTestRunner
,
'SlapOSAgentTest'
:
RealRequestRunner
}
}
class
TestNode
(
object
):
class
TestNode
(
object
):
...
@@ -106,13 +108,13 @@ class TestNode(object):
...
@@ -106,13 +108,13 @@ class TestNode(object):
# Absolute path to relative path
# Absolute path to relative path
software_config_path
=
os
.
path
.
join
(
repository_path
,
profile_path
)
software_config_path
=
os
.
path
.
join
(
repository_path
,
profile_path
)
if
use_relative_path
:
if
use_relative_path
:
from_path
=
os
.
path
.
join
(
self
.
working_directory
,
from_path
=
os
.
path
.
join
(
self
.
working_directory
,
node_test_suite
.
reference
)
node_test_suite
.
reference
)
software_config_path
=
os
.
path
.
relpath
(
software_config_path
,
from_path
)
software_config_path
=
os
.
path
.
relpath
(
software_config_path
,
from_path
)
# Construct sections
# Construct sections
if
not
(
buildout_section_id
is
None
)
:
if
buildout_section_id
is
not
None
:
# Absolute path to relative
# Absolute path to relative
if
use_relative_path
:
if
use_relative_path
:
from_path
=
os
.
path
.
join
(
self
.
working_directory
,
from_path
=
os
.
path
.
join
(
self
.
working_directory
,
...
@@ -320,7 +322,7 @@ shared = true
...
@@ -320,7 +322,7 @@ shared = true
testnode_software_successfully_built
=
True
testnode_software_successfully_built
=
True
logger
.
info
(
"Will now skip build of testnode software"
)
logger
.
info
(
"Will now skip build of testnode software"
)
# Clean-up test suites
# Clean-up test suites
self
.
purgeOldTestSuite
(
test_suite_data
)
#
self.purgeOldTestSuite(test_suite_data)
for
test_suite
in
test_suite_data
:
for
test_suite
in
test_suite_data
:
node_test_suite
=
self
.
getNodeTestSuite
(
node_test_suite
=
self
.
getNodeTestSuite
(
test_suite
.
pop
(
"test_suite_reference"
))
test_suite
.
pop
(
"test_suite_reference"
))
...
@@ -354,6 +356,8 @@ shared = true
...
@@ -354,6 +356,8 @@ shared = true
generated_config
=
taskdistributor
.
generateConfiguration
(
generated_config
=
taskdistributor
.
generateConfiguration
(
node_test_suite
.
test_suite_title
)
node_test_suite
.
test_suite_title
)
json_data
=
json
.
loads
(
generated_config
)
json_data
=
json
.
loads
(
generated_config
)
logger
.
info
(
"DEBUG JSON"
)
logger
.
info
(
json_data
)
cluster_configuration
=
deunicodeData
(
json_data
[
'configuration_list'
][
0
])
cluster_configuration
=
deunicodeData
(
json_data
[
'configuration_list'
][
0
])
node_test_suite
.
edit
(
cluster_configuration
=
cluster_configuration
)
node_test_suite
.
edit
(
cluster_configuration
=
cluster_configuration
)
# Now prepare the installation of SlapOS and create instance
# Now prepare the installation of SlapOS and create instance
...
@@ -364,35 +368,26 @@ shared = true
...
@@ -364,35 +368,26 @@ shared = true
# should be at the same revision, so it is safe to prune orphan
# should be at the same revision, so it is safe to prune orphan
# objects now.
# objects now.
git_gc_auto
()
git_gc_auto
()
def
report_error
(
error_message
):
test_result
.
reportFailure
(
stdout
=
error_message
)
logger
.
error
(
error_message
)
raise
ValueError
(
error_message
)
if
status_dict
[
'status_code'
]
==
1
:
report_error
(
status_dict
.
get
(
'error_message'
)
or
"Error during prepareSlapOSForTestSuite"
)
# Give some time so computer partitions may start
# Give some time so computer partitions may start
# as partitions can be of any kind we have and likely will never have
# as partitions can be of any kind we have and likely will never have
# a reliable way to check if they are up or not ...
# a reliable way to check if they are up or not ...
time
.
sleep
(
20
)
time
.
sleep
(
20
)
# XXX: Do not switch according to the test type. IOW, the
# following code must be moved to the test type class.
if
my_test_type
==
'UnitTest'
:
runner
.
runTestSuite
(
node_test_suite
,
portal_url
)
runner
.
runTestSuite
(
node_test_suite
,
portal_url
)
elif
my_test_type
==
'ScalabilityTest'
:
error_message
=
None
# A problem is appeared during runTestSuite
if
status_dict
[
'status_code'
]
==
1
:
error_message
=
"Software installation too long or error(s) are present during SR install."
else
:
status_dict
=
runner
.
runTestSuite
(
node_test_suite
,
portal_url
)
status_dict
=
runner
.
runTestSuite
(
node_test_suite
,
portal_url
)
# A problem is appeared during runTestSuite
# A problem is appeared during runTestSuite
if
status_dict
[
'status_code'
]
==
1
:
if
status_dict
[
'status_code'
]
==
1
:
error_message
=
status_dict
[
'error_message'
]
report_error
(
status_dict
.
get
(
'error_message'
)
or
'Error during runTestSuite'
)
# If an error is appeared
if
error_message
:
test_result
.
reportFailure
(
stdout
=
error_message
)
logger
.
error
(
error_message
)
raise
ValueError
(
error_message
)
else
:
raise
NotImplementedError
# break the loop to get latest priorities from master
break
break
except
(
SubprocessError
,
CalledProcessError
,
ConnectionError
)
as
e
:
except
(
SubprocessError
,
CalledProcessError
,
ConnectionError
)
as
e
:
logger
.
exception
(
""
)
logger
.
exception
(
""
)
...
...
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