diff options
22 files changed, 1190 insertions, 492 deletions
diff --git a/jjb/calipso/calipso.yml b/jjb/calipso/calipso.yml new file mode 100644 index 000000000..c808e9b3c --- /dev/null +++ b/jjb/calipso/calipso.yml @@ -0,0 +1,61 @@ +- project: + name: calipso + + project: '{name}' + + jobs: + - 'calipso-verify-{stream}' + + stream: + - master: + branch: '{stream}' + disabled: false + +- job-template: + name: 'calipso-verify-{stream}' + + disabled: '{obj:disabled}' + + parameters: + - project-parameter: + project: '{project}' + branch: '{branch}' + - 'opnfv-build-defaults' + + + scm: + - git-scm-gerrit + + triggers: + - gerrit: + server-name: 'gerrit.opnfv.org' + trigger-on: + - patchset-created-event: + exclude-drafts: 'false' + exclude-trivial-rebase: 'false' + exclude-no-code-change: 'false' + - draft-published-event + - comment-added-contains-event: + comment-contains-value: 'recheck' + - comment-added-contains-event: + comment-contains-value: 'reverify' + projects: + - project-compare-type: 'ANT' + project-pattern: '{project}' + branches: + - branch-compare-type: 'ANT' + branch-pattern: '**/{branch}' + + builders: + - verify-unit-tests + +- builder: + name: verify-unit-tests + builders: + - shell: | + #!/bin/bash + set -o errexit + set -o nounset + set -o pipefail + cd $WORKSPACE + PYTHONPATH=$PWD/app python3 -m unittest discover -s app/test/fetch diff --git a/jjb/compass4nfv/compass-ci-jobs.yml b/jjb/compass4nfv/compass-ci-jobs.yml index 4c12f199e..8b4a74bd1 100644 --- a/jjb/compass4nfv/compass-ci-jobs.yml +++ b/jjb/compass4nfv/compass-ci-jobs.yml @@ -87,6 +87,24 @@ - 'k8-nosdn-nofeature-ha': disabled: false auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger' + - 'os-nosdn-nofeature-noha': + disabled: false + auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger' + - 'os-odl_l3-nofeature-noha': + disabled: false + auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger' + - 'os-odl_l2-moon-noha': + disabled: false + auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger' + - 'os-nosdn-kvm-noha': + disabled: false + auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger' + - 'os-odl-sfc-noha': + disabled: false + auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger' + - 'os-nosdn-dpdk-noha': + disabled: false + auto-trigger-name: 'compass-{scenario}-{pod}-{stream}-trigger' jobs: @@ -351,6 +369,10 @@ ######################## # trigger macros ######################## + +#--------------------------- +# ha-baremetal-centos-master +#--------------------------- - trigger: name: 'compass-os-nosdn-nofeature-ha-baremetal-centos-master-trigger' triggers: @@ -400,10 +422,41 @@ triggers: - timed: '' +#----------------------------- +# noha-baremetal-centos-master +#----------------------------- +- trigger: + name: 'compass-os-nosdn-nofeature-noha-baremetal-centos-master-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl_l3-nofeature-noha-baremetal-centos-master-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl_l2-moon-noha-baremetal-centos-master-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-nosdn-kvm-noha-baremetal-centos-master-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl-sfc-noha-baremetal-centos-master-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-nosdn-dpdk-noha-baremetal-centos-master-trigger' + triggers: + - timed: '' + +#-------------------- +# ha-baremetal-master +#-------------------- - trigger: name: 'compass-os-nosdn-nofeature-ha-baremetal-master-trigger' triggers: - - timed: '0 2 * * *' + - timed: '0 20 * * *' - trigger: name: 'compass-os-nosdn-openo-ha-baremetal-master-trigger' triggers: @@ -447,9 +500,39 @@ - trigger: name: 'compass-os-odl-sfc-ha-baremetal-master-trigger' triggers: - - timed: '0 4 * * *' + - timed: '0 10 * * *' +#---------------------- +# noha-baremetal-master +#---------------------- +- trigger: + name: 'compass-os-nosdn-kvm-noha-baremetal-master-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-nosdn-nofeature-noha-baremetal-master-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl_l3-nofeature-noha-baremetal-master-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl_l2-moon-noha-baremetal-master-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl-sfc-noha-baremetal-master-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-nosdn-dpdk-noha-baremetal-master-trigger' + triggers: + - timed: '' +#-------------------- +# ha-baremetal-danube +#-------------------- - trigger: name: 'compass-os-nosdn-nofeature-ha-baremetal-danube-trigger' triggers: @@ -499,7 +582,37 @@ triggers: - timed: '' +#---------------------- +# noha-baremetal-danube +#---------------------- +- trigger: + name: 'compass-os-nosdn-kvm-noha-baremetal-danube-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-nosdn-nofeature-noha-baremetal-danube-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl_l3-nofeature-noha-baremetal-danube-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl_l2-moon-noha-baremetal-danube-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl-sfc-noha-baremetal-danube-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-nosdn-dpdk-noha-baremetal-danube-trigger' + triggers: + - timed: '' +#------------------ +# ha-virtual-master +#------------------ - trigger: name: 'compass-os-nosdn-nofeature-ha-virtual-master-trigger' triggers: @@ -531,11 +644,11 @@ - trigger: name: 'compass-os-odl_l2-moon-ha-virtual-master-trigger' triggers: - - timed: '0 22 * * *' + - timed: '0 12 * * *' - trigger: name: 'compass-os-nosdn-kvm-ha-virtual-master-trigger' triggers: - - timed: '0 23 * * *' + - timed: '0 13 * * *' - trigger: name: 'compass-os-nosdn-dpdk-ha-virtual-master-trigger' triggers: @@ -549,6 +662,37 @@ triggers: - timed: '0 16 * * *' +#-------------------- +# noha-virtual-master +#-------------------- +- trigger: + name: 'compass-os-nosdn-kvm-noha-virtual-master-trigger' + triggers: + - timed: '0 13 * * *' +- trigger: + name: 'compass-os-nosdn-nofeature-noha-virtual-master-trigger' + triggers: + - timed: '0 14 * * *' +- trigger: + name: 'compass-os-odl_l3-nofeature-noha-virtual-master-trigger' + triggers: + - timed: '0 15 * * *' +- trigger: + name: 'compass-os-odl_l2-moon-noha-virtual-master-trigger' + triggers: + - timed: '0 18 * * *' +- trigger: + name: 'compass-os-odl-sfc-noha-virtual-master-trigger' + triggers: + - timed: '0 20 * * *' +- trigger: + name: 'compass-os-nosdn-dpdk-noha-virtual-master-trigger' + triggers: + - timed: '0 11 * * *' + +#------------------ +# ha-virtual-danube +#------------------ - trigger: name: 'compass-os-nosdn-nofeature-ha-virtual-danube-trigger' triggers: @@ -597,3 +741,31 @@ name: 'compass-k8-nosdn-nofeature-ha-virtual-danube-trigger' triggers: - timed: '' + +#-------------------- +# noha-virtual-danube +#-------------------- +- trigger: + name: 'compass-os-nosdn-kvm-noha-virtual-danube-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-nosdn-nofeature-noha-virtual-danube-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl_l3-nofeature-noha-virtual-danube-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl_l2-moon-noha-virtual-danube-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-odl-sfc-noha-virtual-danube-trigger' + triggers: + - timed: '' +- trigger: + name: 'compass-os-nosdn-dpdk-noha-virtual-danube-trigger' + triggers: + - timed: '' diff --git a/jjb/compass4nfv/compass-deploy.sh b/jjb/compass4nfv/compass-deploy.sh index 7a5af5f5b..9d4ae5175 100644 --- a/jjb/compass4nfv/compass-deploy.sh +++ b/jjb/compass4nfv/compass-deploy.sh @@ -52,6 +52,11 @@ fi if [[ "$NODE_NAME" =~ "-virtual" ]]; then export NETWORK_CONF=$CONFDIR/vm_environment/$NODE_NAME/${NETWORK_CONF_FILE} export DHA_CONF=$CONFDIR/vm_environment/${DEPLOY_SCENARIO}.yml + if [[ "${DEPLOY_SCENARIO}" =~ "-moon-noha" ]]; then + export VIRT_NUMBER=3 + elif [[ "${DEPLOY_SCENARIO}" =~ "-noha" ]]; then + export VIRT_NUMBER=2 + fi else export INSTALL_NIC=eth1 export NETWORK_CONF=$CONFDIR/hardware_environment/$NODE_NAME/${NETWORK_CONF_FILE} diff --git a/jjb/dovetail/dovetail-run.sh b/jjb/dovetail/dovetail-run.sh index 7bbecc129..346a1ef08 100755 --- a/jjb/dovetail/dovetail-run.sh +++ b/jjb/dovetail/dovetail-run.sh @@ -160,6 +160,37 @@ if [ $(docker ps | grep "opnfv/dovetail:${DOCKER_TAG}" | wc -l) == 0 ]; then exit 1 fi +# Modify tempest_conf.yaml file +tempest_conf_file=${DOVETAIL_CONFIG}/tempest_conf.yaml +if [ ${INSTALLER_TYPE} == 'compass' ]; then + volume_device='vdb' +else + volume_device='vdc' +fi + +cat << EOF >$tempest_conf_file + +compute: + min_compute_nodes: 2 + volume_device_name: ${volume_device} + min_microversion: 2.2 + max_microversion: latest + +compute-feature-enabled: + live_migration: True + block_migration_for_live_migration: True + block_migrate_cinder_iscsi: True + attach_encrypted_volume: True + +EOF + +echo "${tempest_conf_file}..." +cat ${tempest_conf_file} + +cp_tempest_cmd="docker cp ${DOVETAIL_CONFIG}/tempest_conf.yaml $container_id:/home/opnfv/dovetail/dovetail/userconfig" +echo "exec command: ${cp_tempest_cmd}" +$cp_tempest_cmd + list_cmd="dovetail list ${TESTSUITE}" run_cmd="dovetail run --testsuite ${TESTSUITE} -d" echo "Container exec command: ${list_cmd}" diff --git a/jjb/releng/automate.yml b/jjb/releng/automate.yml index d12ee5d68..c6ca37fa9 100644 --- a/jjb/releng/automate.yml +++ b/jjb/releng/automate.yml @@ -237,16 +237,16 @@ name: 'testapi-automate-docker-deploy-macro' builders: - shell: | - bash ./jjb/releng/docker-deploy.sh "sudo docker run -dti -p 8082:8000 + sudo bash ./jjb/releng/docker-deploy.sh "sudo docker run -dti --name testapi -p 8082:8000 -e mongodb_url=mongodb://172.17.0.1:27017 -e base_url=http://testresults.opnfv.org/test opnfv/testapi" \ - "http://testresults.opnfv.org/test/swagger/APIs" "testapi" + "http://testresults.opnfv.org/test/" "testapi" - builder: name: 'reporting-automate-docker-deploy-macro' builders: - shell: | - bash ./jjb/releng/docker-deploy.sh "sudo docker run -itd -p 8084:8000 opnfv/reporting" \ + sudo bash ./jjb/releng/docker-deploy.sh "sudo docker run -itd --name reporting -p 8084:8000 opnfv/reporting" \ "http://testresults.opnfv.org/reporting2/reporting/index.html" "reporting" - builder: diff --git a/jjb/releng/docker-deploy.sh b/jjb/releng/docker-deploy.sh index 2a3e078ae..1e8357717 100644 --- a/jjb/releng/docker-deploy.sh +++ b/jjb/releng/docker-deploy.sh @@ -16,89 +16,137 @@ # specific language governing permissions and limitations * # under the License. * -# Assigning Variables + command=$1 url=$2 module=$3 -function check() { +REPO="opnfv" +latest_image=$REPO/$module:latest +old_image=$REPO/$module:old +latest_container_name=$module +old_container_name=$module"_old" +latest_container_id= +old_container_id= +new_start_container= + +function DEBUG() { + echo `date "+%Y-%m-%d %H:%M:%S.%N"` ": $1" +} - # Verify hosted +function check_connectivity() { + # check update status via test the connectivity of provide url sleep 5 cmd=`curl -s --head --request GET ${url} | grep '200 OK' > /dev/null` rc=$? - echo $rc - - if [[ $rc == 0 ]] - then + DEBUG $rc + if [[ $rc == 0 ]]; then return 0 else return 1 fi - } -echo "Getting contianer Id of the currently running one" -contId=$(sudo docker ps | grep "opnfv/${module}:latest" | awk '{print $1}') - -echo $contId -echo "Pulling the latest image" -sudo docker pull opnfv/${module}:latest +function pull_latest_image() { + DEBUG "pull latest image $latest_image" + docker pull $latest_image +} -echo "Deleting old containers of opnfv/${module}:old" -sudo docker ps -a | grep "opnfv/${module}" | grep "old" | awk '{print $1}' | xargs -r sudo docker rm -f +function get_latest_running_container() { + latest_container_id=`docker ps -q --filter name=^/$latest_container_name$` +} -echo "Deleting old images of opnfv/${module}:latest" -sudo docker images | grep "opnfv/${module}" | grep "old" | awk '{print $3}' | xargs -r sudo docker rmi -f +function get_old_running_container() { + old_container_id=`docker ps -q --filter name=^/$old_container_name$` +} +function delete_old_image() { + DEBUG "delete old image: $old_image" + docker rmi -f $old_image +} -if [[ -z "$contId" ]] -then - echo "No running ${module} container" +function delete_old_container() { + DEBUG "delete old container: $old_container_name" + docker ps -a -q --filter name=^/$old_container_name$ | xargs docker rm -f &>/dev/null +} - echo "Removing stopped ${module} containers in the previous iterations" - sudo docker ps -f status=exited | grep "opnfv_${module}" | awk '{print $1}' | xargs -r sudo docker rm -f -else - echo $contId +function delete_latest_container() { + DEBUG "delete latest container: $module" + docker ps -a -q --filter name=^/$latest_container_name$ | xargs docker rm -f &>/dev/null +} - echo "Get the image id of the currently running conatiner" - currImgId=$(sudo docker ps | grep "$contId" | awk '{print $2}') - echo $currImgId +function delete_latest_image() { + DEBUG "delete latest image: $REPO/$module:latest" + docker rmi -f $latest_image +} - if [[ -z "$currImgId" ]] - then - echo "No image id found for the container id" - exit 1 - fi +function change_image_tag_2_old() { + DEBUG "change image tag 2 old" + docker tag $latest_image $old_image + docker rmi -f $latest_image +} - echo "Changing current image tag to old" - sudo docker tag "$currImgId" opnfv/${module}:old +function mark_latest_container_2_old() { + DEBUG "mark latest container to be old" + docker rename "$latest_container_name" "$old_container_name" +} - echo "Removing stopped ${module} containers in the previous iteration" - sudo docker ps -f status=exited | grep "opnfv_${module}" | awk '{print $1}' | xargs -r sudo docker rm -f +function stop_old_container() { + DEBUG "stop old container" + docker stop "$old_container_name" +} - echo "Renaming the running container name to opnfv_${module} as to identify it." - sudo docker rename $contId opnfv_${module} +function run_latest_image() { + new_start_container=`$command` + DEBUG "run latest image: $new_start_container" +} - echo "Stop the currently running container" - sudo docker stop $contId +get_latest_running_container +get_old_running_container + +if [[ ! -z $latest_container_id ]]; then + DEBUG "latest container is running: $latest_container_id" + delete_old_container + delete_old_image + change_image_tag_2_old + mark_latest_container_2_old + pull_latest_image + stop_old_container + run_latest_image + +elif [[ ! -z $old_container_id ]]; then + DEBUG "old container is running: $old_container_id" + delete_latest_container + delete_latest_image + pull_latest_image + stop_old_container + run_latest_image +else + DEBUG "no container is running" + delete_old_container + delete_old_image + delete_latest_container + delete_latest_image + pull_latest_image + run_latest_image fi -echo "Running a container with the new image" -$command:latest - -if check; then - echo "TestResults Module Hosted." +if check_connectivity; then + DEBUG "CONGRATS: $module update successfully" else - echo "TestResults Module Failed" - if [[ $(sudo docker images | grep "opnfv/${module}" | grep "old" | awk '{print $3}') ]]; then - echo "Running old Image" - $command:old - exit 1 + DEBUG "ATTENTION: $module update failed" + id=`docker ps -a -q --filter name=^/$old_container_name$` + if [[ ! -z $id ]]; then + DEBUG "start old container instead" + docker stop $new_start_container + docker start $id + fi + if ! check_connectivity; then + DEBUG "BIG ISSUE: no container is running normally" fi + exit 1 fi -# Echo Images and Containers -sudo docker images -sudo docker ps -a +docker images +docker ps -a diff --git a/jjb/releng/opnfv-docker.yml b/jjb/releng/opnfv-docker.yml index 7e605b9cb..9d27329ed 100644 --- a/jjb/releng/opnfv-docker.yml +++ b/jjb/releng/opnfv-docker.yml @@ -70,6 +70,11 @@ dockerdir: 'docker/storperf-reporting' <<: *master <<: *other-receivers + - 'storperf-swaggerui': + project: 'storperf' + dockerdir: 'docker/storperf-swaggerui' + <<: *master + <<: *other-receivers - 'yardstick': project: 'yardstick' <<: *master diff --git a/jjb/xci/xci-verify-jobs.yml b/jjb/xci/xci-verify-jobs.yml index 6bc064253..8d1ee55a4 100644 --- a/jjb/xci/xci-verify-jobs.yml +++ b/jjb/xci/xci-verify-jobs.yml @@ -109,6 +109,10 @@ - label: name: SLAVE_LABEL default: 'xci-virtual-{distro}' + - string: + name: GIT_BASE + default: https://gerrit.opnfv.org/gerrit/$PROJECT + description: 'Git URL to use on this Jenkins Slave' builders: - description-setter: @@ -130,7 +134,7 @@ condition: SUCCESSFUL projects: - name: 'xci-verify-healthcheck-{type}-{stream}' - current-parameters: false + current-parameters: true predefined-parameters: | DISTRO={distro} DEPLOY_SCENARIO=os-nosdn-nofeature-noha @@ -162,7 +166,7 @@ parameters: - string: name: DISTRO - default: '{distro}' + default: 'xenial' - string: name: DEPLOY_SCENARIO default: 'os-nosdn-nofeature-noha' @@ -173,11 +177,15 @@ name: XCI_FLAVOR default: 'mini' - string: - name: XCI_DEVEL_ROOT - default: $WORKSPACE + name: OPNFV_RELENG_DEV_PATH + default: $WORKSPACE/ - string: name: ANSIBLE_VERBOSITY default: '-vvvv' + - string: + name: GIT_BASE + default: https://gerrit.opnfv.org/gerrit/$PROJECT + description: 'Git URL to use on this Jenkins Slave' wrappers: - ssh-agent-wrapper @@ -202,7 +210,11 @@ - shell: | #!/bin/bash - cd $WORKSPACE + # for some reason, the PATH is not set correctly + # setting PATH for ansible stuff + export PATH=/home/jenkins/.local/bin:$PATH + + cd $WORKSPACE/xci ./xci-deploy.sh - builder: diff --git a/utils/push-test-logs.sh b/utils/push-test-logs.sh index eb57deb7b..79190ec2f 100644 --- a/utils/push-test-logs.sh +++ b/utils/push-test-logs.sh @@ -31,6 +31,7 @@ node_list=(\ 'huawei-pod1' 'huawei-pod2' 'huawei-pod3' 'huawei-pod4' 'huawei-pod5' \ 'huawei-pod6' 'huawei-pod7' 'huawei-pod12' \ 'huawei-virtual1' 'huawei-virtual2' 'huawei-virtual3' 'huawei-virtual4' \ +'huawei-virtual5' 'huawei-virtual8' 'huawei-virtual9' \ 'zte-pod2' \ 'zte-virtual1') diff --git a/utils/test/reporting/api/handlers/landing.py b/utils/test/reporting/api/handlers/landing.py index 749916fb6..0bf602dc9 100644 --- a/utils/test/reporting/api/handlers/landing.py +++ b/utils/test/reporting/api/handlers/landing.py @@ -7,6 +7,7 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## import requests +import time from tornado.escape import json_encode from tornado.escape import json_decode @@ -24,7 +25,7 @@ class FiltersHandler(BaseHandler): 'status': ['success', 'warning', 'danger'], 'projects': ['functest', 'yardstick'], 'installers': ['apex', 'compass', 'fuel', 'joid'], - 'version': ['colorado', 'master'], + 'version': ['master', 'colorado', 'danube'], 'loops': ['daily', 'weekly', 'monthly'], 'time': ['10 days', '30 days'] } @@ -53,27 +54,27 @@ class ScenariosHandler(BaseHandler): def _get_scenario_result(self, scenario, data, args): result = { 'status': data.get('status'), - 'installers': self._get_installers_result(data['installers'], args) + 'installers': self._get_installers_result(data, args) } return result def _get_installers_result(self, data, args): func = self._get_installer_result - return {k: func(k, data.get(k, {}), args) for k in args['installers']} + return {k: func(data.get(k, {}), args) for k in args['installers']} - def _get_installer_result(self, installer, data, args): - projects = data.get(args['version'], []) - return [self._get_project_data(projects, p) for p in args['projects']] + def _get_installer_result(self, data, args): + return self._get_version_data(data.get(args['version'], {}), args) - def _get_project_data(self, projects, project): + def _get_version_data(self, data, args): + return {k: self._get_project_data(data.get(k, {})) + for k in args['projects']} + + def _get_project_data(self, data): atom = { - 'project': project, - 'score': None, - 'status': None + 'score': data.get('score', ''), + 'status': data.get('status', '') } - for p in projects: - if p['project'] == project: - return p + return atom def _get_scenarios(self): @@ -88,41 +89,42 @@ class ScenariosHandler(BaseHandler): []) ) for a in data} scenario = { - 'status': self._get_status(), - 'installers': installers + 'status': self._get_status() } + scenario.update(installers) + return scenario def _get_status(self): return 'success' def _get_installer(self, data): - return {a.get('version'): self._get_version(a) for a in data} + return {a.get('version'): self._get_version(a.get('projects')) + for a in data} def _get_version(self, data): + return {a.get('project'): self._get_project(a) for a in data} + + def _get_project(self, data): + scores = data.get('scores', []) + trusts = data.get('trust_indicators', []) + try: - scores = data.get('score', {}).get('projects')[0] - trusts = data.get('trust_indicator', {}).get('projects')[0] - except (TypeError, IndexError): - return [] - else: - scores = {key: [dict(date=a.get('date')[:10], - score=a.get('score') - ) for a in scores[key]] for key in scores} - trusts = {key: [dict(date=a.get('date')[:10], - status=a.get('status') - ) for a in trusts[key]] for key in trusts} - atom = self._get_atom(scores, trusts) - return [dict(project=k, - score=sorted(atom[k], reverse=True)[0].get('score'), - status=sorted(atom[k], reverse=True)[0].get('status') - ) for k in atom if atom[k]] - - def _get_atom(self, scores, trusts): - s = {k: {a['date']: a['score'] for a in scores[k]} for k in scores} - t = {k: {a['date']: a['status'] for a in trusts[k]} for k in trusts} - return {k: [dict(score=s[k][a], status=t[k][a], data=a - ) for a in s[k] if a in t[k]] for k in s} + date = sorted(scores, reverse=True)[0].get('date') + except IndexError: + data = time.time() + + try: + score = sorted(scores, reverse=True)[0].get('score') + except IndexError: + score = None + + try: + status = sorted(trusts, reverse=True)[0].get('status') + except IndexError: + status = None + + return {'date': date, 'score': score, 'status': status} def _change_to_utf8(self, obj): if isinstance(obj, dict): diff --git a/utils/test/reporting/pages/app/scripts/controllers/table.controller.js b/utils/test/reporting/pages/app/scripts/controllers/table.controller.js index 44d9441de..8d494c3ae 100644 --- a/utils/test/reporting/pages/app/scripts/controllers/table.controller.js +++ b/utils/test/reporting/pages/app/scripts/controllers/table.controller.js @@ -11,396 +11,268 @@ angular.module('opnfvApp') .controller('TableController', ['$scope', '$state', '$stateParams', '$http', 'TableFactory', '$timeout', function($scope, $state, $stateParams, $http, TableFactory, $timeout) { - $scope.filterlist = []; - $scope.selection = []; - $scope.statusList = []; - $scope.projectList = []; - $scope.installerList = []; - $scope.versionlist = []; - $scope.loopci = []; - $scope.time = []; - $scope.tableDataAll = {}; - $scope.tableInfoAll = {}; - $scope.scenario = {}; - // $scope.selectProjects = []; - - - $scope.VersionConfig = { - create: true, - valueField: 'title', - labelField: 'title', - delimiter: '|', - maxItems: 1, - placeholder: 'Version', - onChange: function(value) { - checkElementArrayValue($scope.selection, $scope.VersionOption); - $scope.selection.push(value); - // console.log($scope.selection); - getScenarioData(); + init(); - } - } + function init() { + $scope.filterlist = []; + $scope.selection = []; - $scope.LoopConfig = { - create: true, - valueField: 'title', - labelField: 'title', - delimiter: '|', - maxItems: 1, - placeholder: 'Loop', - onChange: function(value) { - checkElementArrayValue($scope.selection, $scope.LoopOption); - $scope.selection.push(value); - // console.log($scope.selection); - getScenarioData(); + $scope.statusList = []; + $scope.projectList = []; + $scope.installerList = []; + $scope.versionlist = []; + $scope.loopList = []; + $scope.timeList = []; - } - } + $scope.selectStatus = []; + $scope.selectProjects = []; + $scope.selectInstallers = []; + $scope.selectVersion = null; + $scope.selectLoop = null; + $scope.selectTime = null; + + $scope.statusClicked = false; + $scope.installerClicked = false; + $scope.projectClicked = false; - $scope.TimeConfig = { - create: true, - valueField: 'title', - labelField: 'title', - delimiter: '|', - maxItems: 1, - placeholder: 'Time', - onChange: function(value) { - checkElementArrayValue($scope.selection, $scope.TimeOption); - $scope.selection.push(value); - // console.log($scope.selection) - getScenarioData(); + $scope.scenarios = {}; + $scope.VersionConfig = { + create: true, + valueField: 'title', + labelField: 'title', + delimiter: '|', + maxItems: 1, + placeholder: 'Version', + onChange: function(value) { + $scope.selectVersion = value; + getScenarioData(); + + } } - } + $scope.LoopConfig = { + create: true, + valueField: 'title', + labelField: 'title', + delimiter: '|', + maxItems: 1, + placeholder: 'Loop', + onChange: function(value) { + $scope.selectLoop = value; - init(); + getScenarioData(); + + } + } + + $scope.TimeConfig = { + create: true, + valueField: 'title', + labelField: 'title', + delimiter: '|', + maxItems: 1, + placeholder: 'Time', + onChange: function(value) { + $scope.selectTime = value; + + getScenarioData(); + } + } - function init() { - $scope.toggleSelection = toggleSelection; - getScenarioData(); getFilters(); } function getFilters() { TableFactory.getFilter().get({ - }).$promise.then(function(response) { if (response != null) { $scope.statusList = response.filters.status; $scope.projectList = response.filters.projects; $scope.installerList = response.filters.installers; - $scope.versionlist = response.filters.version; - $scope.loopci = response.filters.loops; - $scope.time = response.filters.time; - - $scope.statusListString = $scope.statusList.toString(); - $scope.projectListString = $scope.projectList.toString(); - $scope.installerListString = $scope.installerList.toString(); - $scope.VersionSelected = $scope.versionlist[1]; - $scope.LoopCiSelected = $scope.loopci[0]; - $scope.TimeSelected = $scope.time[0]; - radioSetting($scope.versionlist, $scope.loopci, $scope.time); + $scope.versionList = toSelectList(response.filters.version); + $scope.loopList = toSelectList(response.filters.loops); + $scope.timeList = toSelectList(response.filters.time); + + $scope.selectStatus = copy($scope.statusList); + $scope.selectInstallers = copy($scope.installerList); + $scope.selectProjects = copy($scope.projectList); + $scope.selectVersion = response.filters.version[0]; + $scope.selectLoop = response.filters.loops[0]; + $scope.selectTime = response.filters.time[0]; + + getScenarioData(); } else { - alert("网络错误"); } - }) + }); + } + + function toSelectList(arr){ + var tempList = []; + angular.forEach(arr, function(ele){ + tempList.push({'title': ele}); + }); + return tempList; + } + + function copy(arr){ + var tempList = []; + angular.forEach(arr, function(ele){ + tempList.push(ele); + }); + return tempList; } function getScenarioData() { - // var utl = BASE_URL + '/scenarios'; var data = { - 'status': ['success', 'danger', 'warning'], - 'projects': ['functest', 'yardstick'], - 'installers': ['apex', 'compass', 'fuel', 'joid'], - 'version': $scope.VersionSelected, - 'loops': $scope.LoopCiSelected, - 'time': $scope.TimeSelected + 'status': $scope.selectStatus, + 'projects': $scope.selectProjects, + 'installers': $scope.selectInstallers, + 'version': $scope.selectVersion, + 'loops': $scope.selectLoop, + 'time': $scope.selectTime }; TableFactory.getScenario(data).then(function(response) { if (response.status == 200) { - $scope.scenario = response.data; - - reSettingcolspan(); + $scope.scenarios = response.data.scenarios; + getScenario(); } }, function(error) { - - }) + }); } - function reSettingcolspan() { - if ($scope.selectProjects == undefined || $scope.selectProjects == null) { - constructJson(); - $scope.colspan = $scope.tableDataAll.colspan; + function getScenario(){ - } else { - constructJson(); - $scope.colspan = $scope.tempColspan; - } - // console.log("test") - } - - //construct json - function constructJson(selectProject) { + $scope.project_row = []; + angular.forEach($scope.selectInstallers, function(installer){ + angular.forEach($scope.selectProjects, function(project){ + var temp = { + 'installer': installer, + 'project': project + } + $scope.project_row.push(temp); - var colspan; - var InstallerData; - var projectsInfo; - $scope.tableDataAll["scenario"] = []; + }); + }); - for (var item in $scope.scenario.scenarios) { + $scope.scenario_rows = []; + angular.forEach($scope.scenarios, function(scenario, name){ + var scenario_row = { + 'name': null, + 'status': null, + 'statusDisplay': null, + 'datadisplay': [], + }; + scenario_row.name = name; + scenario_row.status = scenario.status; - var headData = Object.keys($scope.scenario.scenarios[item].installers).sort(); - var scenarioStatus = $scope.scenario.scenarios[item].status; var scenarioStatusDisplay; - if (scenarioStatus == "success") { + if (scenario.status == "success") { scenarioStatusDisplay = "navy"; - } else if (scenarioStatus == "danger") { + } else if (scenario.status == "danger") { scenarioStatusDisplay = "danger"; - } else if (scenarioStatus == "warning") { + } else if (scenario.status == "warning") { scenarioStatusDisplay = "warning"; } - - InstallerData = headData; - var projectData = []; - var datadisplay = []; - var projects = []; - - for (var j = 0; j < headData.length; j++) { - - projectData.push($scope.scenario.scenarios[item].installers[headData[j]]); - } - for (var j = 0; j < projectData.length; j++) { - - for (var k = 0; k < projectData[j].length; k++) { - projects.push(projectData[j][k].project); - var temArray = []; - if (projectData[j][k].score == null) { - temArray.push("null"); - temArray.push(projectData[j][k].project); - temArray.push(headData[j]); - } else { - temArray.push(projectData[j][k].score); - temArray.push(projectData[j][k].project); - temArray.push(headData[j]); - } - - - if (projectData[j][k].status == "platinium") { - temArray.push("primary"); - temArray.push("P"); - } else if (projectData[j][k].status == "gold") { - temArray.push("danger"); - temArray.push("G"); - } else if (projectData[j][k].status == "silver") { - temArray.push("warning"); - temArray.push("S"); - } else if (projectData[j][k].status == null) { - temArray.push("null"); + scenario_row.statusDisplay = scenarioStatusDisplay; + + angular.forEach($scope.selectInstallers, function(installer){ + angular.forEach($scope.selectProjects, function(project){ + var datadisplay = { + 'installer': null, + 'project': null, + 'value': null, + 'label': null, + 'label_value': null + }; + datadisplay.installer = installer; + datadisplay.project = project; + datadisplay.value = scenario.installers[installer][project].score; + + var single_status = scenario.installers[installer][project].status; + if (single_status == "platinium") { + datadisplay.label = 'primary'; + datadisplay.label_value = 'P'; + } else if (single_status == "gold") { + datadisplay.label = 'danger'; + datadisplay.label_value = 'G'; + } else if (single_status == "silver") { + datadisplay.label = 'warning'; + datadisplay.label_value = 'S'; + } else if (single_status == null) { } + scenario_row.datadisplay.push(datadisplay); - datadisplay.push(temArray); - - } - - } - - colspan = projects.length / headData.length; - - var tabledata = { - scenarioName: item, - Installer: InstallerData, - projectData: projectData, - projects: projects, - datadisplay: datadisplay, - colspan: colspan, - status: scenarioStatus, - statusDisplay: scenarioStatusDisplay - }; - - JSON.stringify(tabledata); - $scope.tableDataAll.scenario.push(tabledata); - - - // console.log(tabledata); - - } - - - projectsInfo = $scope.tableDataAll.scenario[0].projects; - - var tempHeadData = []; - - for (var i = 0; i < InstallerData.length; i++) { - for (var j = 0; j < colspan; j++) { - tempHeadData.push(InstallerData[i]); - } - } - - //console.log(tempHeadData); - - var projectsInfoAll = []; - - for (var i = 0; i < projectsInfo.length; i++) { - var tempA = []; - tempA.push(projectsInfo[i]); - tempA.push(tempHeadData[i]); - projectsInfoAll.push(tempA); - - } - //console.log(projectsInfoAll); - - $scope.tableDataAll["colspan"] = colspan; - $scope.tableDataAll["Installer"] = InstallerData; - $scope.tableDataAll["Projects"] = projectsInfoAll; - - // console.log($scope.tableDataAll); - $scope.colspan = $scope.tableDataAll.colspan; - console.log($scope.tableDataAll); - - } - - //get json element size - function getSize(jsondata) { - var size = 0; - for (var item in jsondata) { - size++; - } - return size; + }); + }); + $scope.scenario_rows.push(scenario_row); + }); } - // console.log($scope.colspan); - - - //find all same element index - function getSameElementIndex(array, element) { - var indices = []; - var idx = array.indexOf(element); - while (idx != -1) { - indices.push(idx); - idx = array.indexOf(element, idx + 1); + function clickBase(eleList, ele){ + var idx = eleList.indexOf(ele); + if(idx > -1){ + eleList.splice(idx, 1); + }else{ + eleList.push(ele); } - //return indices; - var result = { element: element, index: indices }; - JSON.stringify(result); - return result; } - //delete element in array - function deletElement(array, index) { - array.splice(index, 1); + $scope.clickStatus = function(status){ + if($scope.selectStatus.length == $scope.statusList.length && $scope.statusClicked == false){ + $scope.selectStatus = []; + $scope.statusClicked = true; + } - } + clickBase($scope.selectStatus, status); - function radioSetting(array1, array2, array3) { - var tempVersion = []; - var tempLoop = []; - var tempTime = []; - for (var i = 0; i < array1.length; i++) { - var temp = { - title: array1[i] - }; - tempVersion.push(temp); - } - for (var i = 0; i < array2.length; i++) { - var temp = { - title: array2[i] - }; - tempLoop.push(temp); + if($scope.selectStatus.length == 0 && $scope.statusClicked == true){ + $scope.selectStatus = copy($scope.statusList); + $scope.statusClicked = false; } - for (var i = 0; i < array3.length; i++) { - var temp = { - title: array3[i] - }; - tempTime.push(temp); - } - $scope.VersionOption = tempVersion; - $scope.LoopOption = tempLoop; - $scope.TimeOption = tempTime; - } - //remove element in the array - function removeArrayValue(arr, value) { - for (var i = 0; i < arr.length; i++) { - if (arr[i] == value) { - arr.splice(i, 1); - break; - } - } + getScenarioData(); } - //check if exist element - function checkElementArrayValue(arrayA, arrayB) { - for (var i = 0; i < arrayB.length; i++) { - if (arrayA.indexOf(arrayB[i].title) > -1) { - removeArrayValue(arrayA, arrayB[i].title); - } + $scope.clickInstaller = function(installer){ + if($scope.selectInstallers.length == $scope.installerList.length && $scope.installerClicked == false){ + $scope.selectInstallers = []; + $scope.installerClicked = true; } - } - function toggleSelection(status) { - var idx = $scope.selection.indexOf(status); + clickBase($scope.selectInstallers, installer); - if (idx > -1) { - $scope.selection.splice(idx, 1); - filterData($scope.selection) - } else { - $scope.selection.push(status); - filterData($scope.selection) + if($scope.selectInstallers.length == 0 && $scope.installerClicked == true){ + $scope.selectInstallers = copy($scope.installerList); + $scope.installerClicked = false; } - // console.log($scope.selection); + getScenarioData(); } - //filter function - function filterData(selection) { - - $scope.selectInstallers = []; - $scope.selectProjects = []; - $scope.selectStatus = []; - for (var i = 0; i < selection.length; i++) { - if ($scope.statusListString.indexOf(selection[i]) > -1) { - $scope.selectStatus.push(selection[i]); - } - if ($scope.projectListString.indexOf(selection[i]) > -1) { - $scope.selectProjects.push(selection[i]); - } - if ($scope.installerListString.indexOf(selection[i]) > -1) { - $scope.selectInstallers.push(selection[i]); - } + $scope.clickProject = function(project){ + if($scope.selectProjects.length == $scope.projectList.length && $scope.projectClicked == false){ + $scope.selectProjects = []; + $scope.projectClicked = true; } + clickBase($scope.selectProjects, project); - // $scope.colspan = $scope.selectProjects.length; - //when some selection is empty, we set it full - if ($scope.selectInstallers.length == 0) { - $scope.selectInstallers = $scope.installerList; - - } - if ($scope.selectProjects.length == 0) { - $scope.selectProjects = $scope.projectList; - $scope.colspan = $scope.tableDataAll.colspan; - } else { - $scope.colspan = $scope.selectProjects.length; - $scope.tempColspan = $scope.colspan; - } - if ($scope.selectStatus.length == 0) { - $scope.selectStatus = $scope.statusList + if($scope.selectProjects.length == 0 && $scope.projectClicked == true){ + $scope.selectProjects = copy($scope.projectList); + $scope.projectClicked = false; } - // console.log($scope.selectStatus); - // console.log($scope.selectProjects); - + getScenarioData(); } - } - ]);
\ No newline at end of file + ]); diff --git a/utils/test/reporting/pages/app/scripts/factory/table.factory.js b/utils/test/reporting/pages/app/scripts/factory/table.factory.js index f0af34fb2..e715c5c28 100644 --- a/utils/test/reporting/pages/app/scripts/factory/table.factory.js +++ b/utils/test/reporting/pages/app/scripts/factory/table.factory.js @@ -28,7 +28,7 @@ angular.module('opnfvApp') } }); }, - getScenario: function() { + getScenario: function(data) { var config = { headers: { @@ -36,7 +36,7 @@ angular.module('opnfvApp') } } - return $http.post(BASE_URL + '/landing-page/scenarios', {}, config); + return $http.post(BASE_URL + '/landing-page/scenarios', data, config); }, diff --git a/utils/test/reporting/pages/app/views/commons/table.html b/utils/test/reporting/pages/app/views/commons/table.html index f504bd76b..a33c48312 100644 --- a/utils/test/reporting/pages/app/views/commons/table.html +++ b/utils/test/reporting/pages/app/views/commons/table.html @@ -29,9 +29,9 @@ <div class=" col-md-12" data-toggle="buttons" aria-pressed="false"> <label> Status </label> - <label class="btn btn-outline btn-success btn-sm" style="height:25px; margin-right: 5px;" ng-repeat="status in statusList" value={{status}} ng-checked="selection.indexOf(status)>-1" ng-click="toggleSelection(status)"> + <label class="btn btn-outline btn-success btn-sm" style="height:25px; margin-right: 5px;" ng-repeat="status in statusList" value={{status}} ng-checked="selectStatus.indexOf(status)>-1" ng-click="clickStatus(status)"> <input type="checkbox" disabled="disabled" > {{status}} - + </label> </div> @@ -39,7 +39,7 @@ <div class=" col-md-12" data-toggle="buttons"> <label> Projects </label> - <label class="btn btn-outline btn-success btn-sm " style="height:25px;margin-right: 5px;" ng-repeat="project in projectList" value={{project}} ng-checked="selection.indexOf(project)>-1" ng-click="toggleSelection(project)"> + <label class="btn btn-outline btn-success btn-sm " style="height:25px;margin-right: 5px;" ng-repeat="project in projectList" value={{project}} ng-checked="selectProjects.indexOf(project)>-1" ng-click="clickProject(project)"> <input type="checkbox" disabled="disabled"> {{project}} </label> @@ -47,7 +47,7 @@ <hr class="myhr"> <div class=" col-md-12" data-toggle="buttons"> <label> Installers </label> - <label class="btn btn-outline btn-success btn-sm" style="height:25px;margin-right: 5px;" ng-repeat="installer in installerList" value={{installer}} ng-checked="selection.indexOf(installer)>-1" ng-click="toggleSelection(installer)"> + <label class="btn btn-outline btn-success btn-sm" style="height:25px;margin-right: 5px;" ng-repeat="installer in installerList" value={{installer}} ng-checked="selectInstallers.indexOf(installer)>-1" ng-click="clickInstaller(installer)"> <input type="checkbox" disabled="disabled"> {{installer}} </label> </div> @@ -56,17 +56,17 @@ <div class=" col-md-1" style="margin-top:5px;margin-right: 5px;"> - <selectize options="VersionOption" ng-model="VersionSelected" config="VersionConfig"></selectize> + <selectize options="versionList" ng-model="selectVersion" config="VersionConfig"></selectize> </div> <div class=" col-md-1" style="margin-top:5px;margin-right: 5px;"> - <selectize options="LoopOption" ng-model="LoopCiSelected" config="LoopConfig"></selectize> + <selectize options="loopList" ng-model="selectLoop" config="LoopConfig"></selectize> </div> <div class=" col-md-1" style="margin-top:5px;margin-right: 5px;"> - <selectize options="TimeOption" ng-model="TimeSelected" config="TimeConfig"></selectize> + <selectize options="timeList" ng-model="selectTime" config="TimeConfig"></selectize> </div> </div> <div class="table-responsive"> @@ -75,25 +75,25 @@ <thead class="thead"> <tr> <th>Scenario </th> - <th colspan={{colspan}} ng-show="selectInstallers.indexOf(key)!=-1" value={{key}} ng-repeat="key in tableDataAll.Installer"><a href="notfound.html">{{key}}</a> </th> + <th colspan={{selectProjects.length}} ng-show="selectInstallers.indexOf(key)!=-1" value={{key}} ng-repeat="key in selectInstallers"><a href="notfound.html">{{key}}</a> </th> </tr> <tr> <td></td> - <td ng-show="selectProjects.indexOf(project[0])!=-1 && selectInstallers.indexOf(project[1])!=-1" ng-repeat="project in tableDataAll.Projects track by $index" data={{project[1]}} value={{project[0]}}>{{project[0]}}</td> + <td ng-show="selectProjects.indexOf(project.project)!=-1 && selectInstallers.indexOf(project.installer)!=-1" ng-repeat="project in project_row track by $index" data={{project.installer}} value={{project.project}}>{{ project.project }}</td> </tr> </thead> <tbody class="tbody"> - <tr ng-repeat="scenario in tableDataAll.scenario" ng-show="selectStatus.indexOf(scenario.status)!=-1"> + <tr ng-repeat="scenario in scenario_rows" ng-show="selectStatus.indexOf(scenario.status)!=-1"> - <td nowrap="nowrap" data={{scenario.status}}><span class="fa fa-circle text-{{scenario.statusDisplay}}"></span> <a href="notfound.html">{{scenario.scenarioName}}</a> </td> + <td nowrap="nowrap" data={{scenario.status}}><span class="fa fa-circle text-{{scenario.statusDisplay}}"></span> <a href="notfound.html">{{scenario.name}}</a> </td> <!--<td style="background-color:#e7eaec" align="justify" ng-if="data[0]=='Not Support'" ng-repeat="data in scenario.datadisplay track by $index" data={{data[1]}} value={{data[2]}}></td>--> - <td nowrap="nowrap" ng-show="selectInstallers.indexOf(data[2])!=-1 && selectProjects.indexOf(data[1])!=-1" ng-repeat="data in scenario.datadisplay track by $index" data={{data[1]}} value={{data[2]}} class={{data[0]}}> - <span class="label label-{{data[3]}}">{{data[4]}}</a></span> {{data[0]}}</td> + <td nowrap="nowrap" ng-show="selectInstallers.indexOf(data.installer)!=-1 && selectProjects.indexOf(data.project)!=-1" ng-repeat="data in scenario.datadisplay track by $index" data={{data.project}} value={{data.installer}} class={{data.value}}> + <span class="label label-{{data.label}}">{{data.label_value}}</a></span> {{data.value}}</td> </tr> @@ -110,4 +110,4 @@ </div> </div> -</section>
\ No newline at end of file +</section> diff --git a/utils/test/testapi/opnfv_testapi/common/raises.py b/utils/test/testapi/opnfv_testapi/common/raises.py index ec6b8a564..55c58c9e2 100644 --- a/utils/test/testapi/opnfv_testapi/common/raises.py +++ b/utils/test/testapi/opnfv_testapi/common/raises.py @@ -26,6 +26,10 @@ class Forbidden(Raiser): code = httplib.FORBIDDEN +class Conflict(Raiser): + code = httplib.CONFLICT + + class NotFound(Raiser): code = httplib.NOT_FOUND diff --git a/utils/test/testapi/opnfv_testapi/resources/handlers.py b/utils/test/testapi/opnfv_testapi/resources/handlers.py index 474a2033b..ed55c7028 100644 --- a/utils/test/testapi/opnfv_testapi/resources/handlers.py +++ b/utils/test/testapi/opnfv_testapi/resources/handlers.py @@ -50,7 +50,7 @@ class GenericApiHandler(web.RequestHandler): self.auth = self.settings["auth"] def prepare(self): - if self.request.method != "GET" and self.request.method != "DELETE": + if self.request.body: if self.request.headers.get("Content-Type") is not None: if self.request.headers["Content-Type"].startswith( DEFAULT_REPRESENTATION): @@ -110,22 +110,23 @@ class GenericApiHandler(web.RequestHandler): pipelines.append({'$match': query}) total_pages = 0 - if page > 0: - cursor = dbapi.db_list(self.table, query) - records_count = yield cursor.count() - total_pages, return_nr = self._calc_total_pages(records_count, - last, - page, - per_page) - pipelines = self._set_pipelines(pipelines, - sort, - return_nr, - page, - per_page) - cursor = dbapi.db_aggregate(self.table, pipelines) data = list() - while (yield cursor.fetch_next): - data.append(self.format_data(cursor.next_object())) + cursor = dbapi.db_list(self.table, query) + records_count = yield cursor.count() + if records_count > 0: + if page > 0: + total_pages, return_nr = self._calc_total_pages(records_count, + last, + page, + per_page) + pipelines = self._set_pipelines(pipelines, + sort, + return_nr, + page, + per_page) + cursor = dbapi.db_aggregate(self.table, pipelines) + while (yield cursor.fetch_next): + data.append(self.format_data(cursor.next_object())) if res_op is None: res = {self.table: data} else: @@ -188,6 +189,16 @@ class GenericApiHandler(web.RequestHandler): update_req['_id'] = str(data._id) self.finish_request(update_req) + @check.authenticate + @check.no_body + @check.not_exist + @check.updated_one_not_exist + def pure_update(self, data, query=None, **kwargs): + data = self.table_cls.from_dict(data) + update_req = self._update_requests(data) + yield dbapi.db_update(self.table, query, update_req) + self.finish_request() + def _update_requests(self, data): request = dict() for k, v in self.json_args.iteritems(): diff --git a/utils/test/testapi/opnfv_testapi/resources/models.py b/utils/test/testapi/opnfv_testapi/resources/models.py index e8fc532b7..6f04cc236 100644 --- a/utils/test/testapi/opnfv_testapi/resources/models.py +++ b/utils/test/testapi/opnfv_testapi/resources/models.py @@ -48,6 +48,29 @@ class ModelBase(object): return t + @classmethod + def from_dict_with_raise(cls, a_dict): + if a_dict is None: + return None + + attr_parser = cls.attr_parser() + t = cls() + for k, v in a_dict.iteritems(): + if k not in t.__dict__: + raise AttributeError( + '{} has no attribute {}'.format(cls.__name__, k)) + value = v + if isinstance(v, dict) and k in attr_parser: + value = attr_parser[k].from_dict(v) + elif isinstance(v, list) and k in attr_parser: + value = [] + for item in v: + value.append(attr_parser[k].from_dict(item)) + + t.__setattr__(k, value) + + return t + @staticmethod def attr_parser(): return {} diff --git a/utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py b/utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py index 66e8559f7..bd06400b4 100644 --- a/utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py +++ b/utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py @@ -1,5 +1,7 @@ import functools +from opnfv_testapi.common import message +from opnfv_testapi.common import raises from opnfv_testapi.resources import handlers import opnfv_testapi.resources.scenario_models as models from opnfv_testapi.tornado_swagger import swagger @@ -138,6 +140,13 @@ class ScenarioUpdater(object): updates = { ('scores', 'post'): self._update_requests_add_score, ('trust_indicators', 'post'): self._update_requests_add_ti, + ('customs', 'post'): self._update_requests_add_customs, + ('customs', 'put'): self._update_requests_update_customs, + ('customs', 'delete'): self._update_requests_delete_customs, + ('projects', 'post'): self._update_requests_add_projects, + ('projects', 'put'): self._update_requests_update_projects, + ('projects', 'delete'): self._update_requests_delete_projects, + ('owner', 'put'): self._update_requests_change_owner, } updates[(item, action)](self.data) @@ -178,6 +187,76 @@ class ScenarioUpdater(object): project.trust_indicators.append( models.ScenarioTI.from_dict(self.body)) + @iter_installers + @iter_versions + @iter_projects + def _update_requests_add_customs(self, project): + project.customs = list(set(project.customs + self.body)) + + @iter_installers + @iter_versions + @iter_projects + def _update_requests_update_customs(self, project): + project.customs = list(set(self.body)) + + @iter_installers + @iter_versions + @iter_projects + def _update_requests_delete_customs(self, project): + project.customs = filter( + lambda f: f not in self.body, + project.customs) + + @iter_installers + @iter_versions + def _update_requests_add_projects(self, version): + exists = list() + malformat = list() + for n in self.body: + try: + f_n = models.ScenarioProject.from_dict_with_raise(n) + if not any(o.project == f_n.project for o in version.projects): + version.projects.append(f_n) + else: + exists.append(n['project']) + except Exception as e: + malformat.append(e.message) + if malformat: + raises.BadRequest(message.bad_format(malformat)) + elif exists: + raises.Conflict(message.exist('projects', exists)) + + @iter_installers + @iter_versions + def _update_requests_update_projects(self, version): + exists = list() + malformat = list() + projects = list() + for n in self.body: + try: + f_n = models.ScenarioProject.from_dict_with_raise(n) + if not any(o.project == f_n.project for o in projects): + projects.append(models.ScenarioProject.from_dict(n)) + else: + exists.append(n['project']) + except: + malformat.append(n) + if malformat: + raises.BadRequest(message.bad_format(malformat)) + elif exists: + raises.Forbidden(message.exist('projects', exists)) + version.projects = projects + + @iter_installers + @iter_versions + def _update_requests_delete_projects(self, version): + version.projects = self._remove_projects(version.projects) + + @iter_installers + @iter_versions + def _update_requests_change_owner(self, version): + version.owner = self.body + def _filter_installers(self, installers): return self._filter('installer', installers) @@ -187,11 +266,19 @@ class ScenarioUpdater(object): def _filter_projects(self, projects): return self._filter('project', projects) + def _remove_projects(self, projects): + return self._remove('project', projects) + def _filter(self, item, items): return filter( lambda f: getattr(f, item) == getattr(self, item), items) + def _remove(self, field, fields): + return filter( + lambda f: getattr(f, field) not in self.body, + fields) + class GenericScenarioUpdateHandler(GenericScenarioHandler): def __init__(self, application, request, **kwargs): @@ -204,15 +291,15 @@ class GenericScenarioUpdateHandler(GenericScenarioHandler): self.item = None self.action = None - def do_post(self, scenario, item, action, locators): + def do_update(self, item, action, locators): self.item = item self.action = action - for k in locators.keys(): - v = self.get_query_argument(k) - setattr(self, k, v) - locators[k] = v - db_keys = ['name'] - self._update(query=self.set_query(locators=locators), db_keys=db_keys) + for k, v in locators.iteritems(): + if not v: + v = self.get_query_argument(k) + setattr(self, k, v) + locators[k] = v + self.pure_update(query=self.set_query(locators=locators)) def _update_requests(self, data): return ScenarioUpdater(data, @@ -247,16 +334,15 @@ class ScenarioScoresHandler(GenericScenarioUpdateHandler): @type project: L{string} @in project: query @required project: True - @rtype: L{Scenario} @return 200: score is created. @raise 404: scenario/installer/version/project not existed """ - self.do_post(scenario, - 'scores', - 'post', - locators={'installer': None, - 'version': None, - 'project': None}) + self.do_update('scores', + 'post', + locators={'scenario': scenario, + 'installer': None, + 'version': None, + 'project': None}) class ScenarioTIsHandler(GenericScenarioUpdateHandler): @@ -284,13 +370,235 @@ class ScenarioTIsHandler(GenericScenarioUpdateHandler): @type project: L{string} @in project: query @required project: True - @rtype: L{Scenario} @return 200: trust indicator is added. @raise 404: scenario/installer/version/project not existed """ - self.do_post(scenario, - 'trust_indicators', - 'post', - locators={'installer': None, - 'version': None, - 'project': None}) + self.do_update('trust_indicators', + 'post', + locators={'scenario': scenario, + 'installer': None, + 'version': None, + 'project': None}) + + +class ScenarioCustomsHandler(GenericScenarioUpdateHandler): + @swagger.operation(nickname="addCustomizedTestCases") + def post(self, scenario): + """ + @description: add customized test cases + @notes: add several test cases to a project + POST /api/v1/scenarios/<scenario_name>/customs? \ + installer=<installer_name>& \ + version=<version_name>& \ + project=<project_name> + @param body: test cases to be added + @type body: C{list} of L{string} + @in body: body + @param installer: installer type + @type installer: L{string} + @in installer: query + @required installer: True + @param version: version + @type version: L{string} + @in version: query + @required version: True + @param project: project name + @type project: L{string} + @in project: query + @required project: True + @return 200: test cases are added. + @raise 404: scenario/installer/version/project not existed + """ + self.do_update('customs', + 'post', + locators={'scenario': scenario, + 'installer': None, + 'version': None, + 'project': None}) + + @swagger.operation(nickname="updateCustomizedTestCases") + def put(self, scenario): + """ + @description: update customized test cases + @notes: substitute all the customized test cases + PUT /api/v1/scenarios/<scenario_name>/customs? \ + installer=<installer_name>& \ + version=<version_name>& \ + project=<project_name> + @param body: new supported test cases + @type body: C{list} of L{string} + @in body: body + @param installer: installer type + @type installer: L{string} + @in installer: query + @required installer: True + @param version: version + @type version: L{string} + @in version: query + @required version: True + @param project: project name + @type project: L{string} + @in project: query + @required project: True + @return 200: substitute test cases success. + @raise 404: scenario/installer/version/project not existed + """ + self.do_update('customs', + 'put', + locators={'scenario': scenario, + 'installer': None, + 'version': None, + 'project': None}) + + @swagger.operation(nickname="deleteCustomizedTestCases") + def delete(self, scenario): + """ + @description: delete one or several customized test cases + @notes: delete one or some customized test cases + DELETE /api/v1/scenarios/<scenario_name>/customs? \ + installer=<installer_name>& \ + version=<version_name>& \ + project=<project_name> + @param body: test case(s) to be deleted + @type body: C{list} of L{string} + @in body: body + @param installer: installer type + @type installer: L{string} + @in installer: query + @required installer: True + @param version: version + @type version: L{string} + @in version: query + @required version: True + @param project: project name + @type project: L{string} + @in project: query + @required project: True + @return 200: delete test case(s) success. + @raise 404: scenario/installer/version/project not existed + """ + self.do_update('customs', + 'delete', + locators={'scenario': scenario, + 'installer': None, + 'version': None, + 'project': None}) + + +class ScenarioProjectsHandler(GenericScenarioUpdateHandler): + @swagger.operation(nickname="addProjectsUnderScenario") + def post(self, scenario): + """ + @description: add projects to scenario + @notes: add one or multiple projects + POST /api/v1/scenarios/<scenario_name>/projects? \ + installer=<installer_name>& \ + version=<version_name> + @param body: projects to be added + @type body: C{list} of L{ScenarioProject} + @in body: body + @param installer: installer type + @type installer: L{string} + @in installer: query + @required installer: True + @param version: version + @type version: L{string} + @in version: query + @required version: True + @return 200: projects are added. + @raise 400: bad schema + @raise 409: conflict, project already exists + @raise 404: scenario/installer/version not existed + """ + self.do_update('projects', + 'post', + locators={'scenario': scenario, + 'installer': None, + 'version': None}) + + @swagger.operation(nickname="updateScenarioProjects") + def put(self, scenario): + """ + @description: replace all projects + @notes: substitute all projects, delete existed ones with new provides + PUT /api/v1/scenarios/<scenario_name>/projects? \ + installer=<installer_name>& \ + version=<version_name> + @param body: new projects + @type body: C{list} of L{ScenarioProject} + @in body: body + @param installer: installer type + @type installer: L{string} + @in installer: query + @required installer: True + @param version: version + @type version: L{string} + @in version: query + @required version: True + @return 200: replace projects success. + @raise 400: bad schema + @raise 404: scenario/installer/version not existed + """ + self.do_update('projects', + 'put', + locators={'scenario': scenario, + 'installer': None, + 'version': None}) + + @swagger.operation(nickname="deleteProjectsUnderScenario") + def delete(self, scenario): + """ + @description: delete one or multiple projects + @notes: delete one or multiple projects + DELETE /api/v1/scenarios/<scenario_name>/projects? \ + installer=<installer_name>& \ + version=<version_name> + @param body: projects(names) to be deleted + @type body: C{list} of L{string} + @in body: body + @param installer: installer type + @type installer: L{string} + @in installer: query + @required installer: True + @param version: version + @type version: L{string} + @in version: query + @required version: True + @return 200: delete project(s) success. + @raise 404: scenario/installer/version not existed + """ + self.do_update('projects', + 'delete', + locators={'scenario': scenario, + 'installer': None, + 'version': None}) + + +class ScenarioOwnerHandler(GenericScenarioUpdateHandler): + @swagger.operation(nickname="changeScenarioOwner") + def put(self, scenario): + """ + @description: change scenario owner + @notes: substitute all projects, delete existed ones with new provides + PUT /api/v1/scenarios/<scenario_name>/owner? \ + installer=<installer_name>& \ + version=<version_name> + @param body: new owner + @type body: L{string} + @in body: body + @param installer: installer type + @type installer: L{string} + @in installer: query + @required installer: True + @param version: version + @type version: L{string} + @in version: query + @required version: True + @return 200: change owner success. + @raise 404: scenario/installer/version not existed + """ + self.do_update('owner', + 'put', + locators={'scenario': scenario, + 'installer': None, + 'version': None}) diff --git a/utils/test/testapi/opnfv_testapi/resources/scenario_models.py b/utils/test/testapi/opnfv_testapi/resources/scenario_models.py index 9f5a07434..7d0770759 100644 --- a/utils/test/testapi/opnfv_testapi/resources/scenario_models.py +++ b/utils/test/testapi/opnfv_testapi/resources/scenario_models.py @@ -74,7 +74,8 @@ class ScenarioVersion(models.ModelBase): @property projects: @ptype projects: C{list} of L{ScenarioProject} """ - def __init__(self, version=None, projects=None): + def __init__(self, owner=None, version=None, projects=None): + self.owner = owner self.version = version self.projects = list_default(projects) @@ -83,7 +84,9 @@ class ScenarioVersion(models.ModelBase): return {'projects': ScenarioProject} def __eq__(self, other): - return [self.version == other.version and self._projects_eq(other)] + return [self.version == other.version and + self.owner == other.owner and + self._projects_eq(other)] def __ne__(self, other): return not self.__eq__(other) diff --git a/utils/test/testapi/opnfv_testapi/router/url_mappings.py b/utils/test/testapi/opnfv_testapi/router/url_mappings.py index 458942576..9c9556c6b 100644 --- a/utils/test/testapi/opnfv_testapi/router/url_mappings.py +++ b/utils/test/testapi/opnfv_testapi/router/url_mappings.py @@ -58,6 +58,12 @@ mappings = [ scenario_handlers.ScenarioScoresHandler), (r"/api/v1/scenarios/([^/]+)/trust_indicators", scenario_handlers.ScenarioTIsHandler), + (r"/api/v1/scenarios/([^/]+)/customs", + scenario_handlers.ScenarioCustomsHandler), + (r"/api/v1/scenarios/([^/]+)/projects", + scenario_handlers.ScenarioProjectsHandler), + (r"/api/v1/scenarios/([^/]+)/owner", + scenario_handlers.ScenarioOwnerHandler), # static path (r'/(.*\.(css|png|gif|js|html|json|map|woff2|woff|ttf))', diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_base.py b/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_base.py index aa6b83544..77a8d18c1 100644 --- a/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_base.py +++ b/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_base.py @@ -92,21 +92,35 @@ class TestBase(testing.AsyncHTTPTestCase): headers=self.headers) return self._get_return(res, self.list_res) - def update(self, new=None, *args): - if new: + def update_direct_url(self, url, new=None): + if new and hasattr(new, 'format'): new = new.format() - res = self.fetch(self._get_uri(*args), + res = self.fetch(url, method='PUT', body=json.dumps(new), headers=self.headers) return self._get_return(res, self.update_res) - def delete(self, *args): - res = self.fetch(self._get_uri(*args), - method='DELETE', - headers=self.headers) + def update(self, new=None, *args): + return self.update_direct_url(self._get_uri(*args), new) + + def delete_direct_url(self, url, body): + if body: + res = self.fetch(url, + method='DELETE', + body=json.dumps(body), + headers=self.headers, + allow_nonstandard_methods=True) + else: + res = self.fetch(url, + method='DELETE', + headers=self.headers) + return res.code, res.body + def delete(self, *args): + return self.delete_direct_url(self._get_uri(*args), None) + @staticmethod def _get_valid_args(*args): new_args = tuple(['%s' % arg for arg in args if arg is not None]) @@ -132,7 +146,10 @@ class TestBase(testing.AsyncHTTPTestCase): def _get_return(self, res, cls): code = res.code body = res.body - return code, self._get_return_body(code, body, cls) + if body: + return code, self._get_return_body(code, body, cls) + else: + return code, None @staticmethod def _get_return_body(code, body, cls): diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_scenario.py b/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_scenario.py index 0558ea30d..466caaf13 100644 --- a/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_scenario.py +++ b/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_scenario.py @@ -168,15 +168,30 @@ class TestScenarioUpdate(TestScenarioBase): self.version, 'functest') + def update_url_fixture(item): + def _update_url_fixture(xstep): + def wrapper(self, *args, **kwargs): + locator = None + if item in ['projects', 'owner']: + locator = 'installer={}&version={}'.format( + self.installer, + self.version) + self.update_url = '{}/{}?{}'.format(self.scenario_url, + item, + locator) + xstep(self, *args, **kwargs) + return wrapper + return _update_url_fixture + def update_partial(operate, expected): - def _update(set_update): + def _update_partial(set_update): @functools.wraps(set_update) - def wrap(self): + def wrapper(self): update, scenario = set_update(self, deepcopy(self.req_d)) code, body = getattr(self, operate)(update, self.scenario) getattr(self, expected)(code, scenario) - return wrap - return _update + return wrapper + return _update_partial @update_partial('_add', '_success') def test_addScore(self, scenario): @@ -200,9 +215,101 @@ class TestScenarioUpdate(TestScenarioBase): return add, scenario + @update_partial('_add', '_success') + def test_addCustoms(self, scenario): + add = ['odl', 'parser', 'vping_ssh'] + projects = scenario['installers'][0]['versions'][0]['projects'] + functest = filter(lambda f: f['project'] == 'functest', projects)[0] + functest['customs'] = list(set(functest['customs'] + add)) + self.update_url = '{}/customs?{}'.format(self.scenario_url, + self.locate_project) + return add, scenario + + @update_partial('_update', '_success') + def test_updateCustoms(self, scenario): + news = ['odl', 'parser', 'vping_ssh'] + projects = scenario['installers'][0]['versions'][0]['projects'] + functest = filter(lambda f: f['project'] == 'functest', projects)[0] + functest['customs'] = news + self.update_url = '{}/customs?{}'.format(self.scenario_url, + self.locate_project) + + return news, scenario + + @update_partial('_delete', '_success') + def test_deleteCustoms(self, scenario): + obsoletes = ['vping_ssh'] + projects = scenario['installers'][0]['versions'][0]['projects'] + functest = filter(lambda f: f['project'] == 'functest', projects)[0] + functest['customs'] = ['healthcheck'] + self.update_url = '{}/customs?{}'.format(self.scenario_url, + self.locate_project) + + return obsoletes, scenario + + @update_url_fixture('projects') + @update_partial('_add', '_success') + def test_addProjects_succ(self, scenario): + add = models.ScenarioProject(project='qtip').format() + scenario['installers'][0]['versions'][0]['projects'].append(add) + return [add], scenario + + @update_url_fixture('projects') + @update_partial('_add', '_conflict') + def test_addProjects_already_exist(self, scenario): + add = models.ScenarioProject(project='functest').format() + scenario['installers'][0]['versions'][0]['projects'].append(add) + return [add], scenario + + @update_url_fixture('projects') + @update_partial('_add', '_bad_request') + def test_addProjects_bad_schema(self, scenario): + add = models.ScenarioProject(project='functest').format() + add['score'] = None + scenario['installers'][0]['versions'][0]['projects'].append(add) + return [add], scenario + + @update_url_fixture('projects') + @update_partial('_update', '_success') + def test_updateProjects_succ(self, scenario): + update = models.ScenarioProject(project='qtip').format() + scenario['installers'][0]['versions'][0]['projects'] = [update] + return [update], scenario + + @update_url_fixture('projects') + @update_partial('_update', '_bad_request') + def test_updateProjects_bad_schema(self, scenario): + update = models.ScenarioProject(project='functest').format() + update['score'] = None + scenario['installers'][0]['versions'][0]['projects'] = [update] + return [update], scenario + + @update_url_fixture('projects') + @update_partial('_delete', '_success') + def test_deleteProjects(self, scenario): + deletes = ['functest'] + projects = scenario['installers'][0]['versions'][0]['projects'] + scenario['installers'][0]['versions'][0]['projects'] = filter( + lambda f: f['project'] != 'functest', + projects) + return deletes, scenario + + @update_url_fixture('owner') + @update_partial('_update', '_success') + def test_changeOwner(self, scenario): + new_owner = 'new_owner' + scenario['installers'][0]['versions'][0]['owner'] = 'www' + return new_owner, scenario + def _add(self, update_req, new_scenario): return self.post_direct_url(self.update_url, update_req) + def _update(self, update_req, new_scenario): + return self.update_direct_url(self.update_url, update_req) + + def _delete(self, update_req, new_scenario): + return self.delete_direct_url(self.update_url, update_req) + def _success(self, status, new_scenario): self.assertEqual(status, httplib.OK) self._get_and_assert(new_scenario.get('name'), new_scenario) @@ -212,3 +319,6 @@ class TestScenarioUpdate(TestScenarioBase): def _bad_request(self, status, new_scenario): self.assertEqual(status, httplib.BAD_REQUEST) + + def _conflict(self, status, new_scenario): + self.assertEqual(status, httplib.CONFLICT) diff --git a/utils/test/testapi/opnfv_testapi/tornado_swagger/swagger.py b/utils/test/testapi/opnfv_testapi/tornado_swagger/swagger.py index 83f389a6b..6125c9554 100644 --- a/utils/test/testapi/opnfv_testapi/tornado_swagger/swagger.py +++ b/utils/test/testapi/opnfv_testapi/tornado_swagger/swagger.py @@ -94,11 +94,18 @@ class DocParser(object): def _parse_type(self, **kwargs): arg = kwargs.get('arg', None) - body = self._get_body(**kwargs) - self.params.setdefault(arg, {}).update({ - 'name': arg, - 'dataType': body - }) + code = self._parse_epytext_para('code', **kwargs) + link = self._parse_epytext_para('link', **kwargs) + if code is None: + self.params.setdefault(arg, {}).update({ + 'name': arg, + 'type': link + }) + elif code == 'list': + self.params.setdefault(arg, {}).update({ + 'type': 'array', + 'items': {'type': link} + }) def _parse_in(self, **kwargs): arg = kwargs.get('arg', None) |