Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • neurorobotics-platform/VirtualCoach
1 result
Show changes
Commits on Source (7)
...@@ -6,10 +6,9 @@ set -x ...@@ -6,10 +6,9 @@ set -x
whoami whoami
env | sort env | sort
if [ -f .ci/env ]; then if [ -z "${HBP}" ]; then
# add quotes to all vars (but do it once) echo "USAGE: The HBP variable not specified"
sudo sed -i -E 's/="*(.*[^"])"*$/="\1"/' .ci/env exit 1
source '.ci/env'
fi fi
if [ -z ${PYTHON_VERSION_MAJOR_MINOR} ]; then if [ -z ${PYTHON_VERSION_MAJOR_MINOR} ]; then
...@@ -17,24 +16,23 @@ if [ -z ${PYTHON_VERSION_MAJOR_MINOR} ]; then ...@@ -17,24 +16,23 @@ if [ -z ${PYTHON_VERSION_MAJOR_MINOR} ]; then
export PYTHON_VERSION_MAJOR_MINOR=$(python -c "import sys; print('{}.{}'.format(sys.version_info.major, sys.version_info.minor))") export PYTHON_VERSION_MAJOR_MINOR=$(python -c "import sys; print('{}.{}'.format(sys.version_info.major, sys.version_info.minor))")
fi fi
# Force the build to happen in the build workspace, not in the container # Force the build to happen in the build workspace
# This should be done at the beginning, but there are some circular dependencies cd $HBP
# between Gazebo build artifacts (existing in the container) and nrp aliases/variables
export HBP=$WORKSPACE
cd $WORKSPACE
export PYTHONPATH= export PYTHONPATH=
source ${USER_SCRIPTS_DIR}/nrp_variables
cd ${WORKSPACE}/${VIRTUAL_COACH_DIR} VIRTUAL_COACH_DIR=${GIT_CHECKOUT_DIR:-"VirtualCoach"}
cd ${HBP}/${VIRTUAL_COACH_DIR}
# Configure build # Configure build
export VIRTUAL_ENV_PATH=$VIRTUAL_ENV
export NRP_INSTALL_MODE=dev export NRP_INSTALL_MODE=dev
export PYTHONPATH=hbp_nrp_virtual_coach:${VIRTUAL_ENV_PATH}/lib/python${PYTHON_VERSION_MAJOR_MINOR}/site-packages:$PYTHONPATH export PYTHONPATH=hbp_nrp_virtual_coach:$PYTHONPATH
# Build # Build
export IGNORE_LINT='platform_venv|config_files|examples/integration_test/test_experiment_folder' export IGNORE_LINT='platform_venv|config_files|examples/integration_test/test_experiment_folder'
make devinstall # verify_base-ci fails on dependencies mismatch, but ignores linter errors, which are cought by Jenkins afterwards
make verify_base -i rm -rf build_env
virtualenv build_env \
&& . build_env/bin/activate \
&& make --always-make verify_base-ci
...@@ -2,12 +2,11 @@ ...@@ -2,12 +2,11 @@
set -e set -e
# Build package # Build package
git fetch --tags export NRP_INSTALL_MODE=user
PYNRP_VERSION=$(git describe --tags --always) make set-nrp-version
echo "${PYNRP_VERSION}"
echo "VERSION = \"${PYNRP_VERSION}\"" > hbp_nrp_virtual_coach/pynrp/version.py
pushd hbp_nrp_virtual_coach pushd hbp_nrp_virtual_coach
python3 setup.py sdist bdist_wheel python3 setup.py sdist bdist_wheel
popd popd
cp hbp_nrp_virtual_coach/dist/*.whl ./
\ No newline at end of file
...@@ -5,145 +5,4 @@ ...@@ -5,145 +5,4 @@
// https://www.jenkins.io/doc/book/pipeline/shared-libraries/ // https://www.jenkins.io/doc/book/pipeline/shared-libraries/
@Library('nrp-shared-libs@master') _ @Library('nrp-shared-libs@master') _
// Before starting pipeline, we try to get the proper image tag jenkinsCIpipelinePythonModule("VirtualCoach")
def DEFAULT_BRANCH = 'development'
// selectTopicBranch function is used to choose the correct branch name as topic
// the function is defined in shared libs
//
// In case there is a PR for a branch, then Jenkins runs a pipeline for this pull request, not for the branch,
// even if there are new commits to the branch (until PR is not closed). The BRANCH_NAME variable in this case is something like PR-###
// The name of the branch which is merged is stored in CHANGE_BRANCH variable. Thus, we should choose CHANGE_BRANCH as topic
//
// If there is a branch without PR, then Jenkins creates build for it normally for every push and the branch name is stored in BRANCH_NAME variable.
// CHANGE_BRANCH is empty in this case. Thus, we choose BRANCH_NAME as topic for branches without PR.
def TOPIC_BRANCH = selectTopicBranch(env.BRANCH_NAME, env.CHANGE_BRANCH)
// We try to pull the image with the topic name, or use default tag otherwise
def IMG_TAG = checkImageTag("${TOPIC_BRANCH}", "${DEFAULT_BRANCH}")
pipeline {
environment {
USER_SCRIPTS_DIR = "user-scripts"
ADMIN_SCRIPTS_DIR = "admin-scripts"
GAZEBO_ROS_DIR = "GazeboRosPackages"
VIRTUAL_COACH_DIR = "VirtualCoach"
// GIT_CHECKOUT_DIR is a dir of the main project (that was pushed)
GIT_CHECKOUT_DIR = "${env.VIRTUAL_COACH_DIR}"
// That is needed to pass the variables into environment with the same name from
// Jenkins global scope (def ..=..)
TOPIC_BRANCH = "${TOPIC_BRANCH}"
DEFAULT_BRANCH = "${DEFAULT_BRANCH}"
NexusPyPiRepoUrl = "https://nexus.neurorobotics.ebrains.eu/repository/nrp-pypi-internal/"
CODE_COVERAGE_LINE = 74
}
agent {
docker {
label 'ci_label'
// NEXUS_REGISTRY_IP and NEXUS_REGISTRY_PORT are Jenkins global variables
image "${env.NEXUS_REGISTRY_IP}:${env.NEXUS_REGISTRY_PORT}/nrp:${IMG_TAG}"
args '--entrypoint="" -u root --privileged'
}
}
options {
// Skip code checkout prior running pipeline (only Jenkinsfile is checked out)
skipDefaultCheckout true
}
stages {
stage('Code checkout') {
steps {
// clear workspace
sh "rm -rf *"
// Notify BitBucket on the start of the job
// The Bitbucket Build Status Notifier is used
// REF: https://plugins.jenkins.io/bitbucket-build-status-notifier/
bitbucketStatusNotify(buildState: 'INPROGRESS', buildName: 'Code checkout')
// Debug information on available environment
echo sh(script: 'env|sort', returnStdout: true)
// Checkout main project to GIT_CHECKOUT_DIR
dir(env.GIT_CHECKOUT_DIR) {
checkout scm
sh 'chown -R "${USER}" ./'
}
// Clone all dependencies
cloneRepoTopic(env.USER_SCRIPTS_DIR, 'git@bitbucket.org:hbpneurorobotics/user-scripts.git', env.TOPIC_BRANCH, env.DEFAULT_BRANCH, '${USER}')
cloneRepoTopic(env.GAZEBO_ROS_DIR, 'git@bitbucket.org:hbpneurorobotics/gazeborospackages.git', env.TOPIC_BRANCH, env.DEFAULT_BRANCH, '${USER}')
cloneRepoTopic(env.ADMIN_SCRIPTS_DIR, 'git@bitbucket.org:hbpneurorobotics/admin-scripts.git', env.TOPIC_BRANCH, 'master', '${USER}')
}
}
stage('Build GazeboRosPackages') {
steps {
bitbucketStatusNotify(buildState: 'INPROGRESS', buildName: 'Building GazeboRosPackages')
// Use GazeboRosPackages build script
dir(env.GAZEBO_ROS_DIR){
// Determine explicitly the shell as bash (needed for proper user-scripts operation)
sh 'bash .ci/build.bash ${WORKSPACE}/${USER_SCRIPTS_DIR}/'
}
}
}
stage('Build VirtualCoach') {
steps {
bitbucketStatusNotify(buildState: 'INPROGRESS', buildName: 'Building VirtualCoach')
// Determine explicitly the shell as bash (needed for proper user-scripts operation)
dir(env.GIT_CHECKOUT_DIR){
// this is a workaround to pass all env vars into script run by the other user (now we are root)
sh 'env > .ci/env'
sh 'sudo -H -u bbpnrsoa bash .ci/build.bash'
// deliver artifacts
// Make true to false when the coverage reaches 85%-90%. This with turn off auto threshold for coverage
makeReports(true, env.CODE_COVERAGE_LINE)
}
}
}
stage('Create package') {
steps {
bitbucketStatusNotify(buildState: 'INPROGRESS', buildName: 'Building pynrp')
dir(env.GIT_CHECKOUT_DIR){
// tbuild package
sh 'bash ./.ci/whl-package.bash'
// upload package
withCredentials([usernamePassword(credentialsId: 'nexusadmin', usernameVariable: 'USER', passwordVariable: 'PASSWORD')])
{
sh 'pip3 install twine packaging'
sh 'twine upload -u $USER -p $PASSWORD --repository-url ${NexusPyPiRepoUrl} hbp_nrp_virtual_coach/dist/*.whl'
}
}
}
}
}
post {
always {
dir(env.GIT_CHECKOUT_DIR){
archiveArtifacts 'p*.*'
archiveArtifacts 'test-reports/*.*'
archiveArtifacts 'coverage.xml'
}
}
aborted {
bitbucketStatusNotify(buildState: 'FAILED', buildDescription: 'Build aborted!')
}
failure {
bitbucketStatusNotify(buildState: 'FAILED', buildDescription: 'Build failed, see console output!')
}
success{
bitbucketStatusNotify(buildState: 'SUCCESSFUL', buildDescription: 'branch ' + env.GIT_BRANCH)
}
}
}
...@@ -18,7 +18,7 @@ PYTHON_PIP_VERSION?=pip>=19 ...@@ -18,7 +18,7 @@ PYTHON_PIP_VERSION?=pip>=19
python_version_full := $(shell python -c "import sys; maj, min = sys.version_info[:2]; print('{}.{}'.format(maj, min))" 2>&1) python_version_full := $(shell python -c "import sys; maj, min = sys.version_info[:2]; print('{}.{}'.format(maj, min))" 2>&1)
ifeq ($(NRP_INSTALL_MODE),user) ifeq ($(NRP_INSTALL_MODE),user)
include user_makefile include $(HBP)/user-scripts/config_files/user_makefile
else else
CI_REPO?=git@bitbucket.org:hbpneurorobotics/admin-scripts.git CI_REPO?=git@bitbucket.org:hbpneurorobotics/admin-scripts.git
CI_DIR?=$(HBP)/admin-scripts/ContinuousIntegration CI_DIR?=$(HBP)/admin-scripts/ContinuousIntegration
......
This repository is part of the Neurorobotics Platform software This repository is part of the Neurorobotics Platform software
Copyright (C) Human Brain Project Copyright (C) Human Brain Project
https://neurorobotics.ai https://neurorobotics.net
The Human Brain Project is a European Commission funded project The Human Brain Project is a European Commission funded project
in the frame of the [Horizon2020 FET Flagship plan](http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships). in the frame of the [Horizon2020 FET Flagship plan](http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships).
This work has received funding from the European Union’s Horizon 2020 Framework Programme for Research and Innovation under the Specific Grant Agreement No. 720270 (Human Brain Project SGA1), and the Specific Grant Agreement No. 785907 (Human Brain Project SGA2), and under the Specific Grant Agreement No. 945539 (Human Brain Project SGA3).
You are free to clone this repository and amend its code with respect to You are free to clone this repository and amend its code with respect to
the license that you find in the root folder. the license that you find in the root folder.
......
...@@ -69,11 +69,11 @@ The first thing we need to do is to import the Virtual Coach and create a Virtua ...@@ -69,11 +69,11 @@ The first thing we need to do is to import the Virtual Coach and create a Virtua
from pynrp.virtual_coach import VirtualCoach from pynrp.virtual_coach import VirtualCoach
vc = VirtualCoach(environment='http://localhost:9000', storage_username='nrpuser', storage_password='password') vc = VirtualCoach(environment='http://localhost:9000', storage_username='nrpuser', storage_password='password')
Here we chose the local machine as the NRP server. The Virtual Coach can connect to NRP servers running in other locations by setting the *environment* parameter to the proper URL. For example, it is possible to connect to the NRP server running at *http://148.187.149.212* by executing this command: Here we chose the local machine as the NRP server. The Virtual Coach can connect to NRP servers running in other locations by setting the *environment* parameter to the proper URL. For example, it is possible to connect to the NRP server running at *https://nrp-legacy.neurorobotics.ebrains.eu* by executing this command:
.. code-block:: python .. code-block:: python
vc = VirtualCoach('http://148.187.149.212', oidc_username='<your-hbp-username>', oidc_password='<your-hbp-password>') vc = VirtualCoach('https://nrp-legacy.neurorobotics.ebrains.eu', oidc_username='<your-hbp-username>', oidc_password='<your-hbp-password>')
Notice that parameters *storage_username* and *storage_password* are replaced with *oidc_username* and *oidc_password*. The latter must be used when EBRAINS :term:`OIDC` authentication is required, the former are used otherwise. Notice that parameters *storage_username* and *storage_password* are replaced with *oidc_username* and *oidc_password*. The latter must be used when EBRAINS :term:`OIDC` authentication is required, the former are used otherwise.
......
...@@ -56,6 +56,9 @@ class Config(dict): ...@@ -56,6 +56,9 @@ class Config(dict):
# initialize the parent dictionary, required by pylint # initialize the parent dictionary, required by pylint
dict.__init__(self) dict.__init__(self)
# remove trailing slash in the environment url
url = environment[:-1] if environment.endswith('/') else environment
# ensure the config.json file exists and is readable by our user # ensure the config.json file exists and is readable by our user
logger.info('Loading configuration file config.json') logger.info('Loading configuration file config.json')
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config.json') path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config.json')
...@@ -90,7 +93,7 @@ class Config(dict): ...@@ -90,7 +93,7 @@ class Config(dict):
# convenience, prepend the proxy url to all proxy services, we cannot do this # convenience, prepend the proxy url to all proxy services, we cannot do this
# for the simulation services because they are backend/experiment id specific # for the simulation services because they are backend/experiment id specific
for k, v in self['proxy-services'].items(): for k, v in self['proxy-services'].items():
self['proxy-services'][k] = '{}/proxy/{}'.format(environment, v) self['proxy-services'][k] = '{}/proxy/{}'.format(url, v)
def __validate(self, key, values): def __validate(self, key, values):
""" """
......
...@@ -885,7 +885,7 @@ class Simulation(object): ...@@ -885,7 +885,7 @@ class Simulation(object):
:param file_name: The name of csv file for which to retrieve the content. :param file_name: The name of csv file for which to retrieve the content.
""" """
return self.__vc.get_csv_last_run_file(self.__experiment_id, file_name) return self.__vc.get_last_run_file(self.__experiment_id, 'csv', file_name)
def __send_recorder_cmd(self, cmd): def __send_recorder_cmd(self, cmd):
""" """
......
...@@ -65,6 +65,9 @@ class TestConfig(unittest.TestCase): ...@@ -65,6 +65,9 @@ class TestConfig(unittest.TestCase):
for k, v in config['proxy-services'].items(): for k, v in config['proxy-services'].items():
self.assertEqual(v, '%s/proxy/%s' % ('localhost', self._conf['proxy-services'][k])) self.assertEqual(v, '%s/proxy/%s' % ('localhost', self._conf['proxy-services'][k]))
def test_trailing_slash(self):
self.assertEqual(Config('localhost:8080'), Config('localhost:8080/'))
@patch('pynrp.config.json.load') @patch('pynrp.config.json.load')
def test_validation_missing_key(self, mock_load): def test_validation_missing_key(self, mock_load):
# missing section # missing section
......
...@@ -599,10 +599,10 @@ class TestSimulation(unittest.TestCase): ...@@ -599,10 +599,10 @@ class TestSimulation(unittest.TestCase):
def test_get_csv_last_run_file(self): def test_get_csv_last_run_file(self):
self._sim._Simulation__vc = Mock() self._sim._Simulation__vc = Mock()
self._sim._Simulation__vc.get_csv_last_run_file = Mock() self._sim._Simulation__vc.get_last_run_file = Mock()
self._sim._Simulation__experiment_id = 'experiment_id' self._sim._Simulation__experiment_id = 'experiment_id'
self._sim.get_csv_last_run_file('file_name') self._sim.get_csv_last_run_file('file_name')
self._sim._Simulation__vc.get_csv_last_run_file.assert_called_once_with('experiment_id', 'file_name') self._sim._Simulation__vc.get_last_run_file.assert_called_once_with('experiment_id', 'csv', 'file_name')
def test_recording(self): def test_recording(self):
self._sim._Simulation__config = {'simulation-services': {'recorder':'recorder'}} self._sim._Simulation__config = {'simulation-services': {'recorder':'recorder'}}
...@@ -623,4 +623,4 @@ class TestSimulation(unittest.TestCase): ...@@ -623,4 +623,4 @@ class TestSimulation(unittest.TestCase):
self._sim._Simulation__http_client.post.assert_called_with(u'url/recorder/reset', body='reset') self._sim._Simulation__http_client.post.assert_called_with(u'url/recorder/reset', body='reset')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
\ No newline at end of file
...@@ -29,6 +29,7 @@ from future import standard_library ...@@ -29,6 +29,7 @@ from future import standard_library
standard_library.install_aliases() standard_library.install_aliases()
from builtins import object from builtins import object
from pynrp.virtual_coach import VirtualCoach from pynrp.virtual_coach import VirtualCoach
import pynrp.version
from unittest.mock import Mock, patch, MagicMock from unittest.mock import Mock, patch, MagicMock
import unittest import unittest
...@@ -149,6 +150,7 @@ class TestVirtualCoach(unittest.TestCase): ...@@ -149,6 +150,7 @@ class TestVirtualCoach(unittest.TestCase):
"sdf":"example_model_2.sdf", "sdf":"example_model_2.sdf",
"configPath":"example_model_2/model.config"}} "configPath":"example_model_2/model.config"}}
def test_init_asserts_no_password(self): def test_init_asserts_no_password(self):
# invalid environment # invalid environment
self.assertRaises(AssertionError, VirtualCoach, environment=True) self.assertRaises(AssertionError, VirtualCoach, environment=True)
...@@ -166,6 +168,14 @@ class TestVirtualCoach(unittest.TestCase): ...@@ -166,6 +168,14 @@ class TestVirtualCoach(unittest.TestCase):
def test_no_login_credentials(self): def test_no_login_credentials(self):
self.assertRaises(Exception, VirtualCoach) self.assertRaises(Exception, VirtualCoach)
def test_version(self):
self.assertRegex(pynrp.version._get_version(), "^\d+.\d+.\d+")
self.assertRegex(pynrp.version.VERSION, "^\d+.\d+.\d+")
@patch('os.getenv', return_value="/non-existing-path")
def test_version_exception(self, mock_getenv):
self.assertRaises(RuntimeError, pynrp.version._get_version)
@patch('pynrp.virtual_coach.VirtualCoach._VirtualCoach__get_storage_token') @patch('pynrp.virtual_coach.VirtualCoach._VirtualCoach__get_storage_token')
@patch('getpass.getpass', return_value='password') @patch('getpass.getpass', return_value='password')
......
'''version string - generated by setVersion.sh''' '''version string - automatically calculated from the SCM (git)'''
VERSION = '3.2.1' import subprocess
import os
from subprocess import CalledProcessError
def _get_version():
try:
version = subprocess.run(['bash',
f'{os.getenv("HBP")}/user-scripts/nrp_get_scm_version.sh',
'get_scm_version'],
stdout=subprocess.PIPE, check=True).stdout.decode('utf-8')
except CalledProcessError as e:
raise RuntimeError("The SCM version calculation script failed.\
Expected path: $HBP/user-scripts/nrp_get_scm_version.sh,\
check its existance.") from e
return version
VERSION = _get_version()
...@@ -3,7 +3,7 @@ future ...@@ -3,7 +3,7 @@ future
texttable==0.8.7 texttable==0.8.7
progressbar2==3.52.1 progressbar2==3.52.1
httplib2==0.12.1 httplib2==0.12.1
pandas==1.0.5 pandas==1.4.4
jupyter==1.0.0 jupyter==1.0.0
requests requests
ipykernel==5.3.2 ipykernel==5.3.2
......
...@@ -2,47 +2,15 @@ ...@@ -2,47 +2,15 @@
# pylint: disable=F0401,E0611,W0622,E0012,W0142,W0402 # pylint: disable=F0401,E0611,W0622,E0012,W0142,W0402
from builtins import str from setuptools import setup
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
from optparse import Option # pylint:disable=deprecated-module
import pip
import pathlib import pathlib
import pynrp import pynrp
README = (pathlib.Path(__file__).parent / "README.txt").read_text() README = (pathlib.Path(__file__).parent / "README.txt").read_text()
options = Option('--workaround')
options.skip_requirements_regex = None
reqs_file = './requirements.txt' reqs_file = './requirements.txt'
install_reqs = list(val.strip() for val in open(reqs_file))
pip_version_major = int(pip.__version__.split('.')[0]) reqs = install_reqs
# Hack for old pip versions
if pip_version_major == 1:
# Versions 1.x rely on pip.req.parse_requirements
# but don't require a "session" parameter
from pip.req import parse_requirements # pylint:disable=no-name-in-module, import-error
install_reqs = parse_requirements(reqs_file, options=options)
reqs = [str(ir.req) for ir in install_reqs]
elif 10 > pip_version_major > 1:
# Versions greater than 1.x but smaller than 10.x rely on pip.req.parse_requirements
# and requires a "session" parameter
from pip.req import parse_requirements # pylint:disable=no-name-in-module, import-error
from pip.download import PipSession # pylint:disable=no-name-in-module, import-error
options.isolated_mode = False
install_reqs = parse_requirements( # pylint:disable=unexpected-keyword-arg
reqs_file,
session=PipSession,
options=options
)
reqs = [str(ir.req) for ir in install_reqs]
elif pip_version_major >= 10:
# Versions greater or equal to 10.x don't rely on pip.req.parse_requirements
install_reqs = list(val.strip() for val in open(reqs_file))
reqs = install_reqs
config = { config = {
'description': 'Python interface to the Neurorobotics Platform (NRP)', 'description': 'Python interface to the Neurorobotics Platform (NRP)',
...@@ -57,7 +25,10 @@ config = { ...@@ -57,7 +25,10 @@ config = {
'package_data': { 'package_data': {
'pynrp': ['config.json'] 'pynrp': ['config.json']
}, },
'classifiers': ['Programming Language :: Python :: 3.8'], 'classifiers': [
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10'],
'scripts': [], 'scripts': [],
'name': 'pynrp', 'name': 'pynrp',
'include_package_data': True, 'include_package_data': True,
......