From 3815c917a31398425800001aa79e15114685d01d Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Fri, 10 May 2024 17:28:05 -0700 Subject: [PATCH] Retire Sahara: remove repo content Sahara project is retiring - https://review.opendev.org/c/openstack/governance/+/919374 this commit remove the content of this project repo Depends-On: https://review.opendev.org/c/openstack/project-config/+/919376 Change-Id: I0995b857b4277b95946c5457180cdcfa9c3c7ec2 --- .coveragerc | 13 - .gitignore | 27 - .stestr.conf | 3 - .zuul.yaml | 325 ------- CONTRIBUTING.rst | 19 - HACKING.rst | 53 - LICENSE | 175 ---- README.rst | 44 +- doc/requirements.txt | 7 - doc/source/conf.py | 239 ----- doc/source/contributor/contributing.rst | 14 - doc/source/index.rst | 52 - doc/source/releasenotes.rst | 11 - doc/source/scenario.rst | 792 --------------- doc/source/tempest-plugin.rst | 76 -- etc/scenario/gate/README.rst | 0 etc/scenario/gate/credentials.yaml.mako | 3 - etc/scenario/gate/credentials_s3.yaml.mako | 6 - etc/scenario/gate/edp.yaml.mako | 43 - etc/scenario/gate/edp_s3.yaml.mako | 25 - etc/scenario/gate/fake.yaml.mako | 35 - etc/scenario/gate/spark-1.6.0.yaml.mako | 41 - etc/scenario/simple-testcase.yaml | 89 -- playbooks/sahara-tests-scenario.yaml | 19 - ...y-to-use-clouds-yaml-5f66a2c3959f894b.yaml | 7 - .../notes/add-cdh-513-c4feaf4f360122ff.yaml | 3 - .../add-cli-for-ng-cbdcdddd5195c333.yaml | 6 - .../notes/drop-py-2-7-188a5c85a436ee4f.yaml | 7 - .../fix-bug-1644747-5f9790ad2a0e5e56.yaml | 7 - ...datasource-discovery-b690920189f0b161.yaml | 6 - ...d-resource-discovery-36ffa157cf9b06b9.yaml | 6 - ...tiple-plugin-version-13ed695ad2d8fab1.yaml | 6 - ...rm-scenario-template-2d28644c9fd2ec06.yaml | 4 - .../force-pem-ssh-keys-2cc9eb30a76c8dd1.yaml | 6 - ...tempest-client-tests-9042d5d50bbdce9e.yaml | 9 - ...ease-cluster-timeout-0e97fb7d7a6ea75a.yaml | 4 - ...rate-to-keystoneauth-c65c162d74b5d7b9.yaml | 3 - ...ted-sahara-cli-tests-a2b32eeb6ec11512.yaml | 3 - .../more-auth-fixes-9ba23ceb54ba042a.yaml | 5 - ...w-cli-tests-job-type-4fdc68dfda728cd3.yaml | 6 - ...ages_to_glanceclient-9f8351330147f770.yaml | 3 - .../post-030-preocata-40c5fbf4d3522074.yaml | 35 - .../remove-nova-network-09a2afd1efd83e05.yaml | 6 - ...emove-old-yaml-files-97ecb4a11582f033.yaml | 6 - ...pest-tests-as-plugin-25d63ac04902f1ad.yaml | 4 - ...-scenario-fix-runner-59350d608d19caf9.yaml | 5 - ...-tempest-tests-apiv2-c9878e0ba37fba2a.yaml | 10 - ...saharaclient-version-75ca47761f133743.yaml | 10 - ...rio-boot-from-volume-a87c680b03f560a0.yaml | 5 - ...cenario-feature-sets-ac3e0e9e40b236bb.yaml | 11 - ...unner-templatesdebug-5ee5e2447cff770a.yaml | 4 - .../notes/scenario-s3-20d41440d84af3a9.yaml | 8 - ...scenario-tests-apiv2-c71c72f51c667075.yaml | 8 - ...fy-testrunner-config-d3a19014db107ff1.yaml | 7 - .../start-using-reno-ef5696b99f56dc5e.yaml | 3 - ...port-non-fake-plugin-a4ba718b787246e8.yaml | 8 - .../switch-to-stestr-6de59dc9e46768f6.yaml | 4 - .../tempest-basic-s3-636953aa1bb198d7.yaml | 6 - ...ove-scenario-manager-4e9ea9dbf585b8cf.yaml | 7 - ...test-templates-ocata-dcc438604c94b72f.yaml | 15 - .../test-templates-pike-4e3e78362f49cb6e.yaml | 12 - ...est-templates-queens-901f1af3cf80f98d.yaml | 13 - ...test-templates-rocky-c271d2acb0d3c42a.yaml | 16 - ...test-templates-stein-751a4344bfcca652.yaml | 11 - ...th-no-public-network-5b7df696fbbd4386.yaml | 5 - ...ack-steps-timestamps-56c40d06c49dc88e.yaml | 4 - ...e-plugin-version-def-96979000c20b2db8.yaml | 6 - ...test-templates-ocata-3707688601c11f21.yaml | 7 - ...est-stable-interface-aefdfd8f81361dc8.yaml | 8 - releasenotes/source/_static/.placeholder | 0 releasenotes/source/_templates/.placeholder | 0 releasenotes/source/conf.py | 253 ----- releasenotes/source/index.rst | 9 - releasenotes/source/unreleased.rst | 5 - requirements.txt | 27 - roles/run-sahara-scenario/defaults/main.yaml | 8 - roles/run-sahara-scenario/tasks/main.yaml | 24 - .../defaults/main.yaml | 23 - .../setup-sahara-scenario-env/tasks/main.yaml | 52 - .../tasks/setup_s3.yaml | 29 - .../templates/sahara_scenario_conf.ini.j2 | 16 - sahara_tempest_plugin/README.rst | 5 - sahara_tempest_plugin/__init__.py | 0 sahara_tempest_plugin/clients.py | 30 - sahara_tempest_plugin/common/__init__.py | 0 sahara_tempest_plugin/common/plugin_utils.py | 351 ------- sahara_tempest_plugin/config.py | 84 -- sahara_tempest_plugin/plugin.py | 73 -- sahara_tempest_plugin/services/__init__.py | 0 .../services/data_processing/__init__.py | 0 .../services/data_processing/base_client.py | 50 - .../services/data_processing/v1_1/__init__.py | 20 - .../v1_1/data_processing_client.py | 270 ------ .../services/data_processing/v2/__init__.py | 20 - .../v2/data_processing_client.py | 241 ----- sahara_tempest_plugin/tests/__init__.py | 0 sahara_tempest_plugin/tests/api/__init__.py | 0 sahara_tempest_plugin/tests/api/base.py | 260 ----- .../tests/api/test_cluster_templates.py | 162 ---- .../tests/api/test_data_sources.py | 254 ----- .../tests/api/test_job_binaries.py | 231 ----- .../tests/api/test_job_binary_internals.py | 109 --- .../tests/api/test_job_templates.py | 113 --- sahara_tempest_plugin/tests/api/test_jobs.py | 111 --- .../tests/api/test_node_group_templates.py | 121 --- .../tests/api/test_plugins.py | 59 -- sahara_tempest_plugin/tests/cli/README.rst | 5 - sahara_tempest_plugin/tests/cli/__init__.py | 0 sahara_tempest_plugin/tests/cli/base.py | 209 ---- .../tests/cli/cluster_templates.py | 55 -- sahara_tempest_plugin/tests/cli/clusters.py | 97 -- .../tests/cli/data_sources.py | 55 -- sahara_tempest_plugin/tests/cli/images.py | 89 -- .../tests/cli/job_binaries.py | 133 --- .../tests/cli/job_templates.py | 55 -- sahara_tempest_plugin/tests/cli/job_types.py | 51 - sahara_tempest_plugin/tests/cli/jobs.py | 69 -- .../tests/cli/node_group_templates.py | 118 --- sahara_tempest_plugin/tests/cli/plugins.py | 95 -- .../tests/cli/test_scenario.py | 223 ----- .../tests/clients/__init__.py | 0 sahara_tempest_plugin/tests/clients/base.py | 331 ------- .../tests/clients/test_cluster_templates.py | 85 -- .../tests/clients/test_data_sources.py | 92 -- .../tests/clients/test_job_binaries.py | 141 --- .../clients/test_job_binary_internals.py | 72 -- .../tests/clients/test_job_executions.py | 320 ------ .../tests/clients/test_jobs.py | 98 -- .../clients/test_node_group_templates.py | 82 -- .../tests/clients/test_plugins.py | 46 - sahara_tests/__init__.py | 0 sahara_tests/scenario/README.rst | 2 - sahara_tests/scenario/__init__.py | 0 sahara_tests/scenario/base.py | 909 ------------------ sahara_tests/scenario/clients.py | 451 --------- .../scenario/custom_checks/__init__.py | 0 .../scenario/custom_checks/check_cinder.py | 18 - .../scenario/custom_checks/check_kafka.py | 148 --- .../scenario/custom_checks/check_run_jobs.py | 18 - .../scenario/custom_checks/check_scale.py | 18 - .../scenario/custom_checks/check_sentry.py | 41 - .../scenario/custom_checks/check_transient.py | 18 - sahara_tests/scenario/defaults/README.rst | 29 - .../scenario/defaults/ambari-2.3.yaml.mako | 69 -- .../scenario/defaults/ambari-2.4.yaml.mako | 72 -- .../scenario/defaults/ambari-2.5.yaml.mako | 72 -- .../scenario/defaults/ambari-2.6.yaml.mako | 72 -- .../scenario/defaults/cdh-5.11.0.yaml.mako | 97 -- .../scenario/defaults/cdh-5.13.0.yaml.mako | 97 -- .../scenario/defaults/cdh-5.9.0.yaml.mako | 97 -- .../scenario/defaults/cdh-ha.yaml.mako | 81 -- .../scenario/defaults/credentials.yaml.mako | 5 - .../defaults/credentials_s3.yaml.mako | 6 - .../edp-examples/edp-hive/expected_output.csv | 2 - .../defaults/edp-examples/edp-hive/input.csv | 4 - .../defaults/edp-examples/edp-hive/script.q | 6 - .../defaults/edp-examples/edp-java/README.rst | 56 -- .../edp-examples/edp-java/edp-java.jar | Bin 4039 -> 0 bytes .../edp-java/oozie_command_line/README.rst | 28 - .../wordcount/job.properties | 23 - .../oozie_command_line/wordcount/workflow.xml | 49 - .../edp-examples/edp-java/src/NOTICE.txt | 2 - .../edp-examples/edp-java/src/WordCount.java | 95 -- .../edp-mapreduce/edp-mapreduce.jar | Bin 15641 -> 0 bytes .../edp-pig/cleanup-string/README.rst | 35 - .../cleanup-string/data/expected_output | 3 - .../edp-pig/cleanup-string/data/input | 3 - .../edp-pig-udf-stringcleaner.jar | Bin 1539 -> 0 bytes .../edp-pig/cleanup-string/example.pig | 3 - .../cleanup-string/src/StringCleaner.java | 25 - .../edp-pig/top-todoers/README.rst | 68 -- .../edp-pig/top-todoers/data/expected_output | 3 - .../edp-pig/top-todoers/data/input | 18 - .../edp-pig/top-todoers/example.pig | 17 - .../edp-pig/trim-spaces/data/expected_output | 4 - .../edp-pig/trim-spaces/data/input | 4 - .../edp-examples/edp-shell/shell-example.sh | 3 - .../edp-examples/edp-shell/shell-example.txt | 1 - .../edp-examples/edp-spark/NOTICE.txt | 2 - .../edp-examples/edp-spark/README.rst | 66 -- .../edp-examples/edp-spark/sample_input.txt | 10 - .../edp-spark/spark-kafka-example.py | 48 - .../edp-examples/edp-spark/spark-pi.py | 39 - .../edp-spark/spark-wordcount.jar | Bin 6900 -> 0 bytes .../edp-spark/wordcountapp/pom.xml | 61 -- .../sahara/edp/spark/SparkWordCount.scala | 38 - .../hadoop-mapreduce-examples-2.6.0.jar | Bin 270322 -> 0 bytes .../json-api-examples/v1.1/README.rst | 277 ------ .../create.hdfs-map-reduce-input.json | 6 - .../create.hdfs-map-reduce-output.json | 6 - .../data-sources/create.swift-pig-input.json | 10 - .../data-sources/create.swift-pig-output.json | 10 - .../v1.1/job-binaries/create.java.json | 6 - .../v1.1/job-binaries/create.map-reduce.json | 9 - .../v1.1/job-binaries/create.pig-job.json | 9 - .../v1.1/job-binaries/create.pig-udf.json | 9 - .../job-binaries/create.shell-script.json | 6 - .../v1.1/job-binaries/create.shell-text.json | 6 - .../v1.1/job-binaries/create.spark.json | 9 - .../v1.1/job-executions/execute.java.json | 14 - .../job-executions/execute.map-reduce.json | 15 - .../v1.1/job-executions/execute.pig.json | 15 - .../v1.1/job-executions/execute.shell.json | 8 - .../v1.1/job-executions/execute.spark.json | 9 - .../v1.1/jobs/create.java.json | 6 - .../v1.1/jobs/create.map-reduce.json | 6 - .../v1.1/jobs/create.pig.json | 7 - .../v1.1/jobs/create.shell.json | 7 - .../v1.1/jobs/create.spark.json | 7 - sahara_tests/scenario/defaults/edp.yaml.mako | 137 --- .../scenario/defaults/edp_s3.yaml.mako | 49 - sahara_tests/scenario/defaults/fake.yaml.mako | 36 - .../defaults/mapr-5.2.0.mrv2.yaml.mako | 59 -- .../defaults/pike/ambari-2.4.yaml.mako | 69 -- .../defaults/pike/cdh-5.11.0.yaml.mako | 94 -- .../defaults/pike/cdh-5.7.0.yaml.mako | 94 -- .../defaults/pike/cdh-5.9.0.yaml.mako | 94 -- .../defaults/pike/mapr-5.2.0.mrv2.yaml.mako | 56 -- .../defaults/pike/spark-1.6.0.yaml.mako | 37 - .../defaults/pike/spark-2.1.0.yaml.mako | 37 - .../defaults/pike/storm-1.1.0.yaml.mako | 37 - .../defaults/pike/vanilla-2.7.1.yaml.mako | 85 -- .../defaults/queens/ambari-2.4.yaml.mako | 69 -- .../defaults/queens/cdh-5.11.0.yaml.mako | 94 -- .../defaults/queens/cdh-5.7.0.yaml.mako | 94 -- .../defaults/queens/cdh-5.9.0.yaml.mako | 94 -- .../defaults/queens/mapr-5.2.0.mrv2.yaml.mako | 56 -- .../defaults/queens/spark-2.1.0.yaml.mako | 37 - .../defaults/queens/spark-2.2.yaml.mako | 37 - .../defaults/queens/storm-1.1.0.yaml.mako | 37 - .../defaults/queens/vanilla-2.7.1.yaml.mako | 85 -- .../defaults/queens/vanilla-2.8.2.yaml.mako | 84 -- .../defaults/rocky/ambari-2.3.yaml.mako | 69 -- .../defaults/rocky/ambari-2.4.yaml.mako | 72 -- .../defaults/rocky/ambari-2.5.yaml.mako | 72 -- .../defaults/rocky/ambari-2.6.yaml.mako | 72 -- .../defaults/rocky/cdh-5.11.0.yaml.mako | 97 -- .../defaults/rocky/cdh-5.13.0.yaml.mako | 97 -- .../defaults/rocky/cdh-5.9.0.yaml.mako | 97 -- .../defaults/rocky/mapr-5.2.0.mrv2.yaml.mako | 59 -- .../defaults/rocky/spark-2.2.yaml.mako | 37 - .../defaults/rocky/spark-2.3.yaml.mako | 37 - .../defaults/rocky/storm-1.1.0.yaml.mako | 37 - .../defaults/rocky/storm-1.2.0.yaml.mako | 37 - .../defaults/rocky/vanilla-2.7.1.yaml.mako | 85 -- .../defaults/rocky/vanilla-2.7.5.yaml.mako | 84 -- .../defaults/rocky/vanilla-2.8.2.yaml.mako | 84 -- .../scenario/defaults/spark-2.2.yaml.mako | 37 - .../scenario/defaults/spark-2.3.yaml.mako | 37 - .../defaults/stein/ambari-2.3.yaml.mako | 69 -- .../defaults/stein/ambari-2.4.yaml.mako | 72 -- .../defaults/stein/ambari-2.5.yaml.mako | 72 -- .../defaults/stein/ambari-2.6.yaml.mako | 72 -- .../defaults/stein/cdh-5.11.0.yaml.mako | 97 -- .../defaults/stein/cdh-5.13.0.yaml.mako | 97 -- .../defaults/stein/cdh-5.9.0.yaml.mako | 97 -- .../defaults/stein/mapr-5.2.0.mrv2.yaml.mako | 59 -- .../defaults/stein/spark-2.2.yaml.mako | 37 - .../defaults/stein/spark-2.3.yaml.mako | 37 - .../defaults/stein/storm-1.1.0.yaml.mako | 37 - .../defaults/stein/storm-1.2.0.yaml.mako | 37 - .../defaults/stein/vanilla-2.7.1.yaml.mako | 85 -- .../defaults/stein/vanilla-2.7.5.yaml.mako | 84 -- .../defaults/stein/vanilla-2.8.2.yaml.mako | 84 -- .../scenario/defaults/storm-1.0.1.yaml.mako | 37 - .../scenario/defaults/storm-1.1.0.yaml.mako | 37 - .../scenario/defaults/storm-1.2.0.yaml.mako | 37 - .../scenario/defaults/transient.yaml.mako | 56 -- .../scenario/defaults/vanilla-2.7.1.yaml.mako | 84 -- .../scenario/defaults/vanilla-2.7.5.yaml.mako | 84 -- .../scenario/defaults/vanilla-2.8.2.yaml.mako | 84 -- sahara_tests/scenario/runner.py | 205 ---- sahara_tests/scenario/stestr.conf | 3 - sahara_tests/scenario/testcase.py.mako | 26 - sahara_tests/scenario/timeouts.py | 32 - sahara_tests/scenario/utils.py | 325 ------- sahara_tests/scenario/validation.py | 483 ---------- sahara_tests/unit/__init__.py | 0 sahara_tests/unit/scenario/__init__.py | 0 sahara_tests/unit/scenario/clouds.yaml | 7 - sahara_tests/unit/scenario/dummy.crt | 1 - .../unit/scenario/templatevars_complete.ini | 11 - .../unit/scenario/templatevars_incomplete.ini | 4 - .../unit/scenario/templatevars_nodefault.ini | 4 - sahara_tests/unit/scenario/test_base.py | 677 ------------- sahara_tests/unit/scenario/test_runner.py | 485 ---------- sahara_tests/unit/scenario/test_utils.py | 215 ----- sahara_tests/unit/scenario/test_validation.py | 27 - sahara_tests/unit/scenario/vanilla2_7_1.yaml | 111 --- .../unit/scenario/vanilla2_7_1.yaml.mako | 119 --- sahara_tests/unit/utils/__init__.py | 0 sahara_tests/unit/utils/test_url.py | 38 - sahara_tests/utils/__init__.py | 0 sahara_tests/utils/crypto.py | 63 -- sahara_tests/utils/tempfiles.py | 36 - sahara_tests/utils/url.py | 27 - sahara_tests/version.py | 18 - setup.cfg | 44 - setup.py | 21 - test-requirements.txt | 11 - tools/cover.sh | 84 -- tools/gate/cli_tests/README.rst | 3 - tools/gate/cli_tests/commons | 24 - tools/gate/cli_tests/pre_test_hook.sh | 8 - tools/gate/cli_tests/settings | 25 - tools/gate/scenario/README.rst | 3 - tools/gate/scenario/commons | 50 - tools/gate/scenario/dsvm-scenario-rc | 12 - tools/gate/scenario/post_test_hook.sh | 61 -- tools/gate/scenario/pre_test_hook.sh | 38 - tools/gate/scenario/settings | 43 - tools/lintstack.py | 192 ---- tools/lintstack.sh | 61 -- tox.ini | 91 -- 314 files changed, 8 insertions(+), 19286 deletions(-) delete mode 100644 .coveragerc delete mode 100644 .gitignore delete mode 100644 .stestr.conf delete mode 100644 .zuul.yaml delete mode 100644 CONTRIBUTING.rst delete mode 100644 HACKING.rst delete mode 100644 LICENSE delete mode 100644 doc/requirements.txt delete mode 100644 doc/source/conf.py delete mode 100644 doc/source/contributor/contributing.rst delete mode 100644 doc/source/index.rst delete mode 100644 doc/source/releasenotes.rst delete mode 100644 doc/source/scenario.rst delete mode 100644 doc/source/tempest-plugin.rst delete mode 100644 etc/scenario/gate/README.rst delete mode 100644 etc/scenario/gate/credentials.yaml.mako delete mode 100644 etc/scenario/gate/credentials_s3.yaml.mako delete mode 100644 etc/scenario/gate/edp.yaml.mako delete mode 100644 etc/scenario/gate/edp_s3.yaml.mako delete mode 100644 etc/scenario/gate/fake.yaml.mako delete mode 100644 etc/scenario/gate/spark-1.6.0.yaml.mako delete mode 100644 etc/scenario/simple-testcase.yaml delete mode 100644 playbooks/sahara-tests-scenario.yaml delete mode 100644 releasenotes/notes/add-ability-to-use-clouds-yaml-5f66a2c3959f894b.yaml delete mode 100644 releasenotes/notes/add-cdh-513-c4feaf4f360122ff.yaml delete mode 100644 releasenotes/notes/add-cli-for-ng-cbdcdddd5195c333.yaml delete mode 100644 releasenotes/notes/drop-py-2-7-188a5c85a436ee4f.yaml delete mode 100644 releasenotes/notes/fix-bug-1644747-5f9790ad2a0e5e56.yaml delete mode 100644 releasenotes/notes/fix-datasource-discovery-b690920189f0b161.yaml delete mode 100644 releasenotes/notes/fix-installed-resource-discovery-36ffa157cf9b06b9.yaml delete mode 100644 releasenotes/notes/fix-ngt-create-cli-test-with-multiple-plugin-version-13ed695ad2d8fab1.yaml delete mode 100644 releasenotes/notes/fix-storm-scenario-template-2d28644c9fd2ec06.yaml delete mode 100644 releasenotes/notes/force-pem-ssh-keys-2cc9eb30a76c8dd1.yaml delete mode 100644 releasenotes/notes/import-tempest-client-tests-9042d5d50bbdce9e.yaml delete mode 100644 releasenotes/notes/increase-cluster-timeout-0e97fb7d7a6ea75a.yaml delete mode 100644 releasenotes/notes/migrate-to-keystoneauth-c65c162d74b5d7b9.yaml delete mode 100644 releasenotes/notes/migrated-sahara-cli-tests-a2b32eeb6ec11512.yaml delete mode 100644 releasenotes/notes/more-auth-fixes-9ba23ceb54ba042a.yaml delete mode 100644 releasenotes/notes/new-cli-tests-job-type-4fdc68dfda728cd3.yaml delete mode 100644 releasenotes/notes/novaclient_images_to_glanceclient-9f8351330147f770.yaml delete mode 100644 releasenotes/notes/post-030-preocata-40c5fbf4d3522074.yaml delete mode 100644 releasenotes/notes/remove-nova-network-09a2afd1efd83e05.yaml delete mode 100644 releasenotes/notes/remove-old-yaml-files-97ecb4a11582f033.yaml delete mode 100644 releasenotes/notes/sahara-api-tempest-tests-as-plugin-25d63ac04902f1ad.yaml delete mode 100644 releasenotes/notes/sahara-scenario-fix-runner-59350d608d19caf9.yaml delete mode 100644 releasenotes/notes/sahara-tempest-tests-apiv2-c9878e0ba37fba2a.yaml delete mode 100644 releasenotes/notes/saharaclient-version-75ca47761f133743.yaml delete mode 100644 releasenotes/notes/scenario-boot-from-volume-a87c680b03f560a0.yaml delete mode 100644 releasenotes/notes/scenario-feature-sets-ac3e0e9e40b236bb.yaml delete mode 100644 releasenotes/notes/scenario-runner-templatesdebug-5ee5e2447cff770a.yaml delete mode 100644 releasenotes/notes/scenario-s3-20d41440d84af3a9.yaml delete mode 100644 releasenotes/notes/scenario-tests-apiv2-c71c72f51c667075.yaml delete mode 100644 releasenotes/notes/simplify-testrunner-config-d3a19014db107ff1.yaml delete mode 100644 releasenotes/notes/start-using-reno-ef5696b99f56dc5e.yaml delete mode 100644 releasenotes/notes/support-non-fake-plugin-a4ba718b787246e8.yaml delete mode 100644 releasenotes/notes/switch-to-stestr-6de59dc9e46768f6.yaml delete mode 100644 releasenotes/notes/tempest-basic-s3-636953aa1bb198d7.yaml delete mode 100644 releasenotes/notes/tempest-clients-remove-scenario-manager-4e9ea9dbf585b8cf.yaml delete mode 100644 releasenotes/notes/test-templates-ocata-dcc438604c94b72f.yaml delete mode 100644 releasenotes/notes/test-templates-pike-4e3e78362f49cb6e.yaml delete mode 100644 releasenotes/notes/test-templates-queens-901f1af3cf80f98d.yaml delete mode 100644 releasenotes/notes/test-templates-rocky-c271d2acb0d3c42a.yaml delete mode 100644 releasenotes/notes/test-templates-stein-751a4344bfcca652.yaml delete mode 100644 releasenotes/notes/testing-with-no-public-network-5b7df696fbbd4386.yaml delete mode 100644 releasenotes/notes/track-steps-timestamps-56c40d06c49dc88e.yaml delete mode 100644 releasenotes/notes/update-plugin-version-def-96979000c20b2db8.yaml delete mode 100644 releasenotes/notes/update-test-templates-ocata-3707688601c11f21.yaml delete mode 100644 releasenotes/notes/use-tempest-stable-interface-aefdfd8f81361dc8.yaml delete mode 100644 releasenotes/source/_static/.placeholder delete mode 100644 releasenotes/source/_templates/.placeholder delete mode 100644 releasenotes/source/conf.py delete mode 100644 releasenotes/source/index.rst delete mode 100644 releasenotes/source/unreleased.rst delete mode 100644 requirements.txt delete mode 100644 roles/run-sahara-scenario/defaults/main.yaml delete mode 100644 roles/run-sahara-scenario/tasks/main.yaml delete mode 100644 roles/setup-sahara-scenario-env/defaults/main.yaml delete mode 100644 roles/setup-sahara-scenario-env/tasks/main.yaml delete mode 100644 roles/setup-sahara-scenario-env/tasks/setup_s3.yaml delete mode 100644 roles/setup-sahara-scenario-env/templates/sahara_scenario_conf.ini.j2 delete mode 100644 sahara_tempest_plugin/README.rst delete mode 100644 sahara_tempest_plugin/__init__.py delete mode 100644 sahara_tempest_plugin/clients.py delete mode 100644 sahara_tempest_plugin/common/__init__.py delete mode 100644 sahara_tempest_plugin/common/plugin_utils.py delete mode 100644 sahara_tempest_plugin/config.py delete mode 100644 sahara_tempest_plugin/plugin.py delete mode 100644 sahara_tempest_plugin/services/__init__.py delete mode 100644 sahara_tempest_plugin/services/data_processing/__init__.py delete mode 100644 sahara_tempest_plugin/services/data_processing/base_client.py delete mode 100644 sahara_tempest_plugin/services/data_processing/v1_1/__init__.py delete mode 100644 sahara_tempest_plugin/services/data_processing/v1_1/data_processing_client.py delete mode 100644 sahara_tempest_plugin/services/data_processing/v2/__init__.py delete mode 100644 sahara_tempest_plugin/services/data_processing/v2/data_processing_client.py delete mode 100644 sahara_tempest_plugin/tests/__init__.py delete mode 100644 sahara_tempest_plugin/tests/api/__init__.py delete mode 100644 sahara_tempest_plugin/tests/api/base.py delete mode 100644 sahara_tempest_plugin/tests/api/test_cluster_templates.py delete mode 100644 sahara_tempest_plugin/tests/api/test_data_sources.py delete mode 100644 sahara_tempest_plugin/tests/api/test_job_binaries.py delete mode 100644 sahara_tempest_plugin/tests/api/test_job_binary_internals.py delete mode 100644 sahara_tempest_plugin/tests/api/test_job_templates.py delete mode 100644 sahara_tempest_plugin/tests/api/test_jobs.py delete mode 100644 sahara_tempest_plugin/tests/api/test_node_group_templates.py delete mode 100644 sahara_tempest_plugin/tests/api/test_plugins.py delete mode 100644 sahara_tempest_plugin/tests/cli/README.rst delete mode 100644 sahara_tempest_plugin/tests/cli/__init__.py delete mode 100644 sahara_tempest_plugin/tests/cli/base.py delete mode 100644 sahara_tempest_plugin/tests/cli/cluster_templates.py delete mode 100644 sahara_tempest_plugin/tests/cli/clusters.py delete mode 100644 sahara_tempest_plugin/tests/cli/data_sources.py delete mode 100644 sahara_tempest_plugin/tests/cli/images.py delete mode 100644 sahara_tempest_plugin/tests/cli/job_binaries.py delete mode 100644 sahara_tempest_plugin/tests/cli/job_templates.py delete mode 100644 sahara_tempest_plugin/tests/cli/job_types.py delete mode 100644 sahara_tempest_plugin/tests/cli/jobs.py delete mode 100644 sahara_tempest_plugin/tests/cli/node_group_templates.py delete mode 100644 sahara_tempest_plugin/tests/cli/plugins.py delete mode 100644 sahara_tempest_plugin/tests/cli/test_scenario.py delete mode 100644 sahara_tempest_plugin/tests/clients/__init__.py delete mode 100644 sahara_tempest_plugin/tests/clients/base.py delete mode 100644 sahara_tempest_plugin/tests/clients/test_cluster_templates.py delete mode 100644 sahara_tempest_plugin/tests/clients/test_data_sources.py delete mode 100644 sahara_tempest_plugin/tests/clients/test_job_binaries.py delete mode 100644 sahara_tempest_plugin/tests/clients/test_job_binary_internals.py delete mode 100644 sahara_tempest_plugin/tests/clients/test_job_executions.py delete mode 100644 sahara_tempest_plugin/tests/clients/test_jobs.py delete mode 100644 sahara_tempest_plugin/tests/clients/test_node_group_templates.py delete mode 100644 sahara_tempest_plugin/tests/clients/test_plugins.py delete mode 100644 sahara_tests/__init__.py delete mode 100644 sahara_tests/scenario/README.rst delete mode 100644 sahara_tests/scenario/__init__.py delete mode 100644 sahara_tests/scenario/base.py delete mode 100644 sahara_tests/scenario/clients.py delete mode 100644 sahara_tests/scenario/custom_checks/__init__.py delete mode 100644 sahara_tests/scenario/custom_checks/check_cinder.py delete mode 100644 sahara_tests/scenario/custom_checks/check_kafka.py delete mode 100644 sahara_tests/scenario/custom_checks/check_run_jobs.py delete mode 100644 sahara_tests/scenario/custom_checks/check_scale.py delete mode 100644 sahara_tests/scenario/custom_checks/check_sentry.py delete mode 100644 sahara_tests/scenario/custom_checks/check_transient.py delete mode 100644 sahara_tests/scenario/defaults/README.rst delete mode 100644 sahara_tests/scenario/defaults/ambari-2.3.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/ambari-2.4.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/ambari-2.5.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/ambari-2.6.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/cdh-5.11.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/cdh-5.13.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/cdh-5.9.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/cdh-ha.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/credentials.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/credentials_s3.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-hive/expected_output.csv delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-hive/input.csv delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-hive/script.q delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-java/README.rst delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-java/edp-java.jar delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-java/oozie_command_line/README.rst delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-java/oozie_command_line/wordcount/job.properties delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-java/oozie_command_line/wordcount/workflow.xml delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-java/src/NOTICE.txt delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-java/src/WordCount.java delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-mapreduce/edp-mapreduce.jar delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/README.rst delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/data/expected_output delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/data/input delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/edp-pig-udf-stringcleaner.jar delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/example.pig delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/src/StringCleaner.java delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/README.rst delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/data/expected_output delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/data/input delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/example.pig delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/trim-spaces/data/expected_output delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-pig/trim-spaces/data/input delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-shell/shell-example.sh delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-shell/shell-example.txt delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-spark/NOTICE.txt delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-spark/README.rst delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-spark/sample_input.txt delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-spark/spark-kafka-example.py delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-spark/spark-pi.py delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-spark/spark-wordcount.jar delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-spark/wordcountapp/pom.xml delete mode 100644 sahara_tests/scenario/defaults/edp-examples/edp-spark/wordcountapp/src/main/scala/sahara/edp/spark/SparkWordCount.scala delete mode 100644 sahara_tests/scenario/defaults/edp-examples/hadoop2/edp-java/hadoop-mapreduce-examples-2.6.0.jar delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/README.rst delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/data-sources/create.hdfs-map-reduce-input.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/data-sources/create.hdfs-map-reduce-output.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/data-sources/create.swift-pig-input.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/data-sources/create.swift-pig-output.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-binaries/create.java.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-binaries/create.map-reduce.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-binaries/create.pig-job.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-binaries/create.pig-udf.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-binaries/create.shell-script.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-binaries/create.shell-text.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-binaries/create.spark.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-executions/execute.java.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-executions/execute.map-reduce.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-executions/execute.pig.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-executions/execute.shell.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/job-executions/execute.spark.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/jobs/create.java.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/jobs/create.map-reduce.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/jobs/create.pig.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/jobs/create.shell.json delete mode 100644 sahara_tests/scenario/defaults/edp-examples/json-api-examples/v1.1/jobs/create.spark.json delete mode 100644 sahara_tests/scenario/defaults/edp.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/edp_s3.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/fake.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/mapr-5.2.0.mrv2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/pike/ambari-2.4.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/pike/cdh-5.11.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/pike/cdh-5.7.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/pike/cdh-5.9.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/pike/mapr-5.2.0.mrv2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/pike/spark-1.6.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/pike/spark-2.1.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/pike/storm-1.1.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/pike/vanilla-2.7.1.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/queens/ambari-2.4.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/queens/cdh-5.11.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/queens/cdh-5.7.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/queens/cdh-5.9.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/queens/mapr-5.2.0.mrv2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/queens/spark-2.1.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/queens/spark-2.2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/queens/storm-1.1.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/queens/vanilla-2.7.1.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/queens/vanilla-2.8.2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/ambari-2.3.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/ambari-2.4.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/ambari-2.5.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/ambari-2.6.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/cdh-5.11.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/cdh-5.13.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/cdh-5.9.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/mapr-5.2.0.mrv2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/spark-2.2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/spark-2.3.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/storm-1.1.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/storm-1.2.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/vanilla-2.7.1.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/vanilla-2.7.5.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/rocky/vanilla-2.8.2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/spark-2.2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/spark-2.3.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/ambari-2.3.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/ambari-2.4.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/ambari-2.5.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/ambari-2.6.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/cdh-5.11.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/cdh-5.13.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/cdh-5.9.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/mapr-5.2.0.mrv2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/spark-2.2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/spark-2.3.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/storm-1.1.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/storm-1.2.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/vanilla-2.7.1.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/vanilla-2.7.5.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/stein/vanilla-2.8.2.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/storm-1.0.1.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/storm-1.1.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/storm-1.2.0.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/transient.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/vanilla-2.7.1.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/vanilla-2.7.5.yaml.mako delete mode 100644 sahara_tests/scenario/defaults/vanilla-2.8.2.yaml.mako delete mode 100755 sahara_tests/scenario/runner.py delete mode 100644 sahara_tests/scenario/stestr.conf delete mode 100644 sahara_tests/scenario/testcase.py.mako delete mode 100644 sahara_tests/scenario/timeouts.py delete mode 100644 sahara_tests/scenario/utils.py delete mode 100644 sahara_tests/scenario/validation.py delete mode 100644 sahara_tests/unit/__init__.py delete mode 100644 sahara_tests/unit/scenario/__init__.py delete mode 100644 sahara_tests/unit/scenario/clouds.yaml delete mode 100644 sahara_tests/unit/scenario/dummy.crt delete mode 100644 sahara_tests/unit/scenario/templatevars_complete.ini delete mode 100644 sahara_tests/unit/scenario/templatevars_incomplete.ini delete mode 100644 sahara_tests/unit/scenario/templatevars_nodefault.ini delete mode 100644 sahara_tests/unit/scenario/test_base.py delete mode 100644 sahara_tests/unit/scenario/test_runner.py delete mode 100644 sahara_tests/unit/scenario/test_utils.py delete mode 100644 sahara_tests/unit/scenario/test_validation.py delete mode 100644 sahara_tests/unit/scenario/vanilla2_7_1.yaml delete mode 100644 sahara_tests/unit/scenario/vanilla2_7_1.yaml.mako delete mode 100644 sahara_tests/unit/utils/__init__.py delete mode 100644 sahara_tests/unit/utils/test_url.py delete mode 100644 sahara_tests/utils/__init__.py delete mode 100644 sahara_tests/utils/crypto.py delete mode 100644 sahara_tests/utils/tempfiles.py delete mode 100644 sahara_tests/utils/url.py delete mode 100644 sahara_tests/version.py delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 test-requirements.txt delete mode 100755 tools/cover.sh delete mode 100644 tools/gate/cli_tests/README.rst delete mode 100644 tools/gate/cli_tests/commons delete mode 100755 tools/gate/cli_tests/pre_test_hook.sh delete mode 100644 tools/gate/cli_tests/settings delete mode 100644 tools/gate/scenario/README.rst delete mode 100644 tools/gate/scenario/commons delete mode 100755 tools/gate/scenario/dsvm-scenario-rc delete mode 100755 tools/gate/scenario/post_test_hook.sh delete mode 100755 tools/gate/scenario/pre_test_hook.sh delete mode 100644 tools/gate/scenario/settings delete mode 100755 tools/lintstack.py delete mode 100755 tools/lintstack.sh delete mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 590b21b6..00000000 --- a/.coveragerc +++ /dev/null @@ -1,13 +0,0 @@ -[run] -branch = True -source = sahara_tests -omit = - .tox/* - sahara_tests/unit/* - -[paths] -source = sahara_tests - -[report] -ignore_errors = True -precision = 3 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8ffeb89d..00000000 --- a/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -*.egg-info -*.egg[s] -*.log -*.py[co] -.coverage -.testrepository -.tox -.stestr -.venv -AUTHORS -ChangeLog -build -cover -develop-eggs -dist -doc/build -doc/html -eggs -scenario_key-* -sdist -target -tools/lintstack.head.py -tools/pylint_exceptions -doc/source/sample.config - -# Files created by releasenotes build -releasenotes/build diff --git a/.stestr.conf b/.stestr.conf deleted file mode 100644 index ed3a4ffb..00000000 --- a/.stestr.conf +++ /dev/null @@ -1,3 +0,0 @@ -[DEFAULT] -test_path=sahara_tests/unit -group_regex=([^\.]+\.)+ diff --git a/.zuul.yaml b/.zuul.yaml deleted file mode 100644 index 685fb107..00000000 --- a/.zuul.yaml +++ /dev/null @@ -1,325 +0,0 @@ -- project: - queue: sahara - templates: - - publish-openstack-docs-pti - - openstack-python3-ussuri-jobs - - release-notes-jobs-python3 - check: - jobs: - - openstack-tox-cover: - voting: false - - openstack-tox-pylint: - voting: false - - sahara-tests-scenario: - voting: false - - sahara-tests-scenario-v2: - voting: false - - sahara-tests-tempest: - voting: false - - sahara-tests-tempest-v2: - voting: false - - sahara-tests-scenario-wallaby: - voting: false - - sahara-tests-scenario-victoria: - voting: false - - sahara-tests-scenario-ussuri: - voting: false - - sahara-tests-scenario-train: - voting: false - - sahara-tests-scenario-stein: - voting: false - - openstack-tox-py36: - voting: false - - openstack-tox-py37: - voting: false - gate: - jobs: - - sahara-tests-scenario: - voting: false - - sahara-tests-scenario-v2: - voting: false - - sahara-tests-tempest: - voting: false - - sahara-tests-tempest-v2: - voting: false - - openstack-tox-py36: - voting: false - - openstack-tox-py37: - voting: false - experimental: - jobs: - - sahara-tests-scenario-multinode-spark - -- job: - name: sahara-tests-tempest - description: | - Run Tempest tests from the Sahara plugin. - parent: devstack-tempest - required-projects: - - openstack/sahara-tests - - openstack/sahara - - openstack/sahara-plugin-ambari - - openstack/sahara-plugin-cdh - - openstack/sahara-plugin-mapr - - openstack/sahara-plugin-spark - - openstack/sahara-plugin-storm - - openstack/sahara-plugin-vanilla - - openstack/heat - # - openstack/ceilometer - vars: - tempest_test_regex: ^(sahara_tempest_plugin.tests.) - tox_envlist: all - devstack_localrc: - IMAGE_URLS: https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img - TEMPEST_PLUGINS: /opt/stack/sahara-tests - USE_PYTHON3: True - devstack_local_conf: - test-config: - $TEMPEST_CONFIG: - data_processing: - test_image_name: xenial-server-cloudimg-amd64-disk1 - test_ssh_user: ubuntu - data-processing-feature-enabled: - s3: 'True' - devstack_plugins: - sahara: https://opendev.org/openstack/sahara - heat: https://opendev.org/openstack/heat - # ceilometer: https://opendev.org/openstack/ceilometer - devstack_services: - tls-proxy: false - irrelevant-files: - - ^.*\.rst$ - - ^api-ref/.*$ - - ^doc/.*$ - - ^etc/.*$ - - ^releasenotes/.*$ - - ^sahara_tests/.*$ - -- job: - name: sahara-tests-tempest-v2 - description: | - Run Tempest tests from the Sahara plugin against Sahara APIv2 - and Python 3. - parent: sahara-tests-tempest - required-projects: - - openstack/python-saharaclient - branches: master - vars: - devstack_localrc: - USE_PYTHON3: 'True' - devstack_local_conf: - test-config: - $TEMPEST_CONFIG: - data-processing: - api_version_saharaclient: '2' - use_api_v2: 'True' - devstack_services: - s-account: false - s-container: false - s-object: false - s-proxy: false - -# variant for pre-Rocky branches (no S3) -- job: - name: sahara-tests-tempest - branches: - - stable/ocata - - stable/pike - - stable/queens - vars: - devstack_localrc: - USE_PYTHON3: 'False' - devstack_local_conf: - test-config: - $TEMPEST_CONFIG: - data-processing-feature-enabled: - s3: 'False' - -# variant for pre-Ussuri branches (Python 2 by default) -- job: - name: sahara-tests-tempest - branches: - - stable/rocky - - stable/stein - - stable/train - vars: - devstack_localrc: - USE_PYTHON3: 'False' - -- job: - name: sahara-tests-scenario - description: | - Run scenario tests for Sahara. - parent: devstack - roles: - - zuul: openstack/devstack - - zuul: openstack/sahara-image-elements - required-projects: - - openstack/sahara-tests - - openstack/sahara - - openstack/sahara-plugin-ambari - - openstack/sahara-plugin-cdh - - openstack/sahara-plugin-mapr - - openstack/sahara-plugin-spark - - openstack/sahara-plugin-storm - - openstack/sahara-plugin-vanilla - - openstack/heat - # - openstack/ceilometer - - openstack/sahara-image-elements - - openstack/shade - run: playbooks/sahara-tests-scenario.yaml - host-vars: - controller: - devstack_plugins: - sahara: https://opendev.org/openstack/sahara - heat: https://opendev.org/openstack/heat - # ceilometer: https://opendev.org/openstack/ceilometer - shade: https://opendev.org/openstack/shade - group-vars: - subnode: - devstack_services: - tls-proxy: false - vars: - devstack_services: - tls-proxy: false - devstack_localrc: - # required to contain (almost any) custom-built image - SWIFT_LOOPBACK_DISK_SIZE: 8G - SWIFT_MAX_FILE_SIZE: 8589934592 - USE_PYTHON3: True - devstack_local_conf: - post-config: - $SAHARA_CONF_FILE: - DEFAULT: - min_transient_cluster_active_time: 90 - sahara_image_name: xenial-server - sahara_image_url: https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img - sahara_plugin: fake - sahara_plugin_version: '0.1' - sahara_scenario_test_template: fake.yaml.mako - sahara_scenario_tox_env: venv - irrelevant-files: - - ^.*\.rst$ - - ^api-ref/.*$ - - ^doc/.*$ - - ^releasenotes/.*$ - - ^sahara_tempest_plugin/.*$ - -- job: - name: sahara-tests-scenario-v2 - parent: sahara-tests-scenario - vars: - sahara_scenario_use_api_v2: True - -- job: - name: sahara-tests-scenario-wallaby - parent: sahara-tests-scenario-py3 - override-checkout: stable/wallaby - -- job: - name: sahara-tests-scenario-victoria - parent: sahara-tests-scenario-py3 - override-checkout: stable/victoria - -- job: - name: sahara-tests-scenario-ussuri - parent: sahara-tests-scenario-py3 - nodeset: openstack-single-node-bionic - override-checkout: stable/ussuri - -# pre-Ussuri scenario tests: fully-py3 based according the rules -# (jobs running on master must use Python 3), but use RGW -# on pre-Train branches as Swift/py3 does not work there. -- job: - name: sahara-tests-scenario-train - parent: sahara-tests-scenario - nodeset: openstack-single-node-bionic - override-checkout: stable/train - -- job: - name: sahara-tests-scenario-stein - parent: sahara-tests-scenario-py3 - nodeset: openstack-single-node-bionic - override-checkout: stable/stein - -- job: - name: sahara-tests-scenario-modwsgi - description: | - Run scenario tests on a Sahara deployment based on mod_wsgi. - parent: sahara-tests-scenario - vars: - devstack_localrc: - SAHARA_USE_MOD_WSGI: 'True' - -# variant to be used on pre-Ussuri branches (Python 2 only) -- job: - name: sahara-tests-scenario - branches: - - stable/rocky - - stable/stein - - stable/train - vars: - devstack_localrc: - USE_PYTHON3: 'False' - -- job: - name: sahara-tests-scenario-radosgw - description: | - Run scenario tests for Sahara, using RadosGW instead of Swift. - parent: sahara-tests-scenario - required-projects: - - openstack/devstack-plugin-ceph - host-vars: - controller: - devstack_plugins: - devstack-plugin-ceph: 'https://opendev.org/openstack/devstack-plugin-ceph' - vars: - devstack_localrc: - ENABLE_CEPH_CINDER: 'False' - ENABLE_CEPH_C_BAK: 'False' - ENABLE_CEPH_GLANCE: 'False' - ENABLE_CEPH_MANILA: 'False' - ENABLE_CEPH_NOVA: 'False' - ENABLE_CEPH_RGW: 'True' - devstack_local_conf: - test-config: - "$TEMPEST_CONFIG": - service_available: - swift: 'True' - devstack_services: - s-account: false - s-container: false - s-object: false - s-proxy: false - sahara_enable_s3: True - -- job: - name: sahara-tests-scenario-py3 - description: | - Run scenario tests on a Sahara deployment based on Python 3. - Required by some pre-Ussuri branches of sahara, which also - needs swift (not fully ported to Python 3 at the time). - parent: sahara-tests-scenario-radosgw - vars: - devstack_localrc: - USE_PYTHON3: 'True' - -- job: - name: sahara-tests-scenario-multinode-spark - description: | - Run scenario tests based on Spark on a multinode Sahara deployment. - parent: sahara-tests-scenario-radosgw - nodeset: openstack-two-node - vars: - sahara_image_name: xenial-spark - sahara_image_url: '{{ ansible_user_dir }}/{{ zuul.projects["opendev.org/openstack/sahara-image-elements"].src_dir }}/ubuntu_sahara_spark_latest.qcow2' - sahara_plugin: spark - sahara_plugin_version: 1.6.0 - sahara_scenario_test_template: spark-1.6.0.yaml.mako - sahara_flavors: - sah1.small: - id: 20 - ram: 1536 - disk: 20 - vcpus: 1 - ephemeral: 0 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 443a6a2e..00000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,19 +0,0 @@ -The source repository for this project can be found at: - - https://opendev.org/openstack/sahara-tests - -Pull requests submitted through GitHub are not monitored. - -To start contributing to OpenStack, follow the steps in the contribution guide -to set up and use Gerrit: - - https://docs.openstack.org/contributors/code-and-documentation/quick-start.html - -Bugs should be filed on Storyboard: - - https://storyboard.openstack.org/#!/project/openstack/sahara-tests - -For more specific information about contributing to this repository, see the -sahara-tests contributor guide: - - https://docs.openstack.org/sahara-tests/latest/contributor/contributing.html diff --git a/HACKING.rst b/HACKING.rst deleted file mode 100644 index c1a1cac0..00000000 --- a/HACKING.rst +++ /dev/null @@ -1,53 +0,0 @@ -Sahara Style Commandments -========================== - -- Step 1: Read the OpenStack Style Commandments - https://docs.openstack.org/hacking/latest/ -- Step 2: Read on - -Sahara Specific Commandments ------------------------------ - -Commit Messages ---------------- -Using a common format for commit messages will help keep our git history -readable. Follow these guidelines: - -- [S365] First, provide a brief summary of 50 characters or less. Summaries - of greater then 72 characters will be rejected by the gate. - -- [S364] The first line of the commit message should provide an accurate - description of the change, not just a reference to a bug or blueprint. - -Imports -------- -- [S366, S367] Organize your imports according to the ``Import order`` - -Dictionaries/Lists ------------------- - -- [S360] Ensure default arguments are not mutable. -- [S368] Must use a dict comprehension instead of a dict constructor with a - sequence of key-value pairs. For more information, please refer to - http://legacy.python.org/dev/peps/pep-0274/ -======= -Logs ----- - -- [S369] Check LOG.info translations - -- [S370] Check LOG.error translations - -- [S371] Check LOG.warning translations - -- [S372] Check LOG.critical translation - -- [S373] LOG.debug never used for translations - -- [S374] You used a deprecated log level - -Importing json --------------- - -- [S375] It's more preferable to use ``jsonutils`` from ``oslo_serialization`` - instead of ``json`` for operating with ``json`` objects. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 67db8588..00000000 --- a/LICENSE +++ /dev/null @@ -1,175 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. diff --git a/README.rst b/README.rst index e7911944..4ee2c5f1 100644 --- a/README.rst +++ b/README.rst @@ -1,38 +1,10 @@ -======================== -Team and repository tags -======================== +This project is no longer maintained. -.. image:: https://governance.openstack.org/tc/badges/sahara-tests.svg - :target: https://governance.openstack.org/tc/reference/tags/index.html +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". -.. Change things from this point on - -Tests for Sahara project -======================== - -.. image:: https://img.shields.io/pypi/v/sahara-tests.svg - :target: https://pypi.org/project/sahara-tests/ - :alt: Latest Version - -.. image:: https://img.shields.io/pypi/dm/sahara-tests.svg - :target: https://pypi.org/project/sahara-tests/ - :alt: Downloads - -In sahara_tests folder we have next tools: - - Sahara-scenario framework - - Sahara tempest tests - -* License: Apache License, Version 2.0 -* `Sahara-scenario documentation`_ -* `PyPi`_ - package installation -* `Storyboard project`_ - release management -* `Source`_ -* `Specs`_ -* `Release notes`_ - -.. _Sahara-scenario documentation: https://docs.openstack.org/sahara-tests/latest/scenario.html -.. _PyPi: https://pypi.org/project/sahara-tests -.. _Storyboard project: https://storyboard.openstack.org/#!/project/940 -.. _Source: https://opendev.org/openstack/sahara-tests -.. _Specs: https://specs.openstack.org/openstack/sahara-specs/ -.. _Release notes: https://docs.openstack.org/releasenotes/sahara-tests/unreleased.html +For any further questions, please email +openstack-discuss@lists.openstack.org or join #openstack-dev on +OFTC. diff --git a/doc/requirements.txt b/doc/requirements.txt deleted file mode 100644 index f93a0671..00000000 --- a/doc/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -openstackdocstheme>=2.2.1 # Apache-2.0 -reno>=3.1.0 # Apache-2.0 -sphinx>=2.0.0,!=2.1.0 # BSD diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 3626dfac..00000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,239 +0,0 @@ -# -*- coding: utf-8 -*- -# -# sahara-tests documentation build configuration file, created by -# sphinx-quickstart on Mon Dec 21 21:22:00 2015. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - 'reno.sphinxext', - 'openstackdocstheme', -] - -# openstackdocstheme options -openstackdocs_repo_name = 'openstack/sahara-tests' -openstackdocs_auto_name = False -openstackdocs_use_storyboard = True - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'sahara-tests' -copyright = u'2015, Sahara team' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'native' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'openstackdocs' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -html_use_smartypants = False - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'sahara-testsdoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'sahara-tests.tex', u'sahara-tests Documentation', - u'Sahara team', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'sahara-tests', u'sahara-tests Documentation', - [u'Sahara team'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'sahara-tests', u'sahara-tests Documentation', - u'Sahara team', 'sahara-tests', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' diff --git a/doc/source/contributor/contributing.rst b/doc/source/contributor/contributing.rst deleted file mode 100644 index 413918a0..00000000 --- a/doc/source/contributor/contributing.rst +++ /dev/null @@ -1,14 +0,0 @@ -============================ -So You Want to Contribute... -============================ - -For general information on contributing to OpenStack, please check out the -`contributor guide `_ to get started. -It covers all the basics that are common to all OpenStack projects: the -accounts you need, the basics of interacting with our Gerrit review system, how -we communicate as a community, etc. - -sahara-tests is maintained by the OpenStack Sahara project. -To understand our development process and how you can contribute to it, please -look at the Sahara project's general contributor's page: -http://docs.openstack.org/sahara/latest/contributor/contributing.html diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index 627f9e35..00000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,52 +0,0 @@ -Welcome to sahara-tests's documentation! -======================================== - -Tests for the -`Sahara project `_. - -It provides Sahara scenario tests framework and tempest tests. - -User guide ----------- - -**Scenario Tests** - -.. toctree:: - :maxdepth: 1 - - scenario - -**Tempest Plugin** - -.. toctree:: - :maxdepth: 1 - - tempest-plugin - -**Contributor Guide** - -.. toctree:: - :maxdepth: 1 - - contributor/contributing - -Source ------- - -* License: Apache License, Version 2.0 -* `PyPi`_ - package installation -* `Storyboard project`_ - release management -* `Source`_ -* `Specs`_ -* :doc:`releasenotes` - -.. _PyPi: https://pypi.org/project/sahara-tests -.. _Storyboard project: https://storyboard.openstack.org/#!/project/940 -.. _Source: https://opendev.org/openstack/sahara-tests -.. _Specs: https://specs.openstack.org/openstack/sahara-specs/ - -Indices and tables ------------------- - -* :ref:`genindex` -* :ref:`search` diff --git a/doc/source/releasenotes.rst b/doc/source/releasenotes.rst deleted file mode 100644 index 2f44c3c5..00000000 --- a/doc/source/releasenotes.rst +++ /dev/null @@ -1,11 +0,0 @@ -:orphan: - -.. The :orphan: directive is required because - this file is not in the toctree - even if it is included by a :doc: directive. - -============= -Release Notes -============= - -.. release-notes:: diff --git a/doc/source/scenario.rst b/doc/source/scenario.rst deleted file mode 100644 index eebd09fe..00000000 --- a/doc/source/scenario.rst +++ /dev/null @@ -1,792 +0,0 @@ -System(scenario) tests for Sahara project -========================================= - -_`Authentication` ------------------ - -You need to be authenticated to run these tests. To authenticate you should -create openrc file (like in devstack) and source it. - -.. sourcecode:: bash - - #!/bin/sh - export OS_TENANT_NAME='admin' - export OS_PROJECT_NAME='admin' - export OS_USERNAME='admin' - export OS_PASSWORD='admin' - export OS_AUTH_URL='http://localhost:5000/v2.0' - -.. - -Also you can specify the authentication details for Sahara tests using flags -in run-command: - -.. sourcecode:: console - - List of flags: - --os-username - --os-password - --os-project-name - --os-auth-url -.. - -Last way to set the authentication details for these tests is using a -``clouds.yaml`` file. - -After creating the file, you can set ``OS_CLOUD`` variable or ``--os-cloud`` -flag to the name of the cloud you have created and those values will be used. - -We have an example of a ``clouds.yaml`` file, and you can find it in -``sahara_tests/unit/scenario/clouds.yaml``. - -Using this example, you can create your own file with clouds instead of -setting the ``OS_CLOUD`` variable or the ``--os-cloud`` flag. Note that more -than one cloud can be defined in the same file. - -Here you can find more information about -`clouds -`_ - -Template variables ------------------- -You need to define these variables because they are used in mako template -files and replace the values from scenario files. These names pass to the test -runner through the ``-V`` parameter and a special config file. - -The format of the config file is an INI-style file, as accepted by the Python -ConfigParser module. The key/values must be specified in the DEFAULT section. - -Variables and defaults templates -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The following variables are currently used by defaults templates: - -+-----------------------------+--------+-------------------------+ -| Variable | Type | Value | -+=============================+========+=========================+ -| network_private_name | string | private network name | -| | | for OS_PROJECT_NAME | -+-----------------------------+--------+-------------------------+ -| network_public_name | string | public network name | -+-----------------------------+--------+-------------------------+ -| _image | string | name of the image to be | -| | | used for the specific | -| | | plugin/version | -+-----------------------------+--------+-------------------------+ -| {ci,medium,large}_flavor_id | string | IDs of flavor with | -| | | different size | -+-----------------------------+--------+-------------------------+ - -After finishing with authentication and configuration of file with template -variables, you can run Sahara tests using Sahara Scenario Framework. - -How to run ----------- - -Scenario framework has default templates for testing Sahara. To -use them, specify plugin and version (for transient check and fake plugin, -version is not necessary): - -.. sourcecode:: console - - $ tox -e venv -- sahara-scenario -p vanilla -v 2.7.1 -.. - -Different OpenStack releases may require different configuration for the -same set of plugin and versions. If you use the plugin and version flag, -if you want to use the configuration file for a specific OpenStack release -supported by sahara-scenario, you can specify also the ``-r RELEASE`` -argument, where ``RELEASE`` is the official name of the OpenStack release. - -By default only default configuration files for the specified plugin and -version (and release, if any) are included. Also, if any job configuration -is included, only jobs not tagged with any features will be executed. -In order to enable feature-specific configuration settings, pass -the list of requested features through the ``--feature`` (``-f``) parameter. - -The parameter makes sure that: - -* additional base configuration file which are feature-specific are included; -* in addition to non-tagged jobs, jobs which are tagged with the specified - features are included too. - -Example: - -.. sourcecode:: console - - $ tox -e venv -- sahara-scenario -p vanilla -v 2.7.1 -f s3 -f myfeature -r rocky -.. - -Create the YAML and/or the YAML mako template files for scenario tests -``etc/scenario/simple-testcase.yaml``. -You can take a look at sample YAML files `How to write scenario files`_. - -The file ``templatevars.ini`` contains the values of the variables referenced -by any testcase you are going to run. - -If you want to run tests for the Vanilla plugin with the Hadoop version 2.7.1, -you should create ``templatevars.ini`` with the appropriate values (see the -section `Variables and defaults templates`_) and use the following tox env: - -.. sourcecode:: console - - $ tox -e venv -- sahara-scenario -V templatevars.ini sahara_tests/scenario/defaults/vanilla-2.7.1.yaml.mako -.. - -Credentials locate in ``sahara_tests/scenario/defaults/credentials.yaml.mako``. -This file replace the variables included into testcase YAML or YAML Mako files -with the values defined into ``templatevars.ini``. - -.. sourcecode:: console - - $ tox -e venv -- sahara-scenario -V templatevars.ini sahara_tests/scenario/defaults/credentials.yaml.mako sahara_tests/scenario/defaults/vanilla-2.7.1.yaml.mako - -.. - -The most useful and comfortable way to run sahara-scenario tests for Vanilla -Plugin: - -.. sourcecode:: console - - $ tox -e venv -- sahara-scenario -V templatevars.ini sahara_tests/scenario/defaults/credantials.yaml.mako -p vanilla -v 2.7.1 - -.. - -For more information about writing scenario YAML files, see the section -section `How to write scenario files`_. - -Virtual environment flags -------------------------- - -You can use the following flags to the Sahara scenario tests. - -Optional arguments -~~~~~~~~~~~~~~~~~~ - -+-------------------+----------------------------+ -| Arguments | Description | -+===================+============================+ -| --help, -h | show help message and exit | -+-------------------+----------------------------+ -| --variable_file, | path to the file with | -| -V | template variables | -+-------------------+----------------------------+ -| --verbose | increase output verbosity | -+-------------------+----------------------------+ -| --validate | validate yaml-files, | -| | tests will not be run | -+-------------------+----------------------------+ -| --args ARGS | pairs of argument | -| | key:value | -+-------------------+----------------------------+ -| --plugin, | specify plugin name | -| -p PLUGIN | | -+-------------------+----------------------------+ -| --plugin_version, | specify plugin version | -| -v PLUGIN_VERSION | | -+-------------------+----------------------------+ -| --release, | specify Sahara release | -| -r RELEASE | | -+-------------------+----------------------------+ -| --report | write results to file | -+-------------------+----------------------------+ -| --feature, | list of features | -| -f FEAT1 | that should be enabled | -| [-f FEAT2 ...] | | -+-------------------+----------------------------+ -| --count COUNT | specify count of runs | -+-------------------+----------------------------+ -| --os-cloud name | name of cloud to connect | -+-------------------+----------------------------+ -| --os-auth-type, | | -| --os-auth-plugin | authentication type to use | -| name | | -+-------------------+----------------------------+ - -Authentication options -~~~~~~~~~~~~~~~~~~~~~~ - -Options specific to the password plugin. - -+--------------------------+--------------------------------+ -| Arguments | Description | -+==========================+================================+ -| --os-auth-url | authentication URL | -| OS_AUTH_URL | | -+--------------------------+--------------------------------+ -| --os-domain-id | domain ID to scope to | -| OS_DOMAIN_ID | | -+--------------------------+--------------------------------+ -| --os-domain-name | domain name to scope to | -| OS_DOMAIN_NAME | | -+--------------------------+--------------------------------+ -| --os-project-id | | -| --os-tenant-id | project ID to scope to | -| OS_PROJECT_ID | | -+--------------------------+--------------------------------+ -| --os-project-name | | -| --os-tenant-name | project name to scope to | -| OS_PROJECT_NAME | | -+--------------------------+--------------------------------+ -| --os-project-domain-id | domain ID containing project | -| OS_PROJECT_DOMAIN_ID | | -+--------------------------+--------------------------------+ -| --os-project-domain-name | domain name containing project | -| OS_PROJECT_DOMAIN_NAME | | -+--------------------------+--------------------------------+ -| --os-trust-id | trust ID | -| OS_TRUST_ID | | -+--------------------------+--------------------------------+ -| | optional domain ID to use with | -| | v3 and v2 parameters. It will | -| --os-default-domain-id | be used for both the user and | -| OS_DEFAULT_DOMAIN_ID | project domain in v3 and | -| | ignored in v2 authentication. | -+--------------------------+--------------------------------+ -| | optional domain name to use | -| | with v3 API and v2parameters. | -| --os-default-domain-name | It will be used for both | -| OS_DEFAULT_DOMAIN_NAME | the user and project domain | -| | in v3 and ignored in v2 | -| | authentication. | -+--------------------------+--------------------------------+ -| --os-user-id | user ID | -| OS_USER_ID | | -+--------------------------+--------------------------------+ -| --os-username, | | -| --os-user-name | username | -| OS_USERNAME | | -+--------------------------+--------------------------------+ -| --os-user-domain-id | user's domain id | -| OS_USER_DOMAIN_ID | | -+--------------------------+--------------------------------+ -| --os-user-domain-name | user's domain name | -| OS_USER_DOMAIN_NAME | | -+--------------------------+--------------------------------+ -| --os-password | user's password | -| OS_PASSWORD | | -+--------------------------+--------------------------------+ - -API Connection Options -~~~~~~~~~~~~~~~~~~~~~~ - -Options controlling the HTTP API connections. - -+--------------------------+--------------------------------------+ -| Arguments | Description | -+==========================+======================================+ -| | explicitly allow client to | -| | perform "insecure" TLS (https) | -| --insecure | requests. The server's | -| | certificate will not be verified | -| | against any certificate authorities. | -| | This option should be used with | -| | caution. | -+--------------------------+--------------------------------------+ -| | specify a CA bundle file to use in | -| --os-cacert | verifying a TLS(https) server | -| | certificate. Defaults to env | -| | [OS_CACERT]. | -+--------------------------+--------------------------------------+ -| --os-cert | defaults to env[OS_CERT] | -+--------------------------+--------------------------------------+ -| --os-key | defaults to env[OS_KEY] | -+--------------------------+--------------------------------------+ -| --timeout | set request timeout (in seconds) | -+--------------------------+--------------------------------------+ - -Service Options -~~~~~~~~~~~~~~~ - -Options control the specialization of the API connection from information -found in the catalog. - -+------------------------+----------------------------+ -| Arguments | Description | -+========================+============================+ -| --os-service-type | service type to request | -| | from the catalog | -+------------------------+----------------------------+ -| --os-service-name | service name to request | -| | from the catalog | -+------------------------+----------------------------+ -| --os-interface | API Interface to use: | -| | [public, internal, admin] | -+------------------------+----------------------------+ -| --os-region-name | region of the cloud to use | -| | | -+------------------------+----------------------------+ -| | endpoint to use instead | -| --os-endpoint-override | of the endpoint in the | -| | catalog | -+------------------------+----------------------------+ -| --os-api-version | which version of the | -| | service API to use | -+------------------------+----------------------------+ - -_`How to write scenario files` ------------------------------- - -The example of full scenario file with all these parameters you can find in -``etc/scenario/simple-testcase.yaml``. - -You can write all sections in one or several files, which can be simple YAML -files or YAML-based Mako templates (.yaml.mako or yml.mako). Fox example, -the most common sections you can keep in ``templatevars.ini`` and -``sahara_tests/scenario/defaults/credentials.yaml.mako``. - -Field "concurrency" -------------------- - -This field has integer value, and set concurrency for run tests - -For example: - ``concurrency: 2`` - -For parallel testing use flag ``--count`` in run command and -setup ``cuncurrency`` value - -Section "credentials" ---------------------- - -This section is dictionary-type. - -+---------------------+--------+----------+----------------+----------------+ -| Fields | Type | Required | Default | Value | -+=====================+========+==========+================+================+ -| sahara_service_type | string | | data-processing| service type | -| | | | | for sahara | -+---------------------+--------+----------+----------------+----------------+ -| sahara_url | string | | None | url of sahara | -+---------------------+--------+----------+----------------+----------------+ -| ssl_cert | string | | None | ssl certificate| -| | | | | for all clients| -+---------------------+--------+----------+----------------+----------------+ -| ssl_verify | boolean| | False | enable verify | -| | | | | ssl for sahara | -+---------------------+--------+----------+----------------+----------------+ - -Section "network" ------------------ - -This section is dictionary-type. - -+-----------------------------+---------+----------+---------+----------------+ -| Fields | Type | Required | Default | Value | -+=============================+=========+==========+=========+================+ -| private_network | string | True | private | name or id of | -| | | | | private network| -+-----------------------------+---------+----------+---------+----------------+ -| public_network | string | | public | name or id of | -| | | | | private network| -+-----------------------------+---------+----------+---------+----------------+ -| auto_assignment_floating_ip | boolean | | False | | -+-----------------------------+---------+----------+---------+----------------+ - - -Section "clusters" ------------------- - -This sections is an array-type. - -.. list-table:: - :header-rows: 1 - - * - Fields - - Type - - Required - - Default - - Value - - * - plugin_name - - string - - True - - - - name of plugin - * - plugin_version - - string - - True - - - - version of plugin - * - image - - string - - True - - - - name or id of image - * - image_username - - string - - - - - - username for registering image - * - existing_cluster - - string - - - - - - cluster name or id for testing - * - key_name - - string - - - - - - name of registered ssh key for testing cluster - * - node_group_templates - - object - - - - - - see `section "node_group_templates"`_ - * - cluster_template - - object - - - - - - see `section "cluster_template"`_ - * - cluster - - object - - - - - - see `section "cluster"`_ - * - scaling - - object - - - - - - see `section "scaling"`_ - * - timeout_check_transient - - integer - - - - 300 - - timeout for checking transient - * - timeout_poll_jobs_status - - integer - - - - 1800 - - timeout for polling jobs state - * - timeout_delete_resource - - integer - - - - 300 - - timeout for delete resource - * - timeout_poll_cluster_status - - integer - - - - 3600 - - timeout for polling cluster state - * - scenario - - array - - - - ['run_jobs', 'scale', 'run_jobs'] - - array of checks - * - edp_jobs_flow - - string, list - - - - - - name of jobs defined under edp_jobs_flow be executed on the cluster; - if list, each item may be a dict with fields - ``name`` (string) and ``features`` (list), or a string - * - hdfs_username - - string - - - - hadoop - - username for hdfs - * - retain_resources - - boolean - - - - False - - - - -Section "node_group_templates" ------------------------------- - -This section is an array-type. - - -.. list-table:: - :header-rows: 1 - - * - Fields - - Type - - Required - - Default - - Value - * - name - - string - - True - - - - name for node group template - * - flavor - - string or object - - True - - - - name or id of flavor, or see `section "flavor"`_ - * - node_processes - - string - - True - - - - name of process - * - description - - string - - - - Empty - - description for node group - * - volumes_per_node - - integer - - - - 0 - - minimum 0 - * - volumes_size - - integer - - - - 0 - - minimum 0 - * - auto_security_group - - boolean - - - - True - - - * - security_group - - array - - - - - - security group - * - node_configs - - object - - - - - - name_of_config_section: config: value - * - availability_zone - - string - - - - - - - * - volumes_availability_zone - - string - - - - - - - * - volume_type - - string - - - - - - - * - is_proxy_gateway - - boolean - - - - False - - use this node as proxy gateway - * - edp_batching - - integer - - - - count jobs - - use for batching jobs - - -Section "flavor" ----------------- - -This section is an dictionary-type. - -+----------------+---------+----------+---------------+-----------------+ -| Fields | Type | Required | Default | Value | -+================+=========+==========+===============+=================+ -| name | string | | auto-generate | name for flavor | -+----------------+---------+----------+---------------+-----------------+ -| id | string | | auto-generate | id for flavor | -+----------------+---------+----------+---------------+-----------------+ -| vcpus | integer | | 1 | number of VCPUs | -| | | | | for the flavor | -+----------------+---------+----------+---------------+-----------------+ -| ram | integer | | 1 | memory in MB for| -| | | | | the flavor | -+----------------+---------+----------+---------------+-----------------+ -| root_disk | integer | | 0 | size of local | -| | | | | disk in GB | -+----------------+---------+----------+---------------+-----------------+ -| ephemeral_disk | integer | | 0 | ephemeral space | -| | | | | in MB | -+----------------+---------+----------+---------------+-----------------+ -| swap_disk | integer | | 0 | swap space in MB| -+----------------+---------+----------+---------------+-----------------+ - - -Section "cluster_template" --------------------------- - -This section is dictionary-type. - -.. list-table:: - :header-rows: 1 - - * - Fields - - Type - - Required - - Default - - Value - * - name - - string - - - - - - name for cluster template - * - description - - string - - - - Empty - - description - * - cluster_configs - - object - - - - - - name_of_config_section: config: value - * - node_group_templates - - object - - True - - - - name_of_node_group: count - * - anti_affinity - - array - - - - Empty - - array of roles - - -Section "cluster" ------------------ - -This section is dictionary-type. - -+--------------+---------+----------+---------+------------------+ -| Fields | Type | Required | Default | Value | -+==============+=========+==========+=========+==================+ -| name | string | | Empty | name for cluster | -+--------------+---------+----------+---------+------------------+ -| description | string | | Empty | description | -+--------------+---------+----------+---------+------------------+ -| is_transient | boolean | | False | value | -+--------------+---------+----------+---------+------------------+ - - -Section "scaling" ------------------ - -This section is an array-type. - -+------------+---------+----------+-----------+--------------------+ -| Fields | Type | Required | Default | Value | -+============+=========+==========+===========+====================+ -| operation | string | True | | "add" or "resize" | -+------------+---------+----------+-----------+--------------------+ -| node_group | string | True | Empty | name of node group | -+------------+---------+----------+-----------+--------------------+ -| size | integer | True | Empty | count node group | -+------------+---------+----------+-----------+--------------------+ - - -Section "edp_jobs_flow" ------------------------ - -This section has an object with a name from the `section "clusters"`_ -field "edp_jobs_flows" -Object has sections of array-type. -Required: type - -.. list-table:: - :header-rows: 1 - - * - Fields - - Type - - Required - - Default - - Value - * - type - - string - - True - - - - "Pig", "Java", "MapReduce", "MapReduce.Streaming", "Hive", "Spark", "Shell" - * - input_datasource - - object - - - - - - see `section "input_datasource"`_ - * - output_datasource - - object - - - - - - see `section "output_datasource"`_ - * - main_lib - - object - - - - - - see `section "main_lib"`_ - * - additional_libs - - object - - - - - - see `section "additional_libs"`_ - * - configs - - dict - - - - Empty - - config: value - * - args - - array - - - - Empty - - array of args - - -Section "input_datasource" --------------------------- - -Required: type, source -This section is dictionary-type. - -+---------------+--------+----------+-----------+---------------------------+ -| Fields | Type | Required | Default | Value | -+===============+========+==========+===========+===========================+ -| type | string | True | | "swift", "hdfs", "maprfs" | -+---------------+--------+----------+-----------+---------------------------+ -| hdfs_username | string | | | username for hdfs | -+---------------+--------+----------+-----------+---------------------------+ -| source | string | True | | uri of source | -+---------------+--------+----------+-----------+---------------------------+ - - -Section "output_datasource" ---------------------------- - -Required: type, destination -This section is dictionary-type. - -+-------------+--------+----------+-----------+---------------------------+ -| Fields | Type | Required | Default | Value | -+=============+========+==========+===========+===========================+ -| type | string | True | | "swift", "hdfs", "maprfs" | -+-------------+--------+----------+-----------+---------------------------+ -| destination | string | True | | uri of source | -+-------------+--------+----------+-----------+---------------------------+ - - -Section "main_lib" ------------------- - -Required: type, source -This section is dictionary-type. - -+--------+--------+----------+-----------+----------------------+ -| Fields | Type | Required | Default | Value | -+========+========+==========+===========+======================+ -| type | string | True | | "swift or "database" | -+--------+--------+----------+-----------+----------------------+ -| source | string | True | | uri of source | -+--------+--------+----------+-----------+----------------------+ - - -Section "additional_libs" -------------------------- - -Required: type, source -This section is an array-type. - -+--------+--------+----------+-----------+----------------------+ -| Fields | Type | Required | Default | Value | -+========+========+==========+===========+======================+ -| type | string | True | | "swift or "database" | -+--------+--------+----------+-----------+----------------------+ -| source | string | True | | uri of source | -+--------+--------+----------+-----------+----------------------+ diff --git a/doc/source/tempest-plugin.rst b/doc/source/tempest-plugin.rst deleted file mode 100644 index 90e1de57..00000000 --- a/doc/source/tempest-plugin.rst +++ /dev/null @@ -1,76 +0,0 @@ -Tempest Integration of Sahara -============================= - -Sahara Tempest plugin contains api, cli and clients tests. - -There are several ways to run Tempest tests: it is possible to run them using -your Devstack or using Rally. - -Run Tempest tests on Devstack ------------------------------ - -See how to configure Tempest at -`Tempest Configuration Guide `_. - -Tempest automatically discovers installed plugins. That's why you just need to -install the Python packages that contains the Sahara Tempest plugin in the -same environment where Tempest is installed. - -.. sourcecode:: console - - $ git clone https://opendev.org/openstack/sahara-tests - $ cd sahara-tests/ - $ pip install sahara-tests/ - -.. - -After that you can run Tempest tests. You can specify the name of -test or a more complex regular expression. While any ``testr``-based -test runner can be used, the official command for executing Tempest -tests is ``tempest run``. - -For example, the following command will run a specific subset of tests: - -.. sourcecode:: console - - $ tempest run --regex '^sahara_tempest_plugin.tests.cli.test_scenario.Scenario.' - -.. - -The full syntax of ``tempest run`` is described on `the relavant section of -the Tempest documentation `_. - -Other useful links: - -* `Using Tempest plugins `_. -* `Tempest Quickstart `_. - -Run Tempest tests using Rally ------------------------------ - -First of all, be sure that Rally is installed and working. There should be -a Rally deployment with correct working Sahara service in it. - -Full information can be found on the -`rally quick start guide `_. - -Using this information, you can install ``rally verify`` tool and plugin for -testing Sahara. After this you are free to run Sahara Tempest tests. Here are -some examples of how to run all the tests: - -.. sourcecode:: console - - $ rally verify start --pattern sahara_tempest_plugin.tests - -.. - -If you want to run client or cli tests, you need to add the following line to -generated config in ``[data-processing]`` field: - -.. sourcecode:: bash - - test_image_name = IMAGE_NAME - -.. - -where ``IMAGE_NAME`` is the name of image on which you would like to run tests. diff --git a/etc/scenario/gate/README.rst b/etc/scenario/gate/README.rst deleted file mode 100644 index e69de29b..00000000 diff --git a/etc/scenario/gate/credentials.yaml.mako b/etc/scenario/gate/credentials.yaml.mako deleted file mode 100644 index 69206e78..00000000 --- a/etc/scenario/gate/credentials.yaml.mako +++ /dev/null @@ -1,3 +0,0 @@ -network: - private_network: ${network_private_name} - public_network: ${network_public_name} diff --git a/etc/scenario/gate/credentials_s3.yaml.mako b/etc/scenario/gate/credentials_s3.yaml.mako deleted file mode 100644 index 48e01da7..00000000 --- a/etc/scenario/gate/credentials_s3.yaml.mako +++ /dev/null @@ -1,6 +0,0 @@ -credentials: - s3_accesskey: ${s3_accesskey} - s3_secretkey: ${s3_secretkey} - s3_endpoint: ${s3_endpoint} - s3_endpoint_ssl: ${s3_endpoint_ssl} - s3_bucket_path: ${s3_bucket_path} diff --git a/etc/scenario/gate/edp.yaml.mako b/etc/scenario/gate/edp.yaml.mako deleted file mode 100644 index 615139a1..00000000 --- a/etc/scenario/gate/edp.yaml.mako +++ /dev/null @@ -1,43 +0,0 @@ -edp_jobs_flow: - fake: - - type: Pig - input_datasource: - type: swift - source: edp-examples/edp-pig/cleanup-string/data/input - output_datasource: - type: hdfs - destination: /user/hadoop/edp-output - main_lib: - type: swift - source: edp-examples/edp-pig/cleanup-string/example.pig - additional_libs: - - type: swift - source: edp-examples/edp-pig/cleanup-string/edp-pig-udf-stringcleaner.jar - spark_pi: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-pi.py - configs: - edp.java.main_class: main - args: - - 2 - spark_wordcount: - - type: Spark - input_datasource: - type: swift - source: edp-examples/edp-spark/sample_input.txt - output_datasource: - type: swift - destination: edp-output - main_lib: - type: database - source: edp-examples/edp-spark/spark-wordcount.jar - configs: - edp.java.main_class: sahara.edp.spark.SparkWordCount - edp.spark.adapt_for_swift: true - fs.swift.service.sahara.username: ${os_username} - fs.swift.service.sahara.password: ${os_password} - args: - - '{input_datasource}' - - '{output_datasource}' diff --git a/etc/scenario/gate/edp_s3.yaml.mako b/etc/scenario/gate/edp_s3.yaml.mako deleted file mode 100644 index 2a3aa11a..00000000 --- a/etc/scenario/gate/edp_s3.yaml.mako +++ /dev/null @@ -1,25 +0,0 @@ -edp_jobs_flow: - spark_wordcount_s3: - - type: Spark - input_datasource: - type: s3 - source: edp-examples/edp-spark/sample_input.txt - output_datasource: - type: swift - destination: edp-output - main_lib: - type: swift - source: edp-examples/edp-spark/spark-wordcount.jar - configs: - edp.java.main_class: sahara.edp.spark.SparkWordCount - edp.spark.adapt_for_swift: true - fs.swift.service.sahara.username: ${os_username} - fs.swift.service.sahara.password: ${os_password} - fs.s3a.access.key: ${s3_accesskey} - fs.s3a.secret.key: ${s3_secretkey} - fs.s3a.endpoint: ${s3_endpoint} - fs.s3a.connection.ssl.enabled: ${s3_endpoint_ssl} - fs.s3a.path.style.access: ${s3_bucket_path} - args: - - '{input_datasource}' - - '{output_datasource}' diff --git a/etc/scenario/gate/fake.yaml.mako b/etc/scenario/gate/fake.yaml.mako deleted file mode 100644 index d9dba287..00000000 --- a/etc/scenario/gate/fake.yaml.mako +++ /dev/null @@ -1,35 +0,0 @@ -<%page args="is_transient='false', use_auto_security_group='true', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: fake - plugin_version: "0.1" - image: ${plugin_image} - node_group_templates: - - name: aio - flavor: ${ci_flavor_id} - node_processes: - - namenode - - jobtracker - - datanode - - tasktracker - volumes_per_node: 2 - volumes_size: 1 - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - jobtracker - auto_security_group: ${use_auto_security_group} - cluster_template: - name: fake01 - node_group_templates: - aio: 1 - cluster: - name: ${cluster_name} - is_transient: ${is_transient} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: fake diff --git a/etc/scenario/gate/spark-1.6.0.yaml.mako b/etc/scenario/gate/spark-1.6.0.yaml.mako deleted file mode 100644 index 29929223..00000000 --- a/etc/scenario/gate/spark-1.6.0.yaml.mako +++ /dev/null @@ -1,41 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', cluster_name='ct', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: spark - plugin_version: 1.6.0 - image: ${plugin_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - master - - namenode - - datanode - - slave - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - slave - auto_security_group: ${use_auto_security_group} - cluster_template: - name: spark160 - node_group_templates: - master: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - scaling: - - operation: add - node_group: worker - size: 1 - scenario: - - run_jobs - - scale - edp_jobs_flow: - - spark_pi - - spark_wordcount - cluster: - name: ${cluster_name} diff --git a/etc/scenario/simple-testcase.yaml b/etc/scenario/simple-testcase.yaml deleted file mode 100644 index 50dde9cb..00000000 --- a/etc/scenario/simple-testcase.yaml +++ /dev/null @@ -1,89 +0,0 @@ -concurrency: 1 - -network: - private_network: private - public_network: public - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.1 - image: sahara-liberty-vanilla-2.7.1-ubuntu-14.04 - edp_jobs_flow: test_flow - - plugin_name: hdp - plugin_version: 2.0.6 - image: f3c4a228-9ba4-41f1-b100-a0587689d4dd - scaling: - - operation: resize - node_group: hdp-worker - size: 5 - - plugin_name: cdh - plugin_version: 5.3.0 - image: ubuntu-cdh-5.3.0 - scaling: - - operation: add - node_group: cdh-worker - size: 1 - edp_jobs_flow: - - test_flow - - name: test_manila - features: manila - -edp_jobs_flow: - test_flow: - - type: Pig - input_datasource: - type: swift - source: etc/edp-examples/edp-pig/top-todoers/data/input - output_datasource: - type: hdfs - destination: /user/hadoop/edp-output - main_lib: - type: swift - source: etc/edp-examples/edp-pig/top-todoers/example.pig - configs: - dfs.replication: 1 - - type: Java - additional_libs: - - type: database - source: etc/edp-examples/hadoop2/edp-java/hadoop-mapreduce-examples-2.7.1.jar - configs: - edp.java.main_class: org.apache.hadoop.examples.QuasiMonteCarlo - args: - - 10 - - 10 - - type: MapReduce - input_datasource: - type: swift - source: etc/edp-examples/edp-pig/trim-spaces/data/input - output_datasource: - type: hdfs - destination: /user/hadoop/edp-output - additional_libs: - - type: database - source: etc/edp-examples/edp-mapreduce/edp-mapreduce.jar - configs: - mapred.mapper.class: org.apache.oozie.example.SampleMapper - mapred.reducer.class: org.apache.oozie.example.SampleReducer - - type: MapReduce.Streaming - input_datasource: - type: swift - source: etc/edp-examples/edp-pig/trim-spaces/data/input - output_datasource: - type: hdfs - destination: /user/hadoop/edp-output - configs: - edp.streaming.mapper: /bin/cat - edp.streaming.reducer: /usr/bin/wc - test_manila: - - type: Pig - input_datasource: - type: manila - source: etc/edp-examples/edp-pig/top-todoers/data/input - output_datasource: - type: manila - destination: /user/hadoop/edp-output - main_lib: - type: manila - source: etc/edp-examples/edp-pig/top-todoers/example.pig - configs: - dfs.replication: 1 diff --git a/playbooks/sahara-tests-scenario.yaml b/playbooks/sahara-tests-scenario.yaml deleted file mode 100644 index 8851df18..00000000 --- a/playbooks/sahara-tests-scenario.yaml +++ /dev/null @@ -1,19 +0,0 @@ -- hosts: all - strategy: linear - roles: - - orchestrate-devstack - -- hosts: controller - tasks: - - name: build the required image - include_role: - name: build-sahara-images-dib - when: sahara_plugin is defined and sahara_plugin != 'fake' - - name: setup the sahara Scenario test environment - include_role: - name: setup-sahara-scenario-env - - include_role: - name: ensure-tox - - name: run the Sahara scenario tests - include_role: - name: run-sahara-scenario diff --git a/releasenotes/notes/add-ability-to-use-clouds-yaml-5f66a2c3959f894b.yaml b/releasenotes/notes/add-ability-to-use-clouds-yaml-5f66a2c3959f894b.yaml deleted file mode 100644 index ba95cd2c..00000000 --- a/releasenotes/notes/add-ability-to-use-clouds-yaml-5f66a2c3959f894b.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -prelude: > - Added ability to use clouds.yaml with scenario tests -features: - - User can now use clouds.yaml in the format defined - by os-client-config to specify the auth values - wanted on a scenario test. diff --git a/releasenotes/notes/add-cdh-513-c4feaf4f360122ff.yaml b/releasenotes/notes/add-cdh-513-c4feaf4f360122ff.yaml deleted file mode 100644 index f02c9c07..00000000 --- a/releasenotes/notes/add-cdh-513-c4feaf4f360122ff.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -features: - - New basic scenario test template for CDH 5.13.0. diff --git a/releasenotes/notes/add-cli-for-ng-cbdcdddd5195c333.yaml b/releasenotes/notes/add-cli-for-ng-cbdcdddd5195c333.yaml deleted file mode 100644 index 886eaf22..00000000 --- a/releasenotes/notes/add-cli-for-ng-cbdcdddd5195c333.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -prelude: > - Create new cli test in Sahara Tempest plugin for node group templates -features: - - Now we can filter node group template with column flag by plugin - and check if it was filtered successfully diff --git a/releasenotes/notes/drop-py-2-7-188a5c85a436ee4f.yaml b/releasenotes/notes/drop-py-2-7-188a5c85a436ee4f.yaml deleted file mode 100644 index 301a5df0..00000000 --- a/releasenotes/notes/drop-py-2-7-188a5c85a436ee4f.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -upgrade: - - | - Python 2.7 support has been dropped. Last release of sahara-tests - to support python 2.7 is 0.9.1, the first version that can be used - with OpenStack Train. - The minimum version of Python now supported by sahara-tests is Python 3.5. diff --git a/releasenotes/notes/fix-bug-1644747-5f9790ad2a0e5e56.yaml b/releasenotes/notes/fix-bug-1644747-5f9790ad2a0e5e56.yaml deleted file mode 100644 index 72db46a2..00000000 --- a/releasenotes/notes/fix-bug-1644747-5f9790ad2a0e5e56.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -prelude: > - Clean resources created in CLI tests -fixes: - - Fix bug in which after running CLI tests, - projects and networks created for these tests - were not deleted at the end of the run. diff --git a/releasenotes/notes/fix-datasource-discovery-b690920189f0b161.yaml b/releasenotes/notes/fix-datasource-discovery-b690920189f0b161.yaml deleted file mode 100644 index 0bb7070b..00000000 --- a/releasenotes/notes/fix-datasource-discovery-b690920189f0b161.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -prelude: > - Discovery of data sources with relative paths is now fixed. -fixes: - - Datasources with relative paths are now properly found - from the default resources. diff --git a/releasenotes/notes/fix-installed-resource-discovery-36ffa157cf9b06b9.yaml b/releasenotes/notes/fix-installed-resource-discovery-36ffa157cf9b06b9.yaml deleted file mode 100644 index 29aa2b2a..00000000 --- a/releasenotes/notes/fix-installed-resource-discovery-36ffa157cf9b06b9.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -prelude: > - Fix default resource discovery from the installed package. -fixes: - - The default set of resources (test templates for each plugin, etc) - can now be properly discovered when the package is installed. diff --git a/releasenotes/notes/fix-ngt-create-cli-test-with-multiple-plugin-version-13ed695ad2d8fab1.yaml b/releasenotes/notes/fix-ngt-create-cli-test-with-multiple-plugin-version-13ed695ad2d8fab1.yaml deleted file mode 100644 index ac9bac21..00000000 --- a/releasenotes/notes/fix-ngt-create-cli-test-with-multiple-plugin-version-13ed695ad2d8fab1.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -fixes: - - | - The node group template creation test for the CLI client does not fail - anymore when the Sahara plugin selected for testing provides more than - one version. diff --git a/releasenotes/notes/fix-storm-scenario-template-2d28644c9fd2ec06.yaml b/releasenotes/notes/fix-storm-scenario-template-2d28644c9fd2ec06.yaml deleted file mode 100644 index b4080ea5..00000000 --- a/releasenotes/notes/fix-storm-scenario-template-2d28644c9fd2ec06.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -fixes: - - | - Fix the test template for storm 1.2. diff --git a/releasenotes/notes/force-pem-ssh-keys-2cc9eb30a76c8dd1.yaml b/releasenotes/notes/force-pem-ssh-keys-2cc9eb30a76c8dd1.yaml deleted file mode 100644 index e305635a..00000000 --- a/releasenotes/notes/force-pem-ssh-keys-2cc9eb30a76c8dd1.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -fixes: - - | - Force the PEM format for the generated ssh keys, - because paramiko does not yet support the new one - (https://github.com/paramiko/paramiko/issues/602). diff --git a/releasenotes/notes/import-tempest-client-tests-9042d5d50bbdce9e.yaml b/releasenotes/notes/import-tempest-client-tests-9042d5d50bbdce9e.yaml deleted file mode 100644 index 11447739..00000000 --- a/releasenotes/notes/import-tempest-client-tests-9042d5d50bbdce9e.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -prelude: > - Client tests have been imported from the Sahara repository. -features: - - The tests for the official Python clients have been moved here - from the Sahara repository. They are based on the Tempest - libraries even they do not follow the Tempest guidelines - (as they need to test the Python clients, they do not use - the native Tempest clients). diff --git a/releasenotes/notes/increase-cluster-timeout-0e97fb7d7a6ea75a.yaml b/releasenotes/notes/increase-cluster-timeout-0e97fb7d7a6ea75a.yaml deleted file mode 100644 index 139222c2..00000000 --- a/releasenotes/notes/increase-cluster-timeout-0e97fb7d7a6ea75a.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -other: - - The default timeout for cluster polling was raised from 1800 - to 3600 seconds. diff --git a/releasenotes/notes/migrate-to-keystoneauth-c65c162d74b5d7b9.yaml b/releasenotes/notes/migrate-to-keystoneauth-c65c162d74b5d7b9.yaml deleted file mode 100644 index 1051ae6b..00000000 --- a/releasenotes/notes/migrate-to-keystoneauth-c65c162d74b5d7b9.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -prelude: > - Migrate auth system from keystoneclient to keystoneauth \ No newline at end of file diff --git a/releasenotes/notes/migrated-sahara-cli-tests-a2b32eeb6ec11512.yaml b/releasenotes/notes/migrated-sahara-cli-tests-a2b32eeb6ec11512.yaml deleted file mode 100644 index 455ae69e..00000000 --- a/releasenotes/notes/migrated-sahara-cli-tests-a2b32eeb6ec11512.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -prelude: > - Migrate sahara cli tests from saharaclient to sahara-tests diff --git a/releasenotes/notes/more-auth-fixes-9ba23ceb54ba042a.yaml b/releasenotes/notes/more-auth-fixes-9ba23ceb54ba042a.yaml deleted file mode 100644 index da02e1a8..00000000 --- a/releasenotes/notes/more-auth-fixes-9ba23ceb54ba042a.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -fixes: - - | - Properly handle more use cases when only Keystone v3 is enabled and/or - its service URI is missing the /v3 suffix. diff --git a/releasenotes/notes/new-cli-tests-job-type-4fdc68dfda728cd3.yaml b/releasenotes/notes/new-cli-tests-job-type-4fdc68dfda728cd3.yaml deleted file mode 100644 index 58824aa0..00000000 --- a/releasenotes/notes/new-cli-tests-job-type-4fdc68dfda728cd3.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -prelude: > - Create new cli tests in Sahara Tempest plugin for job types -features: - - Now we can filter job types with column flag and also save config file of - any job type to the specified file. diff --git a/releasenotes/notes/novaclient_images_to_glanceclient-9f8351330147f770.yaml b/releasenotes/notes/novaclient_images_to_glanceclient-9f8351330147f770.yaml deleted file mode 100644 index c981395d..00000000 --- a/releasenotes/notes/novaclient_images_to_glanceclient-9f8351330147f770.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -upgrade: - - Migration from novaclient.v2.images to glanceclient diff --git a/releasenotes/notes/post-030-preocata-40c5fbf4d3522074.yaml b/releasenotes/notes/post-030-preocata-40c5fbf4d3522074.yaml deleted file mode 100644 index 7609fd3b..00000000 --- a/releasenotes/notes/post-030-preocata-40c5fbf4d3522074.yaml +++ /dev/null @@ -1,35 +0,0 @@ ---- -prelude: | - Fixes and addition for the API and CLI tests. - - Support for CDH 5.9 in scenario tests. - - Less parameters required for scenario tests. -features: - - The Tempest-based tests have received an increase of the coverage - for both API and CLI tests (job, plugins, templates; negative - testing). - - CDH 5.9 can be tested thanks to the addition of the specific - templates. - - Few parameters are not required anymore by the templates in scenario - tests; a default value is provided (name of templates, etc). - - The flavors used in templates are now parameters too. - - If a flavor name is provided in addition to its specification and - a flavor with that name exists, it is used and not created again. - - The dependencies on non-public Tempest interfaces have been - removed. -upgrade: - - the name of the variables/parameters used for the name of - the images in the scenario tests have been changes to follow - a more consistent pattern. -critical: - - The Tempest plugin was fixed after the removal of - the data_processing plugin from the tempest repository. - Most of the work was in place, only a small change was - missing. -fixes: - - The artifacts created during the execution of CLI tests - are properly cleaned at the end of the tests. -other: - - The documentation was improved (scenario tests) and - extended (Tempest plugin). diff --git a/releasenotes/notes/remove-nova-network-09a2afd1efd83e05.yaml b/releasenotes/notes/remove-nova-network-09a2afd1efd83e05.yaml deleted file mode 100644 index 0dfdc77b..00000000 --- a/releasenotes/notes/remove-nova-network-09a2afd1efd83e05.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -upgrade: - - | - Removed nova-network configuration. Nova network has been fully - removed from the OpenStack codebase, in all releases supported - by sahara-tests. diff --git a/releasenotes/notes/remove-old-yaml-files-97ecb4a11582f033.yaml b/releasenotes/notes/remove-old-yaml-files-97ecb4a11582f033.yaml deleted file mode 100644 index 3cda05e6..00000000 --- a/releasenotes/notes/remove-old-yaml-files-97ecb4a11582f033.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -prelude: > - Yaml-files for deprecated plugins was removed -fixes: - - Removed yaml-files for Kilo release - - Removed unused yaml-files for master branch diff --git a/releasenotes/notes/sahara-api-tempest-tests-as-plugin-25d63ac04902f1ad.yaml b/releasenotes/notes/sahara-api-tempest-tests-as-plugin-25d63ac04902f1ad.yaml deleted file mode 100644 index ec45f86a..00000000 --- a/releasenotes/notes/sahara-api-tempest-tests-as-plugin-25d63ac04902f1ad.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -features: - - Sahara API tests have been imported from Tempest and - made available using the Tempest Plugin Interface. diff --git a/releasenotes/notes/sahara-scenario-fix-runner-59350d608d19caf9.yaml b/releasenotes/notes/sahara-scenario-fix-runner-59350d608d19caf9.yaml deleted file mode 100644 index c75cc34f..00000000 --- a/releasenotes/notes/sahara-scenario-fix-runner-59350d608d19caf9.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -fixes: - - | - Fix a strange error where the internally generated test does not start - because sahara_tests.scenario is not found when running inside a virtualenv. diff --git a/releasenotes/notes/sahara-tempest-tests-apiv2-c9878e0ba37fba2a.yaml b/releasenotes/notes/sahara-tempest-tests-apiv2-c9878e0ba37fba2a.yaml deleted file mode 100644 index 46c79804..00000000 --- a/releasenotes/notes/sahara-tempest-tests-apiv2-c9878e0ba37fba2a.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -prelude: > - Tempest tests now support APIv2. -features: - - | - The Tempest plugin provides an APIv2 DataProcessing client and - tempest tests can be executed against APIv2 too. - The type of API used is driven by a tempest.conf configuration key - (data_processing.use_api_v2 for API tests, - data_processing.api_version_saharaclient for client and CLI tests) diff --git a/releasenotes/notes/saharaclient-version-75ca47761f133743.yaml b/releasenotes/notes/saharaclient-version-75ca47761f133743.yaml deleted file mode 100644 index 60ab9d0b..00000000 --- a/releasenotes/notes/saharaclient-version-75ca47761f133743.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -features: - - | - The ``api_version_saharaclient`` variable now controls the Sahara API - version used not only by the Tempest.lib-based clients tests, - but also by the Tempest CLI tests. -deprecations: - - | - The ``saharaclient_version`` option in the ``data-processing`` group - has been renamed to ``api_version_saharaclient``. diff --git a/releasenotes/notes/scenario-boot-from-volume-a87c680b03f560a0.yaml b/releasenotes/notes/scenario-boot-from-volume-a87c680b03f560a0.yaml deleted file mode 100644 index bc135c88..00000000 --- a/releasenotes/notes/scenario-boot-from-volume-a87c680b03f560a0.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -features: - - | - Allow to enable boot_from_volume on node group templates - when running scenario tests with APIv2. diff --git a/releasenotes/notes/scenario-feature-sets-ac3e0e9e40b236bb.yaml b/releasenotes/notes/scenario-feature-sets-ac3e0e9e40b236bb.yaml deleted file mode 100644 index 974ff0a3..00000000 --- a/releasenotes/notes/scenario-feature-sets-ac3e0e9e40b236bb.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -features: - - | - sahara-scenario supports feature sets. When passing specific - feature tags to sahara-scenario, additional job templates and - EDP jobs marked with those tags will be loaded. -fixes: - - | - When passing the plugin/version parameters to sahara-scenario, - users can now specify additional YAML templates which will be merged - to the default YAMLs, instead of being ignored. diff --git a/releasenotes/notes/scenario-runner-templatesdebug-5ee5e2447cff770a.yaml b/releasenotes/notes/scenario-runner-templatesdebug-5ee5e2447cff770a.yaml deleted file mode 100644 index b3257e4a..00000000 --- a/releasenotes/notes/scenario-runner-templatesdebug-5ee5e2447cff770a.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -features: - - | - The fully generated YAML file is printed when the verbose mode is enabled. diff --git a/releasenotes/notes/scenario-s3-20d41440d84af3a9.yaml b/releasenotes/notes/scenario-s3-20d41440d84af3a9.yaml deleted file mode 100644 index 1b292a3c..00000000 --- a/releasenotes/notes/scenario-s3-20d41440d84af3a9.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -features: - - | - sahara-scenario now supports testing the S3 API for job binaries - and data sources, a feature introduced in Rocky. - The code can be enabled using the "s3" feature and - various templates now runs an S3-based job too when - the feature is enabled from the command line. diff --git a/releasenotes/notes/scenario-tests-apiv2-c71c72f51c667075.yaml b/releasenotes/notes/scenario-tests-apiv2-c71c72f51c667075.yaml deleted file mode 100644 index 5ca5c88f..00000000 --- a/releasenotes/notes/scenario-tests-apiv2-c71c72f51c667075.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -prelude: > - Scenario tests now support APIv2. -features: - - | - Scenario tests can be executed against APIv2. - The usage of APIv2 is enabled through a new command line argument - for sahara-scenario (--v2, -2). diff --git a/releasenotes/notes/simplify-testrunner-config-d3a19014db107ff1.yaml b/releasenotes/notes/simplify-testrunner-config-d3a19014db107ff1.yaml deleted file mode 100644 index 9a9981b3..00000000 --- a/releasenotes/notes/simplify-testrunner-config-d3a19014db107ff1.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -prelude: > - Removed the need of a .testr.conf file when calling the - test runner. -fixes: - - A .testr.conf file was previously required in the runner - execution directory, now this is handled internally. diff --git a/releasenotes/notes/start-using-reno-ef5696b99f56dc5e.yaml b/releasenotes/notes/start-using-reno-ef5696b99f56dc5e.yaml deleted file mode 100644 index 25ba5843..00000000 --- a/releasenotes/notes/start-using-reno-ef5696b99f56dc5e.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -other: - - OpenStack reno integration was added for managing release notes diff --git a/releasenotes/notes/support-non-fake-plugin-a4ba718b787246e8.yaml b/releasenotes/notes/support-non-fake-plugin-a4ba718b787246e8.yaml deleted file mode 100644 index 8e31cc23..00000000 --- a/releasenotes/notes/support-non-fake-plugin-a4ba718b787246e8.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -prelude: > - Tests no longer depend on fake plugin to run -other: - - Adapt Sahara Tests code to stop relying only on the fake - plugin and use the default plugin available. However, it's - worth noting that - if available - the fake plugin will - be used. diff --git a/releasenotes/notes/switch-to-stestr-6de59dc9e46768f6.yaml b/releasenotes/notes/switch-to-stestr-6de59dc9e46768f6.yaml deleted file mode 100644 index 31a7f9ec..00000000 --- a/releasenotes/notes/switch-to-stestr-6de59dc9e46768f6.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -upgrade: - - | - sahara-scenario now requires stestr. diff --git a/releasenotes/notes/tempest-basic-s3-636953aa1bb198d7.yaml b/releasenotes/notes/tempest-basic-s3-636953aa1bb198d7.yaml deleted file mode 100644 index 5591140e..00000000 --- a/releasenotes/notes/tempest-basic-s3-636953aa1bb198d7.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -features: - - | - Added basic S3 API tests (job binaries and data sources) to the Tempest - plugin. The tests are disabled by default and can be enabled using - a new tempest.conf key (data-processing-feature-enabled.s3). diff --git a/releasenotes/notes/tempest-clients-remove-scenario-manager-4e9ea9dbf585b8cf.yaml b/releasenotes/notes/tempest-clients-remove-scenario-manager-4e9ea9dbf585b8cf.yaml deleted file mode 100644 index bd25e5c7..00000000 --- a/releasenotes/notes/tempest-clients-remove-scenario-manager-4e9ea9dbf585b8cf.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -other: - - The dependency on tempest.scenario.manager.py has been - removed. There are still dependencies on internal - Tempest interfaces but they are more difficult to replace - due to lack of tempest.lib alternatives, and - scenario.manager.py is undergoing an heavy refactoring. diff --git a/releasenotes/notes/test-templates-ocata-dcc438604c94b72f.yaml b/releasenotes/notes/test-templates-ocata-dcc438604c94b72f.yaml deleted file mode 100644 index dcf2b95f..00000000 --- a/releasenotes/notes/test-templates-ocata-dcc438604c94b72f.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -prelude: > - Ocata test templates are now available, while Liberty - test templates have been removed. -features: - - A folder with test templates with Ocata has been created - and initialized starting from the templates in the main - directory, following the status of the jobs tested on - the Sahara CI. -deprecations: - - The Liberty-specific job templates have been removed. - This means that starting from this release Liberty is - not supported (it has been EOL for a while). - - The MapR 5.0.0 test template have been removed from - the master branch as well. diff --git a/releasenotes/notes/test-templates-pike-4e3e78362f49cb6e.yaml b/releasenotes/notes/test-templates-pike-4e3e78362f49cb6e.yaml deleted file mode 100644 index 7eedf60c..00000000 --- a/releasenotes/notes/test-templates-pike-4e3e78362f49cb6e.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -prelude: > - Pike test templates are now available, while Mitaka - test templates have been removed. -features: - - A folder with scenario templates for Pike was added. - It is a subset of the templates in the main directory. - - Some requirements have been raised (especially Tempest). -deprecations: - - The Mitaka-specific job templates have been removed. - This means that starting from this release Mitaka is - not supported (it has been EOL for a while). diff --git a/releasenotes/notes/test-templates-queens-901f1af3cf80f98d.yaml b/releasenotes/notes/test-templates-queens-901f1af3cf80f98d.yaml deleted file mode 100644 index 7bf7300f..00000000 --- a/releasenotes/notes/test-templates-queens-901f1af3cf80f98d.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -prelude: > - Queens test templates are now available, while Newton - test templates have been removed. -features: - - A folder with scenario templates for Queens was added. - It is a subset of the templates in the main directory. - - The available templates now supports Spark 2.2 - and Vanilla 2.8.2. -deprecations: - - The Newton-specific job templates have been removed. - This means that starting from this release Newton is - not supported (it has been EOL for a while). diff --git a/releasenotes/notes/test-templates-rocky-c271d2acb0d3c42a.yaml b/releasenotes/notes/test-templates-rocky-c271d2acb0d3c42a.yaml deleted file mode 100644 index e0a1dad3..00000000 --- a/releasenotes/notes/test-templates-rocky-c271d2acb0d3c42a.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -prelude: > - Rocky test templates are now available. -features: - - A folder with scenario templates for Rocky was added. - It is a subset of the templates in the main directory, - and includes all non-deprecated plugin/versions. - - The available default test templates now supports also - Spark 2.3, Storm 1.2, Vanilla 2.7.5, and the Ambari-based - HDP 2.6 and 2.5. -fixes: - - The CDH 5.11 test template, previously only available for queens, - was added to main (unversioned) templates directory. -upgrades: - - All the templates for deprecated version/plugins were removed - from the main (unversioned) templates directory. diff --git a/releasenotes/notes/test-templates-stein-751a4344bfcca652.yaml b/releasenotes/notes/test-templates-stein-751a4344bfcca652.yaml deleted file mode 100644 index 5cd29d0a..00000000 --- a/releasenotes/notes/test-templates-stein-751a4344bfcca652.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -prelude: > - Stein test templates are now available, while Ocata - test templates have been removed. -features: - - A folder with scenario templates for Stein has been added. - It is a subset of the templates in the main directory. -deprecations: - - The Ocata-specific job templates have been removed. - This means that starting from this release Ocata is - not supported (it is under Extended Maintenance now). diff --git a/releasenotes/notes/testing-with-no-public-network-5b7df696fbbd4386.yaml b/releasenotes/notes/testing-with-no-public-network-5b7df696fbbd4386.yaml deleted file mode 100644 index e7445911..00000000 --- a/releasenotes/notes/testing-with-no-public-network-5b7df696fbbd4386.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -features: - - | - The public network field can be omitted from the configuration file of - the scenario, enabling the testing when only the project network is used. diff --git a/releasenotes/notes/track-steps-timestamps-56c40d06c49dc88e.yaml b/releasenotes/notes/track-steps-timestamps-56c40d06c49dc88e.yaml deleted file mode 100644 index ae7b4b71..00000000 --- a/releasenotes/notes/track-steps-timestamps-56c40d06c49dc88e.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -features: - - Capture and report the timestamp in scenario tests when - an event starts and when an exception is triggered. diff --git a/releasenotes/notes/update-plugin-version-def-96979000c20b2db8.yaml b/releasenotes/notes/update-plugin-version-def-96979000c20b2db8.yaml deleted file mode 100644 index b82db9c2..00000000 --- a/releasenotes/notes/update-plugin-version-def-96979000c20b2db8.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -other: - - | - Updated the list of supported plugin/versions used by some Tempest - and Tempest-based tests to cover all the combinations available in the - Sahara releases supported by sahara-tests. diff --git a/releasenotes/notes/update-test-templates-ocata-3707688601c11f21.yaml b/releasenotes/notes/update-test-templates-ocata-3707688601c11f21.yaml deleted file mode 100644 index b7aeb744..00000000 --- a/releasenotes/notes/update-test-templates-ocata-3707688601c11f21.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -prelude: > - Long overdue general updates of the test templates -fixes: - - The default templates used by tests have been updated; - added (MapR, Ambari and Storm, and some versions of CDH), - or removed (obsolete versions of Vanilla and CDH). diff --git a/releasenotes/notes/use-tempest-stable-interface-aefdfd8f81361dc8.yaml b/releasenotes/notes/use-tempest-stable-interface-aefdfd8f81361dc8.yaml deleted file mode 100644 index 9d91b54d..00000000 --- a/releasenotes/notes/use-tempest-stable-interface-aefdfd8f81361dc8.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -prelude: > - Sahara Tests plugin now uses tempest stable interface -other: - - Sahara Tests plugin is adapted to use in-tree client, - which was migrated from Tempest code. Also, there's a - new stable interface for Service Clients in Tempest, so - this change adapts the code to use it. diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder deleted file mode 100644 index e69de29b..00000000 diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder deleted file mode 100644 index e69de29b..00000000 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py deleted file mode 100644 index 3ebde8ab..00000000 --- a/releasenotes/source/conf.py +++ /dev/null @@ -1,253 +0,0 @@ -# -*- coding: utf-8 -*- -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Sahara-tests Release Notes documentation build configuration file - -extensions = [ - 'reno.sphinxext', - 'openstackdocstheme' -] - -# openstackdocstheme options -openstackdocs_repo_name = 'openstack/sahara-tests' -openstackdocs_auto_name = False -openstackdocs_use_storyboard = True - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Sahara-tests' -copyright = u'2016, Sahara Developers' - -# Release do not need a version number in the title, they -# cover multiple versions. -# The short X.Y version. -version = '' -# The full version, including alpha/beta/rc tags. -release = '' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'native' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'openstackdocs' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Sahara-testsdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # 'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'Sahara-tests.tex', u'Sahara-tests Documentation', - u'Sahara Developers', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'sahara-tests', u'Sahara-tests Documentation', - [u'Sahara Developers'], 1) -] - -# If true, show URL addresses after external links. -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'Sahara-tests', u'Sahara-tests Documentation', - u'Sahara Developers', 'Sahara-tests', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False - -# -- Options for Internationalization output ------------------------------ -locale_dirs = ['locale/'] diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst deleted file mode 100644 index 490b1dbf..00000000 --- a/releasenotes/source/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -Welcome to Sahara-tests's documentation! -======================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - unreleased diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst deleted file mode 100644 index cd22aabc..00000000 --- a/releasenotes/source/unreleased.rst +++ /dev/null @@ -1,5 +0,0 @@ -============================== - Current Series Release Notes -============================== - -.. release-notes:: diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0c89a283..00000000 --- a/requirements.txt +++ /dev/null @@ -1,27 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -pbr>=1.6 # Apache-2.0 - -Mako>=0.4.0 # MIT -botocore>=1.5.1 # Apache-2.0 -fixtures>=3.0.0 # Apache-2.0/BSD -jsonschema>=3.2.0 # MIT -keystoneauth1>=2.1.0 # Apache-2.0 -oslo.concurrency>=3.5.0 # Apache-2.0 -oslo.serialization>=1.10.0 # Apache-2.0 -oslo.utils>=3.5.0 # Apache-2.0 -oslotest>=1.10.0 # Apache-2.0 -os-client-config>=1.13.1 # Apache-2.0 -paramiko>=1.16.0 # LGPL -python-glanceclient>=2.0.0 # Apache-2.0 -python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 -python-saharaclient>=0.13.0 # Apache-2.0 -python-swiftclient>=2.2.0 # Apache-2.0 -python-neutronclient>=4.2.0 # Apache-2.0 -rfc3986>=0.2.0 # Apache-2.0 -six>=1.9.0 # MIT -stestr>=1.0.0 # Apache-2.0 -tempest>=16.0.0 # Apache-2.0 -testtools>=1.4.0 # MIT diff --git a/roles/run-sahara-scenario/defaults/main.yaml b/roles/run-sahara-scenario/defaults/main.yaml deleted file mode 100644 index 9ec2f0b5..00000000 --- a/roles/run-sahara-scenario/defaults/main.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -sahara_tests_src_dir: "{{ zuul.projects['opendev.org/openstack/sahara-tests'].src_dir }}" -sahara_cloud_demo: 'devstack-admin' -sahara_scenario_conf: '{{ ansible_user_dir }}/template_vars.ini' -sahara_scenario_test_template: 'fake.yaml.mako' -sahara_scenario_tox_env: 'venv' -sahara_enable_s3: False -tox_executable: 'tox' diff --git a/roles/run-sahara-scenario/tasks/main.yaml b/roles/run-sahara-scenario/tasks/main.yaml deleted file mode 100644 index 810a7968..00000000 --- a/roles/run-sahara-scenario/tasks/main.yaml +++ /dev/null @@ -1,24 +0,0 @@ ---- -- name: run sahara-scenario - shell: | - {{ tox_executable }} -e {{ sahara_scenario_tox_env }} --sitepackages -- sahara-scenario --verbose -V {{ sahara_scenario_conf }} \ - etc/scenario/gate/credentials.yaml.mako \ - etc/scenario/gate/edp.yaml.mako \ - {% if sahara_enable_s3 -%} - etc/scenario/gate/credentials_s3.yaml.mako \ - etc/scenario/gate/edp_s3.yaml.mako \ - {% endif -%} - etc/scenario/gate/{{ sahara_scenario_test_template }} \ - --os-cloud {{ sahara_cloud_demo }} \ - {% if sahara_scenario_use_api_v2|default(False) -%} - --v2 \ - {% endif -%} - {% if sahara_enable_s3 -%} - --feature s3 \ - {% endif -%} - | tee scenario.log - if grep -qE '(FAILED|ERROR:)' scenario.log; then - exit 1 - fi - args: - chdir: "{{ sahara_tests_src_dir }}" diff --git a/roles/setup-sahara-scenario-env/defaults/main.yaml b/roles/setup-sahara-scenario-env/defaults/main.yaml deleted file mode 100644 index 6ca4669f..00000000 --- a/roles/setup-sahara-scenario-env/defaults/main.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- -sahara_cloud_admin: 'devstack-admin' -sahara_cloud_demo: 'devstack-admin' -sahara_plugin: 'fake' -sahara_plugin_version: '0.1' -sahara_image_name: 'xenial-server' -sahara_image_user: 'ubuntu' -sahara_image_format: 'qcow2' -sahara_scenario_conf: '{{ ansible_user_dir }}/template_vars.ini' -sahara_enable_s3: False -sahara_network_type: 'neutron' -private_network_name: 'private' -public_network_name: 'public' -sahara_flavor_small: 'sah1.small' -sahara_cluster_transient: 'False' -sahara_auto_security_group: 'True' -sahara_flavors: - 'sah1.small': - id: 20 - ram: 512 - disk: 10 - vcpus: 1 - ephemeral: 0 diff --git a/roles/setup-sahara-scenario-env/tasks/main.yaml b/roles/setup-sahara-scenario-env/tasks/main.yaml deleted file mode 100644 index 091d9412..00000000 --- a/roles/setup-sahara-scenario-env/tasks/main.yaml +++ /dev/null @@ -1,52 +0,0 @@ -- block: - - name: set sahara_image_path based on the remote file - set_fact: - sahara_image_path: "{{ ansible_user_dir }}/{{ sahara_image_name }}.{{ sahara_image_format }}" - - name: download the remote image - get_url: - url: "{{ sahara_image_url }}" - dest: "{{ sahara_image_path }}" - when: sahara_image_url is defined and sahara_image_url!='' and sahara_image_url is search('^http') - -- name: set sahara_image_path from the local file - set_fact: - sahara_image_path: "{{ sahara_image_url }}" - when: sahara_image_url is defined and sahara_image_url!='' and not sahara_image_url is search('^http') - -# we cannot use os_image because Ansible 2.7 requires a newer version of -# openstacksdk than the one available in queens and pike. -- name: register the required image in Glance - command: | - openstack --os-cloud {{ sahara_cloud_demo }} image create \ - --disk-format {{ sahara_image_format }} --file {{ sahara_image_path }} \ - {{ sahara_image_name }} - -- name: register the required image in Sahara - shell: | - openstack --os-cloud {{ sahara_cloud_demo }} dataprocessing image register \ - --username {{ sahara_image_user }} {{ sahara_image_name }}; - openstack --os-cloud {{ sahara_cloud_demo }} dataprocessing image tags add {{ sahara_image_name }} --tags \ - {{ sahara_plugin_version }} {{ sahara_plugin }} - -- name: S3 configuration - import_tasks: setup_s3.yaml - when: sahara_enable_s3 - -# we cannot use os_nova_flavor as well (see above) -- name: create the required flavor(s) - command: | - openstack --os-cloud {{ sahara_cloud_demo }} flavor create \ - --ram {{ item.value.ram }} \ - --vcpus {{ item.value.vcpus|default('1') }} \ - --disk {{ item.value.disk|default('10') }} \ - {% if item.value.ephemeral|default(0) -%} - --ephemeral {{ item.value.ephemeral }} \ - {% endif -%} \ - {{ item.key }} - with_dict: "{{ sahara_flavors }}" - ignore_errors: true - -- name: generate the configuration file for the scenario test - template: - src: sahara_scenario_conf.ini.j2 - dest: "{{ sahara_scenario_conf }}" diff --git a/roles/setup-sahara-scenario-env/tasks/setup_s3.yaml b/roles/setup-sahara-scenario-env/tasks/setup_s3.yaml deleted file mode 100644 index 9d581b98..00000000 --- a/roles/setup-sahara-scenario-env/tasks/setup_s3.yaml +++ /dev/null @@ -1,29 +0,0 @@ -- name: create the S3 credentials - shell: | - ACCESS_KEY=$(openstack --os-cloud {{ sahara_cloud_demo }} ec2 credentials list -f value -c Access | head -n1) - if [ -z "${ACCESS_KEY}" ]; then - ACCESS_KEY=$(openstack --os-cloud {{ sahara_cloud_demo }} ec2 credentials create -f value -c access) - fi - SECRET_KEY=$(openstack --os-cloud {{ sahara_cloud_demo }} ec2 credentials list -f value -c Secret | head -n1) - printf "${ACCESS_KEY}\n${SECRET_KEY}" - register: sahara_s3_credentials_out - -# This task should not be needed normally and the endpoint should be discovered by default -- name: find the swift endpoint for S3 - shell: | - ENDPOINT=$(openstack --os-cloud {{ sahara_cloud_admin }} endpoint list --service swift --interface public -c URL -f value) - ENDPOINT_PREFIX=$(awk -F'//' '{print $1}') - ENDPOINT_SSL="False" - if [ "${ENDPOINT_PREFIX}" = "https" ]; then - ENDPOINT_SSL="True" - fi - printf "${ENDPOINT}\n${ENDPOINT_SSL}" - register: sahara_s3_endpoint_out - -- name: save the S3 access data - set_fact: - sahara_s3_accesskey: "{{ sahara_s3_credentials_out.stdout_lines[0] }}" - sahara_s3_secretkey: "{{ sahara_s3_credentials_out.stdout_lines[1] }}" - sahara_s3_endpoint: "{{ sahara_s3_endpoint_out.stdout_lines[0] }}" - sahara_s3_endpoint_ssl: "{{ sahara_s3_endpoint_out.stdout_lines[1] }}" - sahara_s3_bucket_path: True diff --git a/roles/setup-sahara-scenario-env/templates/sahara_scenario_conf.ini.j2 b/roles/setup-sahara-scenario-env/templates/sahara_scenario_conf.ini.j2 deleted file mode 100644 index 40dd4dab..00000000 --- a/roles/setup-sahara-scenario-env/templates/sahara_scenario_conf.ini.j2 +++ /dev/null @@ -1,16 +0,0 @@ -[DEFAULT] -network_type: {{ sahara_network_type }} -network_private_name: {{ private_network_name }} -network_public_name: {{ public_network_name }} -plugin_image: {{ sahara_image_name }} -ci_flavor_id: {{ sahara_flavor_small }} -cluster_name: testc -is_transient: {{ sahara_cluster_transient }} -auto_security_group: {{ sahara_auto_security_group }} -{% if sahara_enable_s3 -%} -s3_accesskey: {{ sahara_s3_accesskey }} -s3_secretkey: {{ sahara_s3_secretkey }} -s3_endpoint: {{ sahara_s3_endpoint }} -s3_endpoint_ssl: {{ sahara_s3_endpoint_ssl }} -s3_bucket_path: {{ sahara_s3_bucket_path }} -{% endif -%} diff --git a/sahara_tempest_plugin/README.rst b/sahara_tempest_plugin/README.rst deleted file mode 100644 index efd551f1..00000000 --- a/sahara_tempest_plugin/README.rst +++ /dev/null @@ -1,5 +0,0 @@ -=============================================== -Tempest Integration of Sahara -=============================================== - -This directory contains Tempest tests to cover the Sahara project. diff --git a/sahara_tempest_plugin/__init__.py b/sahara_tempest_plugin/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tempest_plugin/clients.py b/sahara_tempest_plugin/clients.py deleted file mode 100644 index a511e6b2..00000000 --- a/sahara_tempest_plugin/clients.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2016 Red Hat, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from tempest import config -from tempest.lib.services import clients - -CONF = config.CONF - - -class Manager(clients.ServiceClients): - """Tempest stable service clients and loaded plugins service clients""" - - def __init__(self, credentials, service=None): - if CONF.identity.auth_version == 'v2': - identity_uri = CONF.identity.uri - else: - identity_uri = CONF.identity.uri_v3 - super(Manager, self).__init__(credentials, identity_uri) diff --git a/sahara_tempest_plugin/common/__init__.py b/sahara_tempest_plugin/common/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tempest_plugin/common/plugin_utils.py b/sahara_tempest_plugin/common/plugin_utils.py deleted file mode 100644 index f3140dc9..00000000 --- a/sahara_tempest_plugin/common/plugin_utils.py +++ /dev/null @@ -1,351 +0,0 @@ -# Copyright 2016 Red Hat, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from collections import OrderedDict -import copy - -from tempest import config - - -CONF = config.CONF - - -"""Default templates. -There should always be at least a master1 and a worker1 node -group template.""" -BASE_VANILLA_DESC = { - 'NODES': { - 'master1': { - 'count': 1, - 'node_processes': ['namenode', 'resourcemanager', - 'hiveserver'] - }, - 'master2': { - 'count': 1, - 'node_processes': ['oozie', 'historyserver', - 'secondarynamenode'] - }, - 'worker1': { - 'count': 1, - 'node_processes': ['datanode', 'nodemanager'], - 'node_configs': { - 'MapReduce': { - 'yarn.app.mapreduce.am.resource.mb': 256, - 'yarn.app.mapreduce.am.command-opts': '-Xmx256m' - }, - 'YARN': { - 'yarn.scheduler.minimum-allocation-mb': 256, - 'yarn.scheduler.maximum-allocation-mb': 1024, - 'yarn.nodemanager.vmem-check-enabled': False - } - } - } - }, - 'cluster_configs': { - 'HDFS': { - 'dfs.replication': 1 - } - } -} - -BASE_SPARK_DESC = { - 'NODES': { - 'master1': { - 'count': 1, - 'node_processes': ['namenode', 'master'] - }, - 'worker1': { - 'count': 1, - 'node_processes': ['datanode', 'slave'] - } - }, - 'cluster_configs': { - 'HDFS': { - 'dfs.replication': 1 - } - } -} - -BASE_CDH_DESC = { - 'NODES': { - 'master1': { - 'count': 1, - 'node_processes': ['CLOUDERA_MANAGER'] - }, - 'master2': { - 'count': 1, - 'node_processes': ['HDFS_NAMENODE', - 'YARN_RESOURCEMANAGER'] - }, - 'master3': { - 'count': 1, - 'node_processes': ['OOZIE_SERVER', 'YARN_JOBHISTORY', - 'HDFS_SECONDARYNAMENODE', - 'HIVE_METASTORE', 'HIVE_SERVER2'] - }, - 'worker1': { - 'count': 1, - 'node_processes': ['YARN_NODEMANAGER', 'HDFS_DATANODE'] - } - }, - 'cluster_configs': { - 'HDFS': { - 'dfs_replication': 1 - } - } -} - -BASE_AMBARI_HDP_DESC = { - 'NODES': { - 'master1': { - 'count': 1, - 'node_processes': ['Ambari', 'MapReduce History Server', - 'Spark History Server', 'NameNode', - 'ResourceManager', 'SecondaryNameNode', - 'YARN Timeline Server', 'ZooKeeper', - 'Kafka Broker'] - }, - 'master2': { - 'count': 1, - 'node_processes': ['Hive Metastore', 'HiveServer', 'Oozie'] - }, - 'worker1': { - 'count': 3, - 'node_processes': ['DataNode', 'NodeManager'] - } - }, - 'cluster_configs': { - 'HDFS': { - 'dfs.datanode.du.reserved': 0 - } - } -} - -BASE_MAPR_DESC = { - 'NODES': { - 'master1': { - 'count': 1, - 'node_processes': ['Metrics', 'Webserver', 'ZooKeeper', - 'HTTPFS', 'Oozie', 'FileServer', 'CLDB', - 'Flume', 'Hue', 'NodeManager', 'HistoryServer', - 'ResourceManager', 'HiveServer2', - 'HiveMetastore', - 'Sqoop2-Client', 'Sqoop2-Server'] - }, - 'worker1': { - 'count': 1, - 'node_processes': ['NodeManager', 'FileServer'] - } - } -} - -BASE_STORM_DESC = { - 'NODES': { - 'master1': { - 'count': 1, - 'node_processes': ['nimbus'] - }, - 'master2': { - 'count': 1, - 'node_processes': ['zookeeper'] - }, - 'worker1': { - 'count': 1, - 'node_processes': ['supervisor'] - } - } -} - - -DEFAULT_TEMPLATES = { - 'fake': OrderedDict([ - ('0.1', { - 'NODES': { - 'master1': { - 'count': 1, - 'node_processes': ['namenode', 'jobtracker'] - }, - 'worker1': { - 'count': 1, - 'node_processes': ['datanode', 'tasktracker'], - } - } - }) - ]), - 'vanilla': OrderedDict([ - ('2.7.1', copy.deepcopy(BASE_VANILLA_DESC)), - ('2.7.5', copy.deepcopy(BASE_VANILLA_DESC)), - ('2.8.2', copy.deepcopy(BASE_VANILLA_DESC)) - ]), - 'ambari': OrderedDict([ - ('2.3', copy.deepcopy(BASE_AMBARI_HDP_DESC)), - ('2.4', copy.deepcopy(BASE_AMBARI_HDP_DESC)), - ('2.5', copy.deepcopy(BASE_AMBARI_HDP_DESC)), - ('2.6', copy.deepcopy(BASE_AMBARI_HDP_DESC)) - ]), - 'spark': OrderedDict([ - ('1.3.1', copy.deepcopy(BASE_SPARK_DESC)), - ('1.6.0', copy.deepcopy(BASE_SPARK_DESC)), - ('2.1.0', copy.deepcopy(BASE_SPARK_DESC)), - ('2.2', copy.deepcopy(BASE_SPARK_DESC)), - ('2.3', copy.deepcopy(BASE_SPARK_DESC)) - ]), - 'cdh': OrderedDict([ - ('5.4.0', copy.deepcopy(BASE_CDH_DESC)), - ('5.5.0', copy.deepcopy(BASE_CDH_DESC)), - ('5.7.0', copy.deepcopy(BASE_CDH_DESC)), - ('5.9.0', copy.deepcopy(BASE_CDH_DESC)), - ('5.11.0', copy.deepcopy(BASE_CDH_DESC)), - ('5.13.0', copy.deepcopy(BASE_CDH_DESC)) - ]), - 'mapr': OrderedDict([ - ('5.1.0.mrv2', copy.deepcopy(BASE_MAPR_DESC)), - ('5.2.0.mrv2', copy.deepcopy(BASE_MAPR_DESC)) - ]), - 'storm': OrderedDict([ - ('1.0.1', copy.deepcopy(BASE_STORM_DESC)), - ('1.1.0', copy.deepcopy(BASE_STORM_DESC)), - ('1.2', copy.deepcopy(BASE_STORM_DESC)) - ]) -} - - -def get_plugin_data(plugin_name, plugin_version): - return DEFAULT_TEMPLATES[plugin_name][plugin_version] - - -def get_default_plugin(): - """Returns the default plugin used for testing.""" - enabled_plugins = CONF.data_processing_feature_enabled.plugins - - if len(enabled_plugins) == 0: - return None - - # NOTE(raissa) if fake is available, use it first. - # this is to reduce load and should be removed - # once the fake plugin is no longer needed - if 'fake' in enabled_plugins: - return 'fake' - - for plugin in enabled_plugins: - if plugin in DEFAULT_TEMPLATES.keys(): - break - else: - plugin = '' - return plugin - - -def get_default_version(plugin): - """Returns the default plugin version used for testing. - - This is gathered separately from the plugin to allow - the usage of plugin name in skip_checks. This method is - rather invoked into resource_setup, which allows API calls - and exceptions. - """ - default_plugin_name = get_default_plugin() - - if not (plugin and default_plugin_name): - return None - - for version in DEFAULT_TEMPLATES[default_plugin_name].keys(): - if version in plugin['versions']: - break - else: - version = None - - return version - - -def get_node_group_template(nodegroup='worker1', - default_version=None, - floating_ip_pool=None, - api_version='1.1'): - """Returns a node group template for the default plugin.""" - try: - flavor = CONF.compute.flavor_ref - default_plugin_name = get_default_plugin() - plugin_data = ( - get_plugin_data(default_plugin_name, default_version) - ) - nodegroup_data = plugin_data['NODES'][nodegroup] - node_group_template = { - 'description': 'Test node group template', - 'plugin_name': default_plugin_name, - 'node_processes': nodegroup_data['node_processes'], - 'flavor_id': flavor, - 'floating_ip_pool': floating_ip_pool, - 'node_configs': nodegroup_data.get('node_configs', {}) - } - if api_version == '1.1': - node_group_template['hadoop_version'] = default_version - else: - node_group_template['plugin_version'] = default_version - return node_group_template - except (IndexError, KeyError): - return None - - -def get_cluster_template(node_group_template_ids=None, - default_version=None, - api_version='1.1'): - """Returns a cluster template for the default plugin. - - node_group_template_ids contains the type and ID of pre-defined - node group templates that have to be used in the cluster template - (instead of dynamically defining them with 'node_processes'). - """ - flavor = CONF.compute.flavor_ref - default_plugin_name = get_default_plugin() - - if node_group_template_ids is None: - node_group_template_ids = {} - try: - plugin_data = ( - get_plugin_data(default_plugin_name, default_version) - ) - - all_node_groups = [] - for ng_name, ng_data in plugin_data['NODES'].items(): - node_group = { - 'name': '%s-node' % (ng_name), - 'flavor_id': flavor, - 'count': ng_data['count'] - } - if ng_name in node_group_template_ids.keys(): - # node group already defined, use it - node_group['node_group_template_id'] = ( - node_group_template_ids[ng_name] - ) - else: - # node_processes list defined on-the-fly - node_group['node_processes'] = ng_data['node_processes'] - if 'node_configs' in ng_data: - node_group['node_configs'] = ng_data['node_configs'] - all_node_groups.append(node_group) - - cluster_template = { - 'description': 'Test cluster template', - 'plugin_name': default_plugin_name, - 'cluster_configs': plugin_data.get('cluster_configs', {}), - 'node_groups': all_node_groups, - } - if api_version == '1.1': - cluster_template['hadoop_version'] = default_version - else: - cluster_template['plugin_version'] = default_version - return cluster_template - except (IndexError, KeyError): - return None diff --git a/sahara_tempest_plugin/config.py b/sahara_tempest_plugin/config.py deleted file mode 100644 index bd756789..00000000 --- a/sahara_tempest_plugin/config.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2016 Red Hat, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslo_config import cfg - -service_option = cfg.BoolOpt("sahara", - default=False, - help="Whether or not sahara is expected to be " - "available") - -data_processing_group = cfg.OptGroup(name="data-processing", - title="Data Processing options") - -DataProcessingGroup = [ - cfg.StrOpt('catalog_type', - default='data-processing', - help="Catalog type of the data processing service."), - cfg.StrOpt('endpoint_type', - default='publicURL', - choices=['public', 'admin', 'internal', - 'publicURL', 'adminURL', 'internalURL'], - help="The endpoint type to use for the data processing " - "service."), -] - -DataProcessingAdditionalGroup = [ - cfg.IntOpt('cluster_timeout', - default=3600, - help='Timeout (in seconds) to wait for cluster deployment.'), - cfg.IntOpt('request_timeout', - default=10, - help='Timeout (in seconds) between status checks.'), - # FIXME: the default values here are an hack needed until it is possible - # to pass values from the job to tempest.conf (or a devstack plugin is - # written). - cfg.StrOpt('test_image_name', - default='xenial-server-cloudimg-amd64-disk1', - help='name of an image which is used for cluster creation.'), - cfg.StrOpt('test_ssh_user', - default='ubuntu', - help='username used to access the test image.'), - cfg.BoolOpt('use_api_v2', - default=False, - help='Run API tests against APIv2 instead of 1.1'), - cfg.StrOpt('api_version_saharaclient', - default='1.1', - help='Version of Sahara API used by saharaclient', - deprecated_name='saharaclient_version'), - cfg.StrOpt('sahara_url', - help='Sahara url as http://ip:port/api_version/tenant_id'), - # TODO(shuyingya): Delete this option once the Mitaka release is EOL. - cfg.BoolOpt('plugin_update_support', - default=True, - help='Does sahara support plugin update?'), -] - - -data_processing_feature_group = cfg.OptGroup( - name="data-processing-feature-enabled", - title="Enabled Data Processing features") - -DataProcessingFeaturesGroup = [ - cfg.ListOpt('plugins', - default=["vanilla", "cdh"], - help="List of enabled data processing plugins"), - # delete this and always execute the tests when Tempest and - # this Tempest plugin stop supporting Queens, the last version - # without or incomplete S3 support. - cfg.BoolOpt('s3', - default=False, - help='Does Sahara support S3?'), -] diff --git a/sahara_tempest_plugin/plugin.py b/sahara_tempest_plugin/plugin.py deleted file mode 100644 index 7e56f07b..00000000 --- a/sahara_tempest_plugin/plugin.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2016 Red Hat, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -import os - -from tempest import config -from tempest.test_discover import plugins - -from sahara_tempest_plugin import config as sahara_config - - -class SaharaTempestPlugin(plugins.TempestPlugin): - def load_tests(self): - base_path = os.path.split(os.path.dirname( - os.path.abspath(__file__)))[0] - test_dir = "sahara_tempest_plugin/tests" - full_test_dir = os.path.join(base_path, test_dir) - return full_test_dir, base_path - - def register_opts(self, conf): - conf.register_opt(sahara_config.service_option, - group='service_available') - conf.register_group(sahara_config.data_processing_group) - conf.register_opts(sahara_config.DataProcessingGroup + - sahara_config.DataProcessingAdditionalGroup, - sahara_config.data_processing_group) - - conf.register_group(sahara_config.data_processing_feature_group) - conf.register_opts(sahara_config.DataProcessingFeaturesGroup, - sahara_config.data_processing_feature_group) - - def get_opt_lists(self): - return [ - (sahara_config.data_processing_group.name, - sahara_config.DataProcessingGroup), - (sahara_config.data_processing_feature_group.name, - sahara_config.DataProcessingFeaturesGroup), - ] - - def get_service_clients(self): - data_processing_config = ( - config.service_client_config('data-processing')) - - params = { - 'name': 'data_processing', - 'service_version': 'data_processing.v1_1', - 'module_path': - 'sahara_tempest_plugin.services.data_processing.v1_1', - 'client_names': ['DataProcessingClient'] - } - params.update(data_processing_config) - params_v2 = { - 'name': 'data_processing_v2', - 'service_version': 'data_processing.v2', - 'module_path': - 'sahara_tempest_plugin.services.data_processing.v2', - 'client_names': ['DataProcessingClient'] - } - params_v2.update(data_processing_config) - return [params, params_v2] diff --git a/sahara_tempest_plugin/services/__init__.py b/sahara_tempest_plugin/services/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tempest_plugin/services/data_processing/__init__.py b/sahara_tempest_plugin/services/data_processing/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tempest_plugin/services/data_processing/base_client.py b/sahara_tempest_plugin/services/data_processing/base_client.py deleted file mode 100644 index 5c469db2..00000000 --- a/sahara_tempest_plugin/services/data_processing/base_client.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2013 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslo_serialization import jsonutils as json - -from tempest.lib.common import rest_client - - -class BaseDataProcessingClient(rest_client.RestClient): - - def _request_and_check_resp(self, request_func, uri, resp_status): - """Make a request and check response status code. - - It returns a ResponseBody. - """ - resp, body = request_func(uri) - self.expected_success(resp_status, resp.status) - return rest_client.ResponseBody(resp, body) - - def _request_and_check_resp_data(self, request_func, uri, resp_status): - """Make a request and check response status code. - - It returns pair: resp and response data. - """ - resp, body = request_func(uri) - self.expected_success(resp_status, resp.status) - return resp, body - - def _request_check_and_parse_resp(self, request_func, uri, - resp_status, *args, **kwargs): - """Make a request, check response status code and parse response body. - - It returns a ResponseBody. - """ - headers = {'Content-Type': 'application/json'} - resp, body = request_func(uri, headers=headers, *args, **kwargs) - self.expected_success(resp_status, resp.status) - body = json.loads(body) - return rest_client.ResponseBody(resp, body) diff --git a/sahara_tempest_plugin/services/data_processing/v1_1/__init__.py b/sahara_tempest_plugin/services/data_processing/v1_1/__init__.py deleted file mode 100644 index f8787541..00000000 --- a/sahara_tempest_plugin/services/data_processing/v1_1/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -# flake8: noqa: E501 - -from sahara_tempest_plugin.services.data_processing.v1_1.data_processing_client import \ - DataProcessingClient - -__all__ = ['DataProcessingClient'] diff --git a/sahara_tempest_plugin/services/data_processing/v1_1/data_processing_client.py b/sahara_tempest_plugin/services/data_processing/v1_1/data_processing_client.py deleted file mode 100644 index 2e698066..00000000 --- a/sahara_tempest_plugin/services/data_processing/v1_1/data_processing_client.py +++ /dev/null @@ -1,270 +0,0 @@ -# Copyright (c) 2013 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslo_serialization import jsonutils as json - -from sahara_tempest_plugin.services.data_processing import base_client - - -class DataProcessingClient(base_client.BaseDataProcessingClient): - - api_version = "v1.1" - - def list_node_group_templates(self): - """List all node group templates for a user.""" - - uri = 'node-group-templates' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_node_group_template(self, tmpl_id): - """Returns the details of a single node group template.""" - - uri = 'node-group-templates/%s' % tmpl_id - return self._request_check_and_parse_resp(self.get, uri, 200) - - def create_node_group_template(self, name, plugin_name, hadoop_version, - node_processes, flavor_id, - node_configs=None, **kwargs): - """Creates node group template with specified params. - - It supports passing additional params using kwargs and returns created - object. - """ - uri = 'node-group-templates' - body = kwargs.copy() - body.update({ - 'name': name, - 'plugin_name': plugin_name, - 'hadoop_version': hadoop_version, - 'node_processes': node_processes, - 'flavor_id': flavor_id, - 'node_configs': node_configs or dict(), - }) - return self._request_check_and_parse_resp(self.post, uri, 202, - body=json.dumps(body)) - - def delete_node_group_template(self, tmpl_id): - """Deletes the specified node group template by id.""" - - uri = 'node-group-templates/%s' % tmpl_id - return self._request_and_check_resp(self.delete, uri, 204) - - def list_plugins(self): - """List all enabled plugins.""" - - uri = 'plugins' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_plugin(self, plugin_name, plugin_version=None): - """Returns the details of a single plugin.""" - - uri = 'plugins/%s' % plugin_name - if plugin_version: - uri += '/%s' % plugin_version - return self._request_check_and_parse_resp(self.get, uri, 200) - - def list_cluster_templates(self): - """List all cluster templates for a user.""" - - uri = 'cluster-templates' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_cluster_template(self, tmpl_id): - """Returns the details of a single cluster template.""" - - uri = 'cluster-templates/%s' % tmpl_id - return self._request_check_and_parse_resp(self.get, uri, 200) - - def create_cluster_template(self, name, plugin_name, hadoop_version, - node_groups, cluster_configs=None, - **kwargs): - """Creates cluster template with specified params. - - It supports passing additional params using kwargs and returns created - object. - """ - uri = 'cluster-templates' - body = kwargs.copy() - body.update({ - 'name': name, - 'plugin_name': plugin_name, - 'hadoop_version': hadoop_version, - 'node_groups': node_groups, - 'cluster_configs': cluster_configs or dict(), - }) - return self._request_check_and_parse_resp(self.post, uri, 202, - body=json.dumps(body)) - - def delete_cluster_template(self, tmpl_id): - """Deletes the specified cluster template by id.""" - - uri = 'cluster-templates/%s' % tmpl_id - return self._request_and_check_resp(self.delete, uri, 204) - - def update_cluster_template(self, tmpl_id, **kwargs): - """Updates the specificed cluster template.""" - uri = 'cluster-templates/%s' % tmpl_id - return self._request_check_and_parse_resp(self.put, uri, 202, - body=json.dumps(kwargs)) - - def list_data_sources(self): - """List all data sources for a user.""" - - uri = 'data-sources' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_data_source(self, source_id): - """Returns the details of a single data source.""" - - uri = 'data-sources/%s' % source_id - return self._request_check_and_parse_resp(self.get, uri, 200) - - def create_data_source(self, name, data_source_type, url, **kwargs): - """Creates data source with specified params. - - It supports passing additional params using kwargs and returns created - object. - """ - uri = 'data-sources' - body = kwargs.copy() - body.update({ - 'name': name, - 'type': data_source_type, - 'url': url - }) - return self._request_check_and_parse_resp(self.post, uri, - 202, body=json.dumps(body)) - - def update_node_group_template(self, tmpl_id, **kwargs): - """Updates the details of a single node group template.""" - uri = 'node-group-templates/%s' % tmpl_id - return self._request_check_and_parse_resp(self.put, uri, 202, - body=json.dumps(kwargs)) - - def delete_data_source(self, source_id): - """Deletes the specified data source by id.""" - - uri = 'data-sources/%s' % source_id - return self._request_and_check_resp(self.delete, uri, 204) - - def update_data_source(self, source_id, **kwargs): - """Updates a data source""" - uri = 'data-sources/%s' % source_id - return self._request_check_and_parse_resp(self.put, uri, 202, - body=json.dumps(kwargs)) - - def list_job_binary_internals(self): - """List all job binary internals for a user.""" - - uri = 'job-binary-internals' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_job_binary_internal(self, job_binary_id): - """Returns the details of a single job binary internal.""" - - uri = 'job-binary-internals/%s' % job_binary_id - return self._request_check_and_parse_resp(self.get, uri, 200) - - def create_job_binary_internal(self, name, data): - """Creates job binary internal with specified params.""" - - uri = 'job-binary-internals/%s' % name - return self._request_check_and_parse_resp(self.put, uri, 202, data) - - def delete_job_binary_internal(self, job_binary_id): - """Deletes the specified job binary internal by id.""" - - uri = 'job-binary-internals/%s' % job_binary_id - return self._request_and_check_resp(self.delete, uri, 204) - - def get_job_binary_internal_data(self, job_binary_id): - """Returns data of a single job binary internal.""" - - uri = 'job-binary-internals/%s/data' % job_binary_id - return self._request_and_check_resp_data(self.get, uri, 200) - - def list_job_binaries(self): - """List all job binaries for a user.""" - - uri = 'job-binaries' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_job_binary(self, job_binary_id): - """Returns the details of a single job binary.""" - - uri = 'job-binaries/%s' % job_binary_id - return self._request_check_and_parse_resp(self.get, uri, 200) - - def create_job_binary(self, name, url, extra=None, **kwargs): - """Creates job binary with specified params. - - It supports passing additional params using kwargs and returns created - object. - """ - uri = 'job-binaries' - body = kwargs.copy() - body.update({ - 'name': name, - 'url': url, - 'extra': extra or dict(), - }) - return self._request_check_and_parse_resp(self.post, uri, - 202, body=json.dumps(body)) - - def delete_job_binary(self, job_binary_id): - """Deletes the specified job binary by id.""" - - uri = 'job-binaries/%s' % job_binary_id - return self._request_and_check_resp(self.delete, uri, 204) - - def get_job_binary_data(self, job_binary_id): - """Returns data of a single job binary.""" - - uri = 'job-binaries/%s/data' % job_binary_id - return self._request_and_check_resp_data(self.get, uri, 200) - - def list_jobs(self): - """List all jobs for a user.""" - - uri = 'jobs' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_job(self, job_id): - """Returns the details of a single job.""" - - uri = 'jobs/%s' % job_id - return self._request_check_and_parse_resp(self.get, uri, 200) - - def create_job(self, name, job_type, mains, libs=None, **kwargs): - """Creates job with specified params. - - It supports passing additional params using kwargs and returns created - object. - """ - uri = 'jobs' - body = kwargs.copy() - body.update({ - 'name': name, - 'type': job_type, - 'mains': mains, - 'libs': libs or list(), - }) - return self._request_check_and_parse_resp(self.post, uri, - 202, body=json.dumps(body)) - - def delete_job(self, job_id): - """Deletes the specified job by id.""" - - uri = 'jobs/%s' % job_id - return self._request_and_check_resp(self.delete, uri, 204) diff --git a/sahara_tempest_plugin/services/data_processing/v2/__init__.py b/sahara_tempest_plugin/services/data_processing/v2/__init__.py deleted file mode 100644 index eaebaa5c..00000000 --- a/sahara_tempest_plugin/services/data_processing/v2/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -# flake8: noqa: E501 - -from sahara_tempest_plugin.services.data_processing.v2.data_processing_client import \ - DataProcessingClient - -__all__ = ['DataProcessingClient'] diff --git a/sahara_tempest_plugin/services/data_processing/v2/data_processing_client.py b/sahara_tempest_plugin/services/data_processing/v2/data_processing_client.py deleted file mode 100644 index 9583188f..00000000 --- a/sahara_tempest_plugin/services/data_processing/v2/data_processing_client.py +++ /dev/null @@ -1,241 +0,0 @@ -# Copyright (c) 2013 Mirantis Inc. -# Copyright (c) 2018 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslo_serialization import jsonutils as json - -from sahara_tempest_plugin.services.data_processing import base_client - - -class DataProcessingClient(base_client.BaseDataProcessingClient): - - api_version = "v2" - - def list_node_group_templates(self): - """List all node group templates for a user.""" - - uri = 'node-group-templates' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_node_group_template(self, tmpl_id): - """Returns the details of a single node group template.""" - - uri = 'node-group-templates/%s' % tmpl_id - return self._request_check_and_parse_resp(self.get, uri, 200) - - def create_node_group_template(self, name, plugin_name, plugin_version, - node_processes, flavor_id, - node_configs=None, **kwargs): - """Creates node group template with specified params. - - It supports passing additional params using kwargs and returns created - object. - """ - uri = 'node-group-templates' - body = kwargs.copy() - body.update({ - 'name': name, - 'plugin_name': plugin_name, - 'plugin_version': plugin_version, - 'node_processes': node_processes, - 'flavor_id': flavor_id, - 'node_configs': node_configs or dict(), - }) - return self._request_check_and_parse_resp(self.post, uri, 202, - body=json.dumps(body)) - - def delete_node_group_template(self, tmpl_id): - """Deletes the specified node group template by id.""" - - uri = 'node-group-templates/%s' % tmpl_id - return self._request_and_check_resp(self.delete, uri, 204) - - def update_node_group_template(self, tmpl_id, **kwargs): - """Updates the details of a single node group template.""" - uri = 'node-group-templates/%s' % tmpl_id - return self._request_check_and_parse_resp(self.patch, uri, 202, - body=json.dumps(kwargs)) - - def list_plugins(self): - """List all enabled plugins.""" - - uri = 'plugins' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_plugin(self, plugin_name, plugin_version=None): - """Returns the details of a single plugin.""" - - uri = 'plugins/%s' % plugin_name - if plugin_version: - uri += '/%s' % plugin_version - return self._request_check_and_parse_resp(self.get, uri, 200) - - def list_cluster_templates(self): - """List all cluster templates for a user.""" - - uri = 'cluster-templates' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_cluster_template(self, tmpl_id): - """Returns the details of a single cluster template.""" - - uri = 'cluster-templates/%s' % tmpl_id - return self._request_check_and_parse_resp(self.get, uri, 200) - - def create_cluster_template(self, name, plugin_name, plugin_version, - node_groups, cluster_configs=None, - **kwargs): - """Creates cluster template with specified params. - - It supports passing additional params using kwargs and returns created - object. - """ - uri = 'cluster-templates' - body = kwargs.copy() - body.update({ - 'name': name, - 'plugin_name': plugin_name, - 'plugin_version': plugin_version, - 'node_groups': node_groups, - 'cluster_configs': cluster_configs or dict(), - }) - return self._request_check_and_parse_resp(self.post, uri, 202, - body=json.dumps(body)) - - def delete_cluster_template(self, tmpl_id): - """Deletes the specified cluster template by id.""" - - uri = 'cluster-templates/%s' % tmpl_id - return self._request_and_check_resp(self.delete, uri, 204) - - def update_cluster_template(self, tmpl_id, **kwargs): - """Updates the specificed cluster template.""" - uri = 'cluster-templates/%s' % tmpl_id - return self._request_check_and_parse_resp(self.patch, uri, 202, - body=json.dumps(kwargs)) - - def list_data_sources(self): - """List all data sources for a user.""" - - uri = 'data-sources' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_data_source(self, source_id): - """Returns the details of a single data source.""" - - uri = 'data-sources/%s' % source_id - return self._request_check_and_parse_resp(self.get, uri, 200) - - def create_data_source(self, name, data_source_type, url, **kwargs): - """Creates data source with specified params. - - It supports passing additional params using kwargs and returns created - object. - """ - uri = 'data-sources' - body = kwargs.copy() - body.update({ - 'name': name, - 'type': data_source_type, - 'url': url - }) - return self._request_check_and_parse_resp(self.post, uri, - 202, body=json.dumps(body)) - - def delete_data_source(self, source_id): - """Deletes the specified data source by id.""" - - uri = 'data-sources/%s' % source_id - return self._request_and_check_resp(self.delete, uri, 204) - - def update_data_source(self, source_id, **kwargs): - """Updates a data source""" - uri = 'data-sources/%s' % source_id - return self._request_check_and_parse_resp(self.patch, uri, 202, - body=json.dumps(kwargs)) - - def list_job_binaries(self): - """List all job binaries for a user.""" - - uri = 'job-binaries' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_job_binary(self, job_binary_id): - """Returns the details of a single job binary.""" - - uri = 'job-binaries/%s' % job_binary_id - return self._request_check_and_parse_resp(self.get, uri, 200) - - def create_job_binary(self, name, url, extra=None, **kwargs): - """Creates job binary with specified params. - - It supports passing additional params using kwargs and returns created - object. - """ - uri = 'job-binaries' - body = kwargs.copy() - body.update({ - 'name': name, - 'url': url, - 'extra': extra or dict(), - }) - return self._request_check_and_parse_resp(self.post, uri, - 202, body=json.dumps(body)) - - def delete_job_binary(self, job_binary_id): - """Deletes the specified job binary by id.""" - - uri = 'job-binaries/%s' % job_binary_id - return self._request_and_check_resp(self.delete, uri, 204) - - def get_job_binary_data(self, job_binary_id): - """Returns data of a single job binary.""" - - uri = 'job-binaries/%s/data' % job_binary_id - return self._request_and_check_resp_data(self.get, uri, 200) - - def list_job_templates(self): - """List all jobs templates for a user.""" - - uri = 'job-templates' - return self._request_check_and_parse_resp(self.get, uri, 200) - - def get_job_template(self, job_id): - """Returns the details of a single job template.""" - - uri = 'job-templates/%s' % job_id - return self._request_check_and_parse_resp(self.get, uri, 200) - - def create_job_template(self, name, job_type, mains, libs=None, **kwargs): - """Creates job with specified params. - - It supports passing additional params using kwargs and returns created - object. - """ - uri = 'job-templates' - body = kwargs.copy() - body.update({ - 'name': name, - 'type': job_type, - 'mains': mains, - 'libs': libs or list(), - }) - return self._request_check_and_parse_resp(self.post, uri, - 202, body=json.dumps(body)) - - def delete_job_template(self, job_id): - """Deletes the specified job by id.""" - - uri = 'job-templates/%s' % job_id - return self._request_and_check_resp(self.delete, uri, 204) diff --git a/sahara_tempest_plugin/tests/__init__.py b/sahara_tempest_plugin/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tempest_plugin/tests/api/__init__.py b/sahara_tempest_plugin/tests/api/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tempest_plugin/tests/api/base.py b/sahara_tempest_plugin/tests/api/base.py deleted file mode 100644 index 708a75bc..00000000 --- a/sahara_tempest_plugin/tests/api/base.py +++ /dev/null @@ -1,260 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from tempest import config -from tempest.lib import exceptions -import tempest.test - -from sahara_tempest_plugin import clients -from sahara_tempest_plugin.common import plugin_utils - - -CONF = config.CONF - - -class InvalidSaharaTestConfiguration(exceptions.TempestException): - message = "Invalid configuration for Sahara tests" - - -class BaseDataProcessingTest(tempest.test.BaseTestCase): - - credentials = ['primary'] - - client_manager = clients.Manager - - @classmethod - def skip_checks(cls): - super(BaseDataProcessingTest, cls).skip_checks() - if not CONF.service_available.sahara: - raise cls.skipException('Sahara support is required') - cls.default_plugin = plugin_utils.get_default_plugin() - - @classmethod - def setup_clients(cls): - super(BaseDataProcessingTest, cls).setup_clients() - if not CONF.data_processing.use_api_v2: - cls.api_version = '1.1' - cls.client = cls.os_primary.data_processing.DataProcessingClient() - else: - cls.api_version = '2.0' - cls.client = \ - cls.os_primary.data_processing_v2.DataProcessingClient() - - @classmethod - def resource_setup(cls): - super(BaseDataProcessingTest, cls).resource_setup() - - plugin = None - if cls.default_plugin: - plugin = cls.client.get_plugin(cls.default_plugin)['plugin'] - cls.default_version = plugin_utils.get_default_version(plugin) - - if cls.default_plugin is not None and cls.default_version is None: - raise InvalidSaharaTestConfiguration( - message="No known Sahara plugin version was found") - - # add lists for watched resources - cls._node_group_templates = [] - cls._cluster_templates = [] - cls._data_sources = [] - cls._job_binary_internals = [] - cls._job_binaries = [] - cls._jobs = [] - - @classmethod - def resource_cleanup(cls): - cls.cleanup_resources(getattr(cls, '_cluster_templates', []), - cls.client.delete_cluster_template) - cls.cleanup_resources(getattr(cls, '_node_group_templates', []), - cls.client.delete_node_group_template) - if cls.api_version == '1.1': - cls.cleanup_resources(getattr(cls, '_jobs', []), - cls.client.delete_job) - else: - cls.cleanup_resources(getattr(cls, '_jobs', []), - cls.client.delete_job_template) - cls.cleanup_resources(getattr(cls, '_job_binaries', []), - cls.client.delete_job_binary) - if cls.api_version == '1.1': - cls.cleanup_resources(getattr(cls, '_job_binary_internals', []), - cls.client.delete_job_binary_internal) - cls.cleanup_resources(getattr(cls, '_data_sources', []), - cls.client.delete_data_source) - super(BaseDataProcessingTest, cls).resource_cleanup() - - @staticmethod - def cleanup_resources(resource_id_list, method): - for resource_id in resource_id_list: - try: - method(resource_id) - except exceptions.NotFound: - # ignore errors while auto removing created resource - pass - - @classmethod - def create_node_group_template(cls, name, plugin_name, hadoop_version, - node_processes, flavor_id, - node_configs=None, **kwargs): - """Creates watched node group template with specified params. - - It supports passing additional params using kwargs and returns created - object. All resources created in this method will be automatically - removed in tearDownClass method. - """ - resp_body = cls.client.create_node_group_template(name, plugin_name, - hadoop_version, - node_processes, - flavor_id, - node_configs, - **kwargs) - resp_body = resp_body['node_group_template'] - # store id of created node group template - cls._node_group_templates.append(resp_body['id']) - - return resp_body - - @classmethod - def create_cluster_template(cls, name, plugin_name, hadoop_version, - node_groups, cluster_configs=None, **kwargs): - """Creates watched cluster template with specified params. - - It supports passing additional params using kwargs and returns created - object. All resources created in this method will be automatically - removed in tearDownClass method. - """ - resp_body = cls.client.create_cluster_template(name, plugin_name, - hadoop_version, - node_groups, - cluster_configs, - **kwargs) - resp_body = resp_body['cluster_template'] - # store id of created cluster template - cls._cluster_templates.append(resp_body['id']) - - return resp_body - - @classmethod - def create_data_source(cls, name, type, url, **kwargs): - """Creates watched data source with specified params. - - It supports passing additional params using kwargs and returns created - object. All resources created in this method will be automatically - removed in tearDownClass method. - """ - resp_body = cls.client.create_data_source(name, type, url, **kwargs) - resp_body = resp_body['data_source'] - # store id of created data source - cls._data_sources.append(resp_body['id']) - - return resp_body - - @classmethod - def create_job_binary_internal(cls, name, data): - """Creates watched job binary internal with specified params. - - It returns created object. All resources created in this method will - be automatically removed in tearDownClass method. - """ - resp_body = cls.client.create_job_binary_internal(name, data) - resp_body = resp_body['job_binary_internal'] - # store id of created job binary internal - cls._job_binary_internals.append(resp_body['id']) - - return resp_body - - @classmethod - def create_job_binary(cls, name, url, extra=None, **kwargs): - """Creates watched job binary with specified params. - - It supports passing additional params using kwargs and returns created - object. All resources created in this method will be automatically - removed in tearDownClass method. - """ - resp_body = cls.client.create_job_binary(name, url, extra, **kwargs) - resp_body = resp_body['job_binary'] - # store id of created job binary - cls._job_binaries.append(resp_body['id']) - - return resp_body - - @classmethod - def create_job(cls, name, job_type, mains, libs=None, **kwargs): - """Creates watched job (v1) with specified params. - - It supports passing additional params using kwargs and returns created - object. All resources created in this method will be automatically - removed in tearDownClass method. - """ - resp_body = cls.client.create_job(name, - job_type, mains, libs, **kwargs) - resp_body = resp_body['job'] - # store id of created job - cls._jobs.append(resp_body['id']) - - return resp_body - - @classmethod - def create_job_template(cls, name, job_type, mains, libs=None, **kwargs): - """Creates watched job template (v2) with specified params. - - It supports passing additional params using kwargs and returns created - object. All resources created in this method will be automatically - removed in tearDownClass method. - """ - resp_body = cls.client.create_job_template(name, job_type, mains, - libs, **kwargs) - resp_body = resp_body['job_template'] - # store id of created job - cls._jobs.append(resp_body['id']) - - return resp_body - - @classmethod - def get_node_group_template(cls, nodegroup='worker1'): - """Returns a node group template for the default plugin.""" - return plugin_utils.get_node_group_template(nodegroup, - cls.default_version, - None, - cls.api_version) - - @classmethod - def get_cluster_template(cls, node_group_template_ids=None): - """Returns a cluster template for the default plugin. - - node_group_template_defined contains the type and ID of pre-defined - node group templates that have to be used in the cluster template - (instead of dynamically defining them with 'node_processes'). - """ - return plugin_utils.get_cluster_template(node_group_template_ids, - cls.default_version, - cls.api_version) - - @classmethod - def wait_for_resource_deletion(cls, resource_id, get_resource): - """Waits for a resource to be deleted. - - The deletion of a resource depends on the client is_resource_deleted - implementation. This implementation will vary slightly from resource - to resource. get_resource param should be the function used to - retrieve that type of resource. - """ - def is_resource_deleted(resource_id): - try: - get_resource(resource_id) - except exceptions.NotFound: - return True - return False - - cls.client.is_resource_deleted = is_resource_deleted - cls.client.wait_for_resource_deletion(resource_id) diff --git a/sahara_tempest_plugin/tests/api/test_cluster_templates.py b/sahara_tempest_plugin/tests/api/test_cluster_templates.py deleted file mode 100644 index 11833c0a..00000000 --- a/sahara_tempest_plugin/tests/api/test_cluster_templates.py +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from testtools import testcase as tc - -from tempest.lib import decorators -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.api import base as dp_base - - -class ClusterTemplateTest(dp_base.BaseDataProcessingTest): - # NOTE: Links to the API documentation: https://docs.openstack.org/ - # api-ref/data-processing/v1.1/#cluster-templates - # https://docs.openstack.org/api-ref/data-processing/v2/#cluster-templates - - @classmethod - def skip_checks(cls): - super(ClusterTemplateTest, cls).skip_checks() - if cls.default_plugin is None: - raise cls.skipException("No Sahara plugins configured") - - @classmethod - def resource_setup(cls): - super(ClusterTemplateTest, cls).resource_setup() - - # pre-define a node group templates - node_group_template_w = cls.get_node_group_template('worker1') - if node_group_template_w is None: - raise dp_base.InvalidSaharaTestConfiguration( - message="No known Sahara plugin was found") - - node_group_template_w['name'] = data_utils.rand_name( - 'sahara-ng-template') - - # hack the arguments: keep the compatibility with the signature - # of self.create_node_group_template - if 'plugin_version' in node_group_template_w: - plugin_version_value = node_group_template_w['plugin_version'] - del node_group_template_w['plugin_version'] - node_group_template_w['hadoop_version'] = plugin_version_value - - resp_body = cls.create_node_group_template(**node_group_template_w) - node_group_template_id = resp_body['id'] - configured_node_group_templates = {'worker1': node_group_template_id} - - cls.full_cluster_template = cls.get_cluster_template( - configured_node_group_templates) - - # create cls.cluster_template variable to use for comparison to cluster - # template response body. The 'node_groups' field in the response body - # has some extra info that post body does not have. The 'node_groups' - # field in the response body is something like this - # - # 'node_groups': [ - # { - # 'count': 3, - # 'name': 'worker-node', - # 'volume_mount_prefix': '/volumes/disk', - # 'created_at': '2014-05-21 14:31:37', - # 'updated_at': None, - # 'floating_ip_pool': None, - # ... - # }, - # ... - # ] - cls.cluster_template = cls.full_cluster_template.copy() - del cls.cluster_template['node_groups'] - - def _create_cluster_template(self, template_name=None): - """Creates Cluster Template with optional name specified. - - It creates template, ensures template name and response body. - Returns id and name of created template. - """ - if not template_name: - # generate random name if it's not specified - template_name = data_utils.rand_name('sahara-cluster-template') - - # hack the arguments: keep the compatibility with the signature - # of self.create_cluster_template - full_cluster_template_w = self.full_cluster_template.copy() - if 'plugin_version' in full_cluster_template_w: - plugin_version_value = full_cluster_template_w['plugin_version'] - del full_cluster_template_w['plugin_version'] - full_cluster_template_w['hadoop_version'] = plugin_version_value - - # create cluster template - resp_body = self.create_cluster_template(template_name, - **full_cluster_template_w) - - # ensure that template created successfully - self.assertEqual(template_name, resp_body['name']) - self.assertDictContainsSubset(self.cluster_template, resp_body) - - return resp_body['id'], template_name - - @tc.attr('smoke') - @decorators.idempotent_id('3525f1f1-3f9c-407d-891a-a996237e728b') - def test_cluster_template_create(self): - self._create_cluster_template() - - @tc.attr('smoke') - @decorators.idempotent_id('7a161882-e430-4840-a1c6-1d928201fab2') - def test_cluster_template_list(self): - template_info = self._create_cluster_template() - - # check for cluster template in list - templates = self.client.list_cluster_templates()['cluster_templates'] - templates_info = [(template['id'], template['name']) - for template in templates] - self.assertIn(template_info, templates_info) - - @tc.attr('smoke') - @decorators.idempotent_id('2b75fe22-f731-4b0f-84f1-89ab25f86637') - def test_cluster_template_get(self): - template_id, template_name = self._create_cluster_template() - - # check cluster template fetch by id - template = self.client.get_cluster_template(template_id) - template = template['cluster_template'] - self.assertEqual(template_name, template['name']) - self.assertDictContainsSubset(self.cluster_template, template) - - @tc.attr('smoke') - @decorators.idempotent_id('ff1fd989-171c-4dd7-91fd-9fbc71b09675') - def test_cluster_template_delete(self): - template_id, _ = self._create_cluster_template() - - # delete the cluster template by id - self.client.delete_cluster_template(template_id) - get_resource = self.client.get_cluster_template - self.wait_for_resource_deletion(template_id, get_resource) - - templates = self.client.list_cluster_templates()['cluster_templates'] - templates_info = [template['id'] - for template in templates] - self.assertNotIn(template_id, templates_info) - - @tc.attr('smoke') - @decorators.idempotent_id('40235aa0-cd4b-494a-9c12-2d0e8a92157a') - def test_cluster_template_update(self): - template_id, _ = self._create_cluster_template() - - new_template_name = data_utils.rand_name('sahara-cluster-template') - body = {'name': new_template_name} - updated_template = self.client.update_cluster_template(template_id, - **body) - updated_template = updated_template['cluster_template'] - - self.assertEqual(new_template_name, updated_template['name']) diff --git a/sahara_tempest_plugin/tests/api/test_data_sources.py b/sahara_tempest_plugin/tests/api/test_data_sources.py deleted file mode 100644 index a1e3c477..00000000 --- a/sahara_tempest_plugin/tests/api/test_data_sources.py +++ /dev/null @@ -1,254 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import testtools -from testtools import testcase as tc - -from tempest import config -from tempest.lib import decorators -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.api import base as dp_base - - -CONF = config.CONF - - -class DataSourceTest(dp_base.BaseDataProcessingTest): - @classmethod - def resource_setup(cls): - super(DataSourceTest, cls).resource_setup() - cls.swift_data_source_with_creds = { - 'url': 'swift://sahara-container.sahara/input-source', - 'description': 'Test data source', - 'credentials': { - 'user': cls.os_primary.credentials.username, - 'password': cls.os_primary.credentials.password - }, - 'type': 'swift' - } - cls.swift_data_source = cls.swift_data_source_with_creds.copy() - del cls.swift_data_source['credentials'] - - cls.s3_data_source_with_creds = { - 'url': 's3://sahara-bucket/input-source', - 'description': 'Test data source', - 'credentials': { - 'accesskey': 'username', - 'secretkey': 'key', - 'endpoint': 'localhost', - 'bucket_in_path': False, - 'ssl': False - }, - 'type': 's3' - } - cls.s3_data_source = cls.s3_data_source_with_creds.copy() - del cls.s3_data_source['credentials'] - - cls.local_hdfs_data_source = { - 'url': 'input-source', - 'description': 'Test data source', - 'type': 'hdfs' - } - - cls.external_hdfs_data_source = { - 'url': 'hdfs://172.18.168.2:8020/usr/hadoop/input-source', - 'description': 'Test data source', - 'type': 'hdfs' - } - - def _create_data_source(self, source_body, source_name=None): - """Creates Data Source with optional name specified. - - It creates a link to input-source file (it may not exist), ensures - source name and response body. Returns id and name of created source. - """ - if not source_name: - # generate random name if it's not specified - source_name = data_utils.rand_name('sahara-data-source') - - # create data source - resp_body = self.create_data_source(source_name, **source_body) - - # ensure that source created successfully - self.assertEqual(source_name, resp_body['name']) - if source_body['type'] == 'swift': - source_body = self.swift_data_source - elif source_body['type'] == 's3': - source_body = self.s3_data_source - self.assertDictContainsSubset(source_body, resp_body) - - return resp_body['id'], source_name - - def _list_data_sources(self, source_info): - # check for data source in list - sources = self.client.list_data_sources()['data_sources'] - sources_info = [(source['id'], source['name']) for source in sources] - self.assertIn(source_info, sources_info) - - def _get_data_source(self, source_id, source_name, source_body): - # check data source fetch by id - source = self.client.get_data_source(source_id)['data_source'] - self.assertEqual(source_name, source['name']) - self.assertDictContainsSubset(source_body, source) - - def _delete_data_source(self, source_id): - # delete the data source by id - self.client.delete_data_source(source_id) - self.wait_for_resource_deletion(source_id, self.client.get_data_source) - - # assert data source does not exist anymore - sources = self.client.list_data_sources()['data_sources'] - sources_ids = [source['id'] for source in sources] - self.assertNotIn(source_id, sources_ids) - - def _update_data_source(self, source_id): - new_source_name = data_utils.rand_name('sahara-data-source') - body = {'name': new_source_name} - updated_source = self.client.update_data_source(source_id, **body) - source = updated_source['data_source'] - self.assertEqual(new_source_name, source['name']) - - @tc.attr('smoke') - @decorators.idempotent_id('9e0e836d-c372-4fca-91b7-b66c3e9646c8') - def test_swift_data_source_create(self): - self._create_data_source(self.swift_data_source_with_creds) - - @tc.attr('smoke') - @decorators.idempotent_id('3cb87a4a-0534-4b97-9edc-8bbc822b68a0') - def test_swift_data_source_list(self): - source_info = ( - self._create_data_source(self.swift_data_source_with_creds)) - self._list_data_sources(source_info) - - @tc.attr('smoke') - @decorators.idempotent_id('fc07409b-6477-4cb3-9168-e633c46b227f') - def test_swift_data_source_get(self): - source_id, source_name = ( - self._create_data_source(self.swift_data_source_with_creds)) - self._get_data_source(source_id, source_name, self.swift_data_source) - - @tc.attr('smoke') - @decorators.idempotent_id('df53669c-0cd1-4cf7-b408-4cf215d8beb8') - def test_swift_data_source_delete(self): - source_id, _ = ( - self._create_data_source(self.swift_data_source_with_creds)) - self._delete_data_source(source_id) - - @tc.attr('smoke') - @decorators.idempotent_id('44398efb-c2a8-4a20-97cd-509c49b5d25a') - def test_swift_data_source_update(self): - source_id, _ = ( - self._create_data_source(self.swift_data_source_with_creds)) - self._update_data_source(source_id) - - @decorators.idempotent_id('54b68270-74d2-4c93-a324-09c2dccb1208') - @testtools.skipUnless(CONF.data_processing_feature_enabled.s3, - 'S3 not available') - def test_s3_data_source_create(self): - self._create_data_source(self.s3_data_source_with_creds) - - @decorators.idempotent_id('5f67a8d1-e362-4204-88ec-674630a71019') - @testtools.skipUnless(CONF.data_processing_feature_enabled.s3, - 'S3 not available') - def test_s3_data_source_list(self): - source_info = ( - self._create_data_source(self.s3_data_source_with_creds)) - self._list_data_sources(source_info) - - @decorators.idempotent_id('84017749-b9d6-4542-9d12-1c73239e03b2') - @testtools.skipUnless(CONF.data_processing_feature_enabled.s3, - 'S3 not available') - def test_s3_data_source_get(self): - source_id, source_name = ( - self._create_data_source(self.s3_data_source_with_creds)) - self._get_data_source(source_id, source_name, self.s3_data_source) - - @decorators.idempotent_id('fb8f9f44-17ea-4be9-8cec-e02f31a49bae') - @testtools.skipUnless(CONF.data_processing_feature_enabled.s3, - 'S3 not available') - def test_s3_data_source_delete(self): - source_id, _ = ( - self._create_data_source(self.s3_data_source_with_creds)) - self._delete_data_source(source_id) - - @decorators.idempotent_id('d069714a-86fb-45ce-8498-43901b065243') - @testtools.skipUnless(CONF.data_processing_feature_enabled.s3, - 'S3 not available') - def test_s3_data_source_update(self): - source_id, _ = ( - self._create_data_source(self.s3_data_source_with_creds)) - self._update_data_source(source_id) - - @tc.attr('smoke') - @decorators.idempotent_id('88505d52-db01-4229-8f1d-a1137da5fe2d') - def test_local_hdfs_data_source_create(self): - self._create_data_source(self.local_hdfs_data_source) - - @tc.attr('smoke') - @decorators.idempotent_id('81d7d42a-d7f6-4d9b-b38c-0801a4dfe3c2') - def test_local_hdfs_data_source_list(self): - source_info = self._create_data_source(self.local_hdfs_data_source) - self._list_data_sources(source_info) - - @tc.attr('smoke') - @decorators.idempotent_id('ec0144c6-db1e-4169-bb06-7abae14a8443') - def test_local_hdfs_data_source_get(self): - source_id, source_name = ( - self._create_data_source(self.local_hdfs_data_source)) - self._get_data_source( - source_id, source_name, self.local_hdfs_data_source) - - @tc.attr('smoke') - @decorators.idempotent_id('e398308b-4230-4f86-ba10-9b0b60a59c8d') - def test_local_hdfs_data_source_delete(self): - source_id, _ = self._create_data_source(self.local_hdfs_data_source) - self._delete_data_source(source_id) - - @tc.attr('smoke') - @decorators.idempotent_id('16a71f3b-0095-431c-b542-c871e1f95e1f') - def test_local_hdfs_data_source_update(self): - source_id, _ = self._create_data_source(self.local_hdfs_data_source) - self._update_data_source(source_id) - - @tc.attr('smoke') - @decorators.idempotent_id('bfd91128-e642-4d95-a973-3e536962180c') - def test_external_hdfs_data_source_create(self): - self._create_data_source(self.external_hdfs_data_source) - - @tc.attr('smoke') - @decorators.idempotent_id('92e2be72-f7ab-499d-ae01-fb9943c90d8e') - def test_external_hdfs_data_source_list(self): - source_info = self._create_data_source(self.external_hdfs_data_source) - self._list_data_sources(source_info) - - @tc.attr('smoke') - @decorators.idempotent_id('a31edb1b-6bc6-4f42-871f-70cd243184ac') - def test_external_hdfs_data_source_get(self): - source_id, source_name = ( - self._create_data_source(self.external_hdfs_data_source)) - self._get_data_source( - source_id, source_name, self.external_hdfs_data_source) - - @tc.attr('smoke') - @decorators.idempotent_id('295924cd-a085-4b45-aea8-0707cdb2da7e') - def test_external_hdfs_data_source_delete(self): - source_id, _ = self._create_data_source(self.external_hdfs_data_source) - self._delete_data_source(source_id) - - @tc.attr('smoke') - @decorators.idempotent_id('9b317861-95db-44bc-9b4b-80d23feade3f') - def test_external_hdfs_data_source_update(self): - source_id, _ = self._create_data_source(self.external_hdfs_data_source) - self._update_data_source(source_id) diff --git a/sahara_tempest_plugin/tests/api/test_job_binaries.py b/sahara_tempest_plugin/tests/api/test_job_binaries.py deleted file mode 100644 index d0138dfa..00000000 --- a/sahara_tempest_plugin/tests/api/test_job_binaries.py +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import testtools -from testtools import testcase as tc - -from tempest import config -from tempest.lib import decorators -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.api import base as dp_base - - -CONF = config.CONF - - -class JobBinaryTest(dp_base.BaseDataProcessingTest): - # NOTE: Links to the API documentation: - # https://docs.openstack.org/api-ref/data-processing/v1.1/#job-binaries - # https://docs.openstack.org/api-ref/data-processing/v2/#job-binaries - - @classmethod - def resource_setup(cls): - super(JobBinaryTest, cls).resource_setup() - cls.swift_job_binary_with_extra = { - 'url': 'swift://sahara-container.sahara/example.jar', - 'description': 'Test job binary', - 'extra': { - 'user': cls.os_primary.credentials.username, - 'password': cls.os_primary.credentials.password - } - } - cls.s3_job_binary_with_extra = { - 'url': 's3://sahara-bucket/example.jar', - 'description': 'Test job binary', - 'extra': { - 'accesskey': cls.os_primary.credentials.username, - 'secretkey': cls.os_primary.credentials.password, - 'endpoint': 'localhost' - } - } - # Create extra cls.swift_job_binary and cls.s3_job_binary variables - # to use for comparison to job binary response body - # because response body has no 'extra' field. - cls.swift_job_binary = cls.swift_job_binary_with_extra.copy() - del cls.swift_job_binary['extra'] - cls.s3_job_binary = cls.s3_job_binary_with_extra.copy() - del cls.s3_job_binary['extra'] - - name = data_utils.rand_name('sahara-internal-job-binary') - cls.job_binary_data = 'Some script may be data' - if not CONF.data_processing.use_api_v2: - job_binary_internal = ( - cls.create_job_binary_internal(name, cls.job_binary_data)) - cls.internal_db_job_binary = { - 'url': 'internal-db://%s' % job_binary_internal['id'], - 'description': 'Test job binary', - } - - def _create_job_binary(self, binary_body, binary_name=None): - """Creates Job Binary with optional name specified. - - It creates a link to data (jar, pig files, etc.), ensures job binary - name and response body. Returns id and name of created job binary. - Data may not exist when using Swift or S3 as data storage. - In other cases data must exist in storage. - """ - if not binary_name: - # generate random name if it's not specified - binary_name = data_utils.rand_name('sahara-job-binary') - - # create job binary - resp_body = self.create_job_binary(binary_name, **binary_body) - - # ensure that binary created successfully - self.assertEqual(binary_name, resp_body['name']) - if binary_body['url'].startswith('swift:'): - binary_body = self.swift_job_binary - elif binary_body['url'].startswith('s3:'): - binary_body = self.s3_job_binary - self.assertDictContainsSubset(binary_body, resp_body) - - return resp_body['id'], binary_name - - @tc.attr('smoke') - @decorators.idempotent_id('c00d43f8-4360-45f8-b280-af1a201b12d3') - def test_swift_job_binary_create(self): - self._create_job_binary(self.swift_job_binary_with_extra) - - @tc.attr('smoke') - @decorators.idempotent_id('f8809352-e79d-4748-9359-ce1efce89f2a') - def test_swift_job_binary_list(self): - binary_info = self._create_job_binary(self.swift_job_binary_with_extra) - - # check for job binary in list - binaries = self.client.list_job_binaries()['binaries'] - binaries_info = [(binary['id'], binary['name']) for binary in binaries] - self.assertIn(binary_info, binaries_info) - - @tc.attr('smoke') - @decorators.idempotent_id('2d4a670f-e8f1-413c-b5ac-50c1bfe9e1b1') - def test_swift_job_binary_get(self): - binary_id, binary_name = ( - self._create_job_binary(self.swift_job_binary_with_extra)) - - # check job binary fetch by id - binary = self.client.get_job_binary(binary_id)['job_binary'] - self.assertEqual(binary_name, binary['name']) - self.assertDictContainsSubset(self.swift_job_binary, binary) - - @tc.attr('smoke') - @decorators.idempotent_id('9b0e8f38-04f3-4616-b399-cfa7eb2677ed') - def test_swift_job_binary_delete(self): - binary_id, _ = ( - self._create_job_binary(self.swift_job_binary_with_extra)) - - # delete the job binary by id - self.client.delete_job_binary(binary_id) - - @decorators.idempotent_id('1cda1990-bfa1-46b1-892d-fc3ceafde537') - @testtools.skipUnless(CONF.data_processing_feature_enabled.s3, - 'S3 not available') - def test_s3_job_binary_create(self): - self._create_job_binary(self.s3_job_binary_with_extra) - - @decorators.idempotent_id('69de4774-44fb-401d-9d81-8c4df83d6cdb') - @testtools.skipUnless(CONF.data_processing_feature_enabled.s3, - 'S3 not available') - def test_s3_job_binary_list(self): - binary_info = self._create_job_binary(self.s3_job_binary_with_extra) - - # check for job binary in list - binaries = self.client.list_job_binaries()['binaries'] - binaries_info = [(binary['id'], binary['name']) for binary in binaries] - self.assertIn(binary_info, binaries_info) - - @decorators.idempotent_id('479ba3ef-67b7-45c9-81e2-ea34366099ce') - @testtools.skipUnless(CONF.data_processing_feature_enabled.s3, - 'S3 not available') - def test_s3_job_binary_get(self): - binary_id, binary_name = ( - self._create_job_binary(self.s3_job_binary_with_extra)) - - # check job binary fetch by id - binary = self.client.get_job_binary(binary_id)['job_binary'] - self.assertEqual(binary_name, binary['name']) - self.assertDictContainsSubset(self.s3_job_binary, binary) - - @decorators.idempotent_id('d949472b-6a57-4250-905d-087dfb614633') - @testtools.skipUnless(CONF.data_processing_feature_enabled.s3, - 'S3 not available') - def test_s3_job_binary_delete(self): - binary_id, _ = ( - self._create_job_binary(self.s3_job_binary_with_extra)) - - # delete the job binary by id - self.client.delete_job_binary(binary_id) - - @tc.attr('smoke') - @decorators.idempotent_id('63662f6d-8291-407e-a6fc-f654522ebab6') - @testtools.skipIf(CONF.data_processing.api_version_saharaclient != '1.1', - 'Job binaries stored on the internal db are available ' - 'only with API v1.1') - def test_internal_db_job_binary_create(self): - self._create_job_binary(self.internal_db_job_binary) - - @tc.attr('smoke') - @decorators.idempotent_id('38731e7b-6d9d-4ffa-8fd1-193c453e88b1') - @testtools.skipIf(CONF.data_processing.api_version_saharaclient != '1.1', - 'Job binaries stored on the internal db are available ' - 'only with API v1.1') - def test_internal_db_job_binary_list(self): - binary_info = self._create_job_binary(self.internal_db_job_binary) - - # check for job binary in list - binaries = self.client.list_job_binaries()['binaries'] - binaries_info = [(binary['id'], binary['name']) for binary in binaries] - self.assertIn(binary_info, binaries_info) - - @tc.attr('smoke') - @decorators.idempotent_id('1b32199b-c3f5-43e1-a37a-3797e57b7066') - @testtools.skipIf(CONF.data_processing.api_version_saharaclient != '1.1', - 'Job binaries stored on the internal db are available ' - 'only with API v1.1') - def test_internal_db_job_binary_get(self): - binary_id, binary_name = ( - self._create_job_binary(self.internal_db_job_binary)) - - # check job binary fetch by id - binary = self.client.get_job_binary(binary_id)['job_binary'] - self.assertEqual(binary_name, binary['name']) - self.assertDictContainsSubset(self.internal_db_job_binary, binary) - - @tc.attr('smoke') - @decorators.idempotent_id('3c42b0c3-3e03-46a5-adf0-df0650271a4e') - @testtools.skipIf(CONF.data_processing.api_version_saharaclient != '1.1', - 'Job binaries stored on the internal db are available ' - 'only with API v1.1') - def test_internal_db_job_binary_delete(self): - binary_id, _ = self._create_job_binary(self.internal_db_job_binary) - - # delete the job binary by id - self.client.delete_job_binary(binary_id) - self.wait_for_resource_deletion(binary_id, self.client.get_job_binary) - - binaries = self.client.list_job_binaries()['binaries'] - binaries_ids = [binary['id'] for binary in binaries] - self.assertNotIn(binary_id, binaries_ids) - - @tc.attr('smoke') - @decorators.idempotent_id('d5d47659-7e2c-4ea7-b292-5b3e559e8587') - @testtools.skipIf(CONF.data_processing.api_version_saharaclient != '1.1', - 'Job binaries stored on the internal db are available ' - 'only with API v1.1') - def test_job_binary_get_data(self): - binary_id, _ = self._create_job_binary(self.internal_db_job_binary) - - # get data of job binary by id - _, data = self.client.get_job_binary_data(binary_id) - self.assertEqual(data.decode("utf-8"), self.job_binary_data) diff --git a/sahara_tempest_plugin/tests/api/test_job_binary_internals.py b/sahara_tempest_plugin/tests/api/test_job_binary_internals.py deleted file mode 100644 index 7622de70..00000000 --- a/sahara_tempest_plugin/tests/api/test_job_binary_internals.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from testtools import testcase as tc - -from tempest import config -from tempest.lib import decorators -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.api import base as dp_base - - -CONF = config.CONF - - -class JobBinaryInternalTest(dp_base.BaseDataProcessingTest): - # NOTE: Link to the API documentation: https://docs.openstack.org/ - # api-ref/data-processing/v1.1/#job-binary-internals - - @classmethod - def skip_checks(cls): - super(JobBinaryInternalTest, cls).skip_checks() - if CONF.data_processing.use_api_v2: - raise cls.skipException('Job binaries stored on the internal db ' - 'are available only with API v1.1') - - @classmethod - def resource_setup(cls): - super(JobBinaryInternalTest, cls).resource_setup() - cls.job_binary_internal_data = 'Some script may be data' - - def _create_job_binary_internal(self, binary_name=None): - """Creates Job Binary Internal with optional name specified. - - It puts data into Sahara database and ensures job binary internal name. - Returns id and name of created job binary internal. - """ - if not binary_name: - # generate random name if it's not specified - binary_name = data_utils.rand_name('sahara-job-binary-internal') - - # create job binary internal - resp_body = ( - self.create_job_binary_internal(binary_name, - self.job_binary_internal_data)) - - # ensure that job binary internal created successfully - self.assertEqual(binary_name, resp_body['name']) - - return resp_body['id'], binary_name - - @tc.attr('smoke') - @decorators.idempotent_id('249c4dc2-946f-4939-83e6-212ddb6ea0be') - def test_job_binary_internal_create(self): - self._create_job_binary_internal() - - @tc.attr('smoke') - @decorators.idempotent_id('1e3c2ecd-5673-499d-babe-4fe2fcdf64ee') - def test_job_binary_internal_list(self): - binary_info = self._create_job_binary_internal() - - # check for job binary internal in list - binaries = self.client.list_job_binary_internals()['binaries'] - binaries_info = [(binary['id'], binary['name']) for binary in binaries] - self.assertIn(binary_info, binaries_info) - - @tc.attr('smoke') - @decorators.idempotent_id('a2046a53-386c-43ab-be35-df54b19db776') - def test_job_binary_internal_get(self): - binary_id, binary_name = self._create_job_binary_internal() - - # check job binary internal fetch by id - binary = self.client.get_job_binary_internal(binary_id) - self.assertEqual(binary_name, binary['job_binary_internal']['name']) - - @tc.attr('smoke') - @decorators.idempotent_id('b3568c33-4eed-40d5-aae4-6ff3b2ac58f5') - def test_job_binary_internal_delete(self): - binary_id, _ = self._create_job_binary_internal() - - # delete the job binary internal by id - self.client.delete_job_binary_internal(binary_id) - get_resource = self.client.get_job_binary_internal - self.wait_for_resource_deletion(binary_id, get_resource) - - # check for job binary internal in list - binaries = self.client.list_job_binary_internals()['binaries'] - binaries_ids = [binary['id'] for binary in binaries] - self.assertNotIn(binary_id, binaries_ids) - - @tc.attr('smoke') - @decorators.idempotent_id('8871f2b0-5782-4d66-9bb9-6f95bcb839ea') - def test_job_binary_internal_get_data(self): - binary_id, _ = self._create_job_binary_internal() - - # get data of job binary internal by id - _, data = self.client.get_job_binary_internal_data(binary_id) - self.assertEqual(data.decode("utf-8"), self.job_binary_internal_data) diff --git a/sahara_tempest_plugin/tests/api/test_job_templates.py b/sahara_tempest_plugin/tests/api/test_job_templates.py deleted file mode 100644 index 6bce837b..00000000 --- a/sahara_tempest_plugin/tests/api/test_job_templates.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# Copyright (c) 2018 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from testtools import testcase as tc - -from tempest import config -from tempest.lib import decorators -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.api import base as dp_base - - -CONF = config.CONF - - -class JobTemplatesTest(dp_base.BaseDataProcessingTest): - # NOTE: Link to the API documentation: https://docs.openstack.org/ - # api-ref/data-processing/v2/#job-templates - - @classmethod - def skip_checks(cls): - super(JobTemplatesTest, cls).skip_checks() - if not CONF.data_processing.use_api_v2: - raise cls.skipException('These tests require API v2') - - @classmethod - def resource_setup(cls): - super(JobTemplatesTest, cls).resource_setup() - # create job binary - job_binary = { - 'name': data_utils.rand_name('sahara-job-binary'), - 'url': 'swift://sahara-container.sahara/example.jar', - 'description': 'Test job binary', - 'extra': { - 'user': cls.os_primary.credentials.username, - 'password': cls.os_primary.credentials.password - } - } - resp_body = cls.create_job_binary(**job_binary) - job_binary_id = resp_body['id'] - - cls.job = { - 'job_type': 'Pig', - 'mains': [job_binary_id] - } - - def _create_job_template(self, job_name=None): - """Creates Job with optional name specified. - - It creates job and ensures job name. Returns id and name of created - job. - """ - if not job_name: - # generate random name if it's not specified - job_name = data_utils.rand_name('sahara-job') - - # create job - resp_body = self.create_job_template(job_name, **self.job) - - # ensure that job created successfully - self.assertEqual(job_name, resp_body['name']) - - return resp_body['id'], job_name - - @decorators.idempotent_id('26e39bc9-df9c-422f-9401-0d2cf5c87c63') - @tc.attr('smoke') - def test_job_template_create(self): - self._create_job_template() - - @decorators.idempotent_id('6d3ce0da-cd37-4ac1-abfa-e53835bd2c08') - @tc.attr('smoke') - def test_job_template_list(self): - job_info = self._create_job_template() - - # check for job in list - jobs = self.client.list_job_templates()['job_templates'] - jobs_info = [(job['id'], job['name']) for job in jobs] - self.assertIn(job_info, jobs_info) - - @decorators.idempotent_id('4396453f-f4b2-415c-916c-6929a51ba89f') - @tc.attr('smoke') - def test_job_template_get(self): - job_id, job_name = self._create_job_template() - - # check job fetch by id - job_t = self.client.get_job_template(job_id)['job_template'] - - self.assertEqual(job_name, job_t['name']) - - @decorators.idempotent_id('2d816b08-b20c-438f-8580-3e40fd741eb4') - @tc.attr('smoke') - def test_job_template_delete(self): - job_id, _ = self._create_job_template() - - # delete the job by id - self.client.delete_job_template(job_id) - self.wait_for_resource_deletion(job_id, self.client.get_job_template) - - jobs = self.client.list_job_templates()['job_templates'] - jobs_ids = [job['id'] for job in jobs] - self.assertNotIn(job_id, jobs_ids) diff --git a/sahara_tempest_plugin/tests/api/test_jobs.py b/sahara_tempest_plugin/tests/api/test_jobs.py deleted file mode 100644 index 7a08e093..00000000 --- a/sahara_tempest_plugin/tests/api/test_jobs.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from testtools import testcase as tc - -from tempest import config -from tempest.lib import decorators -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.api import base as dp_base - - -CONF = config.CONF - - -class JobTest(dp_base.BaseDataProcessingTest): - # NOTE: Link to the API documentation: https://docs.openstack.org/ - # api-ref/data-processing/v1.1/#jobs - - @classmethod - def skip_checks(cls): - super(JobTest, cls).skip_checks() - if CONF.data_processing.use_api_v2: - raise cls.skipException('These tests require API v1.1') - - @classmethod - def resource_setup(cls): - super(JobTest, cls).resource_setup() - # create job binary - job_binary = { - 'name': data_utils.rand_name('sahara-job-binary'), - 'url': 'swift://sahara-container.sahara/example.jar', - 'description': 'Test job binary', - 'extra': { - 'user': cls.os_primary.credentials.username, - 'password': cls.os_primary.credentials.password - } - } - resp_body = cls.create_job_binary(**job_binary) - job_binary_id = resp_body['id'] - - cls.job = { - 'job_type': 'Pig', - 'mains': [job_binary_id] - } - - def _create_job(self, job_name=None): - """Creates Job with optional name specified. - - It creates job and ensures job name. Returns id and name of created - job. - """ - if not job_name: - # generate random name if it's not specified - job_name = data_utils.rand_name('sahara-job') - - # create job - resp_body = self.create_job(job_name, **self.job) - - # ensure that job created successfully - self.assertEqual(job_name, resp_body['name']) - - return resp_body['id'], job_name - - @tc.attr('smoke') - @decorators.idempotent_id('8cf785ca-adf4-473d-8281-fb9a5efa3073') - def test_job_create(self): - self._create_job() - - @tc.attr('smoke') - @decorators.idempotent_id('41e253fe-b02a-41a0-b186-5ff1f0463ba3') - def test_job_list(self): - job_info = self._create_job() - - # check for job in list - jobs = self.client.list_jobs()['jobs'] - jobs_info = [(job['id'], job['name']) for job in jobs] - self.assertIn(job_info, jobs_info) - - @tc.attr('smoke') - @decorators.idempotent_id('3faf17fa-bc94-4a60-b1c3-79e53674c16c') - def test_job_get(self): - job_id, job_name = self._create_job() - - # check job fetch by id - job = self.client.get_job(job_id)['job'] - self.assertEqual(job_name, job['name']) - - @tc.attr('smoke') - @decorators.idempotent_id('dff85e62-7dda-4ad8-b1ee-850adecb0c6e') - def test_job_delete(self): - job_id, _ = self._create_job() - - # delete the job by id - self.client.delete_job(job_id) - self.wait_for_resource_deletion(job_id, self.client.get_job) - - jobs = self.client.list_jobs()['jobs'] - jobs_ids = [job['id'] for job in jobs] - self.assertNotIn(job_id, jobs_ids) diff --git a/sahara_tempest_plugin/tests/api/test_node_group_templates.py b/sahara_tempest_plugin/tests/api/test_node_group_templates.py deleted file mode 100644 index 2fc63770..00000000 --- a/sahara_tempest_plugin/tests/api/test_node_group_templates.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from testtools import testcase as tc - -from tempest.lib import decorators -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.api import base as dp_base - - -class NodeGroupTemplateTest(dp_base.BaseDataProcessingTest): - - @classmethod - def skip_checks(cls): - super(NodeGroupTemplateTest, cls).skip_checks() - if cls.default_plugin is None: - raise cls.skipException("No Sahara plugins configured") - - @classmethod - def resource_setup(cls): - super(NodeGroupTemplateTest, cls).resource_setup() - - def _create_node_group_template(self, template_name=None): - """Creates Node Group Template with optional name specified. - - It creates template, ensures template name and response body. - Returns id and name of created template. - """ - self.node_group_template = self.get_node_group_template() - self.assertIsNotNone(self.node_group_template, - "No known Sahara plugin was found") - - if not template_name: - # generate random name if it's not specified - template_name = data_utils.rand_name('sahara-ng-template') - - # hack the arguments: keep the compatibility with the signature - # of self.create_node_group_template - node_group_template_w = self.node_group_template.copy() - if 'plugin_version' in node_group_template_w: - plugin_version_value = node_group_template_w['plugin_version'] - del node_group_template_w['plugin_version'] - node_group_template_w['hadoop_version'] = plugin_version_value - - # create node group template - resp_body = self.create_node_group_template(template_name, - **node_group_template_w) - - # ensure that template created successfully - self.assertEqual(template_name, resp_body['name']) - self.assertDictContainsSubset(self.node_group_template, resp_body) - - return resp_body['id'], template_name - - @tc.attr('smoke') - @decorators.idempotent_id('63164051-e46d-4387-9741-302ef4791cbd') - def test_node_group_template_create(self): - self._create_node_group_template() - - @tc.attr('smoke') - @decorators.idempotent_id('eb39801d-2612-45e5-88b1-b5d70b329185') - def test_node_group_template_list(self): - template_info = self._create_node_group_template() - - # check for node group template in list - templates = self.client.list_node_group_templates() - templates = templates['node_group_templates'] - templates_info = [(template['id'], template['name']) - for template in templates] - self.assertIn(template_info, templates_info) - - @tc.attr('smoke') - @decorators.idempotent_id('6ee31539-a708-466f-9c26-4093ce09a836') - def test_node_group_template_get(self): - template_id, template_name = self._create_node_group_template() - - # check node group template fetch by id - template = self.client.get_node_group_template(template_id) - template = template['node_group_template'] - self.assertEqual(template_name, template['name']) - self.assertDictContainsSubset(self.node_group_template, template) - - @tc.attr('smoke') - @decorators.idempotent_id('f4f5cb82-708d-4031-81c4-b0618a706a2f') - def test_node_group_template_delete(self): - template_id, _ = self._create_node_group_template() - - # delete the node group template by id - self.client.delete_node_group_template(template_id) - get_resource = self.client.get_node_group_template - self.wait_for_resource_deletion(template_id, get_resource) - - templates = self.client.list_node_group_templates() - templates = templates['node_group_templates'] - templates_ids = [template['id'] for template in templates] - self.assertNotIn(template_id, templates_ids) - - @tc.attr('smoke') - @decorators.idempotent_id('b048b603-832f-4ca5-9d5f-7a0e042f16b5') - def test_node_group_template_update(self): - template_id, template_name = self._create_node_group_template() - - new_template_name = 'updated-template-name' - body = {'name': new_template_name} - updated_template = self.client.update_node_group_template(template_id, - **body) - updated_template = updated_template['node_group_template'] - - self.assertEqual(new_template_name, updated_template['name']) diff --git a/sahara_tempest_plugin/tests/api/test_plugins.py b/sahara_tempest_plugin/tests/api/test_plugins.py deleted file mode 100644 index 8ce3a06f..00000000 --- a/sahara_tempest_plugin/tests/api/test_plugins.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from testtools import testcase as tc - -from tempest.lib import decorators -from tempest import config - -from sahara_tempest_plugin.tests.api import base as dp_base - -CONF = config.CONF - - -class PluginsTest(dp_base.BaseDataProcessingTest): - def _list_all_plugin_names(self): - """Returns all enabled plugin names. - - It ensures main plugins availability. - """ - plugins = self.client.list_plugins()['plugins'] - plugins_names = [plugin['name'] for plugin in plugins] - for enabled_plugin in CONF.data_processing_feature_enabled.plugins: - self.assertIn(enabled_plugin, plugins_names) - - return plugins_names - - @tc.attr('smoke') - @decorators.idempotent_id('01a005a3-426c-4c0b-9617-d09475403e09') - def test_plugin_list(self): - self._list_all_plugin_names() - - @tc.attr('smoke') - @decorators.idempotent_id('53cf6487-2cfb-4a6f-8671-97c542c6e901') - def test_plugin_get(self): - for plugin_name in self._list_all_plugin_names(): - plugin = self.client.get_plugin(plugin_name)['plugin'] - self.assertEqual(plugin_name, plugin['name']) - - for plugin_version in plugin['versions']: - detailed_plugin = self.client.get_plugin(plugin_name, - plugin_version) - detailed_plugin = detailed_plugin['plugin'] - self.assertEqual(plugin_name, detailed_plugin['name']) - - # check that required image tags contains name and version - image_tags = detailed_plugin['required_image_tags'] - self.assertIn(plugin_name, image_tags) - self.assertIn(plugin_version, image_tags) diff --git a/sahara_tempest_plugin/tests/cli/README.rst b/sahara_tempest_plugin/tests/cli/README.rst deleted file mode 100644 index 78f86024..00000000 --- a/sahara_tempest_plugin/tests/cli/README.rst +++ /dev/null @@ -1,5 +0,0 @@ -================ -Sahara CLI Tests -================ - -This directory contains tests to cover CLI commands of saharaclient. diff --git a/sahara_tempest_plugin/tests/cli/__init__.py b/sahara_tempest_plugin/tests/cli/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tempest_plugin/tests/cli/base.py b/sahara_tempest_plugin/tests/cli/base.py deleted file mode 100644 index 1a64e155..00000000 --- a/sahara_tempest_plugin/tests/cli/base.py +++ /dev/null @@ -1,209 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import os -import fixtures -import time - -from tempest import config -from tempest.lib.cli import base -from tempest import test -from tempest.lib import exceptions as exc -from tempest.lib.auth import IDENTITY_VERSION -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.common import plugin_utils - -TEMPEST_CONF = config.CONF -DEL_RESULT = '''\ -{} "{}" has been removed successfully. -''' -TEMPEST_ERROR_MESSAGE = 'No matches found.' - - -class ClientTestBase(base.ClientTestBase): - """Base class for saharaclient tests. - - Establishes the sahara client and retrieves the essential environment - information. - """ - - def _get_clients(self): - cli_dir = os.environ.get('OS_SAHARA_TESTS_DIR', '') - if not cli_dir: - # if this is executed in a virtualenv, the command installed there - # will be the first one. - paths = os.environ.get('PATH').split(':') - for path in paths: - client_candidate = os.path.join(path, 'openstack') - if os.path.isfile(client_candidate) and os.access( - client_candidate, os.X_OK): - cli_dir = path - break - - self.client_manager_admin = \ - test.BaseTestCase.get_client_manager('admin') - auth_provider = self.client_manager_admin.auth_provider - self.project_network = test.BaseTestCase.get_tenant_network('admin') - - project_name = auth_provider.credentials.get('project_name') - if project_name is None: - project_name = auth_provider.credentials.get('tenant_name') - - # complicated but probably the only way to get the exact type - # of Identity API version - if isinstance(auth_provider, IDENTITY_VERSION['v2'][1]): - identity_api_version = 2 - else: - identity_api_version = 3 - - return base.CLIClient( - username=auth_provider.credentials.get('username'), - password=auth_provider.credentials.get('password'), - tenant_name=project_name, - uri=auth_provider.base_url({'service': 'identity'}), - cli_dir=cli_dir, - user_domain=auth_provider.credentials.get('user_domain_name'), - project_domain=auth_provider.credentials.get( - 'project_domain_name'), - identity_api_version=identity_api_version) - - def openstack(self, action, flags='', params='', fail_ok=False, - merge_stderr=False): - if '--os-data-processing-api-version' not in flags: - flags = flags + '--os-data-processing-api-version %s' % \ - (TEMPEST_CONF.data_processing.api_version_saharaclient) - return self.clients.openstack(action, flags=flags, params=params, - fail_ok=fail_ok, merge_stderr=False) - - def listing_result(self, command): - command_for_item = self.openstack('dataprocessing', params=command) - result = self.parser.listing(command_for_item) - return result - - def find_in_listing(self, result, value, field='name'): - for line in result: - if line['Field'].lower() == field.lower(): - self.assertEqual(line['Value'].lower(), value.lower()) - return - raise self.skipException('No table to show information') - - def check_if_delete(self, command, name): - delete_cmd = self.openstack('dataprocessing %s delete' % command, - params=name) - result = DEL_RESULT.format(command, name) - # lower() is required because "command" in the result string could - # have the first letter capitalized. - self.assertEqual(delete_cmd.lower(), result.lower()) - - def update_resource_value(self, command, value, params): - new_value = data_utils.rand_name(value) - command = '%s update %s' % (command, value) - params = '%s %s' % (params, new_value) - update_result = self.listing_result('%s %s' % (command, params)) - self.find_in_listing(update_result, new_value) - return new_value - - def delete_resource(self, command, name): - list_of_resources = self.listing_result('%s list' % command) - list_of_resource_names = [r['Name'] for r in list_of_resources] - if name in list_of_resource_names: - self.openstack('dataprocessing %s delete' % command, params=name) - - def get_default_plugin(self): - plugins = self.listing_result('plugin list') - default_plugin_name = plugin_utils.get_default_plugin() - for plugin in plugins: - if plugin['Name'] == default_plugin_name: - return plugin - raise self.skipException('No available plugins for testing') - - def find_id_of_pool(self): - floating_pool_list = self.openstack('network list --external') - floating_pool = self.parser.listing(floating_pool_list) - if not floating_pool: - raise self.skipException('Floating pool ip list is empty') - # if not empty, there should be at least one element - return floating_pool[0]['ID'] - - def _get_cluster_status(self, cluster_name): - status = None - show_cluster = self.listing_result('cluster show %s' % cluster_name) - for line in show_cluster: - if line['Field'] == 'Status': - status = line['Value'] - if status is None: - raise self.skipException('Can not find the cluster to get its ' - 'status') - return status - - def _get_resource_id(self, resource, resource_name): - resource_id = None - show_resource = self.listing_result('%s show %s' - % (resource, resource_name)) - for line in show_resource: - if line['Field'] == 'Id': - resource_id = line['Value'] - if resource_id is None: - raise self.skipException('No such %s exists' % resource) - return resource_id - - def _poll_cluster_status(self, cluster_name): - with fixtures.Timeout(TEMPEST_CONF.data_processing.cluster_timeout, - gentle=True): - while True: - status = self._get_cluster_status(cluster_name) - if status == 'Active': - break - if status == 'Error': - raise exc.TempestException("Cluster in %s state" % status) - time.sleep(3) - - def wait_for_resource_deletion(self, name, type): - # type can be cluster, cluster template or node group template string - name_exist = False - # if name exists in the command "type list" than tests should fail - with fixtures.Timeout(300, gentle=True): - while True: - list_of_types = self.listing_result('%s list' % type) - list_names = [p['Name'] for p in list_of_types] - if name in list_names: - name_exist = True - if not name_exist: - break - - def check_negative_scenarios(self, error_message, cmd, name): - msg_exist = None - try: - self.openstack('dataprocessing %s' % cmd, params=name) - except exc.CommandFailed as e: - # lower() is required because "result" string could - # have the first letter capitalized. - if error_message.lower() in str(e).lower(): - msg_exist = True - if not msg_exist: - raise exc.TempestException('"%s" is not a part of output of ' - 'executed command "%s" (%s)' - % (error_message, cmd, output_msg)) - else: - raise exc.TempestException('"%s %s" in negative scenarios has ' - 'been executed without any errors' - % (cmd, name)) - - @classmethod - def tearDownClass(cls): - if hasattr(super(ClientTestBase, cls), 'tearDownClass'): - super(ClientTestBase, cls).tearDownClass() - # this'll be needed as long as BaseTestCase functions - # are used in this class, otherwise projects, users, - # networks and routers created won't be deleted - test.BaseTestCase.clear_credentials() diff --git a/sahara_tempest_plugin/tests/cli/cluster_templates.py b/sahara_tempest_plugin/tests/cli/cluster_templates.py deleted file mode 100644 index c15997f3..00000000 --- a/sahara_tempest_plugin/tests/cli/cluster_templates.py +++ /dev/null @@ -1,55 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from sahara_tempest_plugin.tests.cli import base -from tempest.lib.common.utils import data_utils - - -class SaharaClusterTemplateCLITest(base.ClientTestBase): - - def openstack_cluster_template_list(self): - self.assertTableStruct(self.listing_result('cluster template list'), [ - 'Name', - 'Id', - 'Plugin name', - 'Plugin version' - ]) - - def openstack_cluster_template_create(self, ng_master, ng_worker): - cluster_template_name = data_utils.rand_name('cl-tmp') - flag = ("%(ct_name)s %(ngm)s %(ngw)s " - % {'ngw': ''.join([ng_worker, ':3']), - 'ngm': ''.join([' --node-groups ', ng_master, ':1 ']), - 'ct_name': ''.join(['--name ', cluster_template_name])}) - self.assertTableStruct( - self.listing_result(''.join(['cluster template create ', flag])), - [ - 'Field', - 'Value' - ]) - return cluster_template_name - - def openstack_cluster_template_show(self, cluster_template_name): - self.find_in_listing( - self.listing_result( - ''.join(['cluster template show ', cluster_template_name])), - cluster_template_name) - - def openstack_cluster_template_update(self, cluster_template_name): - cmd = 'cluster template' - new_template_name = self.update_resource_value(cmd, - cluster_template_name, - '--name') - return new_template_name - - def openstack_cluster_template_delete(self, cluster_template_name): - self.check_if_delete('cluster template', cluster_template_name) diff --git a/sahara_tempest_plugin/tests/cli/clusters.py b/sahara_tempest_plugin/tests/cli/clusters.py deleted file mode 100644 index ef1472bd..00000000 --- a/sahara_tempest_plugin/tests/cli/clusters.py +++ /dev/null @@ -1,97 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from sahara_tempest_plugin.tests.cli import base -from tempest.lib.common.utils import data_utils -import fixtures - -VERIF_RESULT = '''\ -Cluster "%s" health verification has been started. -''' - - -class SaharaClusterCLITest(base.ClientTestBase): - - def openstack_cluster_list(self): - self.assertTableStruct(self.listing_result('cluster list'), [ - 'Name', - 'Id', - 'Plugin name', - 'Plugin version', - 'Status' - ]) - - def openstack_cluster_create(self, cluster_template_name, image_name): - cluster_name = data_utils.rand_name('cluster') - # FIXME: this is unstable interface, but apparently there are no - # suitable ready-to-use replacements. - id_net_project = self.project_network['id'] - flags = ("%(name)s %(cl-tm)s %(image)s %(network)s" - % {'network': '--neutron-network %s' % (id_net_project), - 'image': '--image %s' % (image_name), - 'cl-tm': '--cluster-template %s' % - (cluster_template_name), - 'name': '--name %s' % (cluster_name)}) - self.assertTableStruct( - self.listing_result(''.join(['cluster create ', flags])), [ - 'Field', - 'Value' - ]) - self._poll_cluster_status(cluster_name) - return cluster_name - - def openstack_cluster_delete(self, cluster_name): - delete_cluster = self.listing_result(''.join(['cluster delete ', - cluster_name])) - self.assertTableStruct(delete_cluster, [ - 'Field', - 'Value' - ]) - - def openstack_cluster_show(self, cluster_name): - self.find_in_listing( - self.listing_result(''.join(['cluster show ', cluster_name])), - cluster_name) - - def openstack_cluster_update(self, cluster_name): - self.assertTableStruct( - self.listing_result(''.join(['cluster update ', - '--description cli-tests ', - cluster_name])), [ - 'Field', - 'Value' - ]) - - def openstack_cluster_verification_show(self, cluster_name): - self.assertTableStruct( - self.listing_result(''.join(['cluster verification --show ', - cluster_name])), [ - 'Field', - 'Value' - ]) - - def openstack_cluster_verification_start(self, cluster_name): - result = self.openstack('dataprocessing cluster verification --start', - params=cluster_name) - expected_result = VERIF_RESULT % cluster_name - self.assertEqual(result, expected_result) - - def openstack_cluster_scale(self, cluster_name, ng_worker): - with fixtures.Timeout(300, gentle=True): - scale_cluster = self.listing_result( - ''.join(['cluster scale --instances ', ng_worker, - ':2 --wait ', cluster_name])) - self.assertTableStruct(scale_cluster, [ - 'Field', - 'Value' - ]) - self._poll_cluster_status(cluster_name) diff --git a/sahara_tempest_plugin/tests/cli/data_sources.py b/sahara_tempest_plugin/tests/cli/data_sources.py deleted file mode 100644 index 7e0b4a5c..00000000 --- a/sahara_tempest_plugin/tests/cli/data_sources.py +++ /dev/null @@ -1,55 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from sahara_tempest_plugin.tests.cli import base -from tempest.lib.common.utils import data_utils - - -class SaharaDataSourceCLITest(base.ClientTestBase): - - def openstack_data_source_list(self): - self.assertTableStruct(self.listing_result('data source list'), [ - 'Name', - 'Type' - ]) - - def openstack_data_source_create(self): - data_source_name = data_utils.rand_name('data-source') - flag = ("%(name)s %(type)s %(url)s %(user)s %(pass)s" - % {'type': '--type hdfs', - 'url': '--url hdfs://demo/input.tar.gz', - 'user': ' --user cli-test', - 'pass': '--password cli', - 'name': data_source_name}) - self.assertTableStruct( - self.listing_result('data source create %s' % flag), - [ - 'Field', - 'Value' - ]) - return data_source_name - - def openstack_data_source_show(self, data_source_name): - self.find_in_listing( - self.listing_result('data source show %s' % data_source_name), - data_source_name) - - def openstack_data_source_update(self, data_source_name): - self.assertTableStruct( - self.listing_result('data source update %s' % data_source_name), - [ - 'Field', - 'Value' - ]) - - def openstack_data_source_delete(self, data_source_name): - self.check_if_delete('data source', data_source_name) diff --git a/sahara_tempest_plugin/tests/cli/images.py b/sahara_tempest_plugin/tests/cli/images.py deleted file mode 100644 index bee42622..00000000 --- a/sahara_tempest_plugin/tests/cli/images.py +++ /dev/null @@ -1,89 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from sahara_tempest_plugin.tests.cli import base - - -class SaharaImageCLITest(base.ClientTestBase): - - def openstack_image_list(self): - self.assertTableStruct(self.listing_result('image list'), [ - 'Name', - 'Id', - 'Username', - 'Tags' - ]) - - def openstack_image_register(self, name_to_register, username): - images_list = self.openstack('image list') - images = self.parser.listing(images_list) - images_name = [p['Name'] for p in images] - flag = None - for image_name in images_name: - if image_name == name_to_register: - flag = (' --username %s %s' % (username, image_name)) - if flag is None: - raise self.skipException('No available image for testing') - self.assertTableStruct( - self.listing_result(''.join(['image register', flag])), [ - 'Field', - 'Value' - ]) - return name_to_register - - def openstack_image_show(self, image_name): - self.assertTableStruct( - self.listing_result(''.join(['image show ', image_name])), [ - 'Field', - 'Value' - ]) - - def openstack_image_tags_add(self, image_name): - plugin = self.get_default_plugin() - flag = '%s --tags %s %s' % (image_name, - plugin['Name'], - plugin['Versions']) - self.assertTableStruct( - self.listing_result(''.join(['image tags add ', flag])), [ - 'Field', - 'Value' - ]) - - def openstack_image_tags_set(self, image_name): - flag = ''.join([image_name, ' --tags update_tag']) - self.assertTableStruct( - self.listing_result(''.join(['image tags set ', flag])), [ - 'Field', - 'Value' - ]) - - def openstack_image_tags_remove(self, image_name): - flag = ''.join([image_name, ' --tags update_tag']) - self.assertTableStruct( - self.listing_result(''.join(['image tags remove ', flag])), [ - 'Field', - 'Value' - ]) - - def openstack_image_unregister(self, image_name): - self.assertTableStruct( - self.listing_result(''.join(['image unregister ', image_name])), [ - 'Field', - 'Value' - ]) - - def negative_unregister_not_existing_image(self, image_name): - """Test to unregister already unregistrated image""" - command_to_execute = 'image unregister' - self.check_negative_scenarios(base.TEMPEST_ERROR_MESSAGE, - command_to_execute, - image_name) diff --git a/sahara_tempest_plugin/tests/cli/job_binaries.py b/sahara_tempest_plugin/tests/cli/job_binaries.py deleted file mode 100644 index c86989e8..00000000 --- a/sahara_tempest_plugin/tests/cli/job_binaries.py +++ /dev/null @@ -1,133 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from filecmp import cmp -from os import fdopen -from os import path -from os import remove -import tempfile - -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.cli import base - - -class SaharaJobBinaryCLITest(base.ClientTestBase): - - def openstack_job_binary_list(self): - self.assertTableStruct(self.listing_result('job binary list'), [ - 'Name', - 'Id', - 'Url' - ]) - - def openstack_job_binary_create(self, job_internal=True): - job_binary_name = data_utils.rand_name('job-fake') - script_name = '' - if job_internal: - fd, script_name = tempfile.mkstemp() - with fdopen(fd, 'w+') as jb: - jb.write('test-script') - flag = ("%(jb_name)s %(data)s " - % {'jb_name': ('--name %s' % job_binary_name), - 'data': ' --data %s' % script_name}) - else: - flag = ("%(jb_name)s --url swift://mybucket.sahara/foo " - "--username foo --password bar" - % {'jb_name': ('--name %s' % job_binary_name)}) - self.assertTableStruct( - self.listing_result('job binary create %s' % flag), - [ - 'Field', - 'Value' - ]) - return job_binary_name, script_name - - def openstack_job_binary_download(self, job_binary_name, - original_file=None): - if path.exists(job_binary_name): - remove(job_binary_name) - - self.openstack('dataprocessing job binary download', - params=job_binary_name) - - self.assertTrue(path.exists(job_binary_name)) - if original_file: - self.assertTrue(cmp(job_binary_name, original_file)) - remove(original_file) - remove(job_binary_name) - - def openstack_job_binary_show(self, job_binary_name): - self.find_in_listing(self.listing_result('job binary show %s' - % job_binary_name), - job_binary_name) - - def openstack_job_binary_update(self, job_binary_name, flag=None): - cmd = 'job binary update --%s' % flag - if flag == 'description': - self.assertTableStruct( - self.listing_result('%s cli-tests %s' - % (cmd, job_binary_name)), [ - 'Field', - 'Value' - ]) - elif flag == 'name': - new_job_binary_name = data_utils.rand_name(job_binary_name) - self.assertTableStruct( - self.listing_result('%s %s %s' - % (cmd, new_job_binary_name, - job_binary_name)), [ - 'Field', - 'Value' - ]) - return new_job_binary_name - else: - # here we check only updating with public/protected flags for now - self.assertTableStruct( - self.listing_result('%s %s' % (cmd, job_binary_name)), [ - 'Field', - 'Value' - ] - ) - - def openstack_job_binary_delete(self, job_binary_name): - self.openstack('dataprocessing job binary delete', - params=job_binary_name) - list_job_binary = self.listing_result('job binary list') - for job_binary in list_job_binary: - if job_binary['Name'] == job_binary_name: - raise self.skipException('Job binary is not delete') - - def negative_delete_removed_job_binary(self, job_binary_name): - """Test to remove already deleted job binary""" - command_to_execute = 'job binary delete' - self.check_negative_scenarios(base.TEMPEST_ERROR_MESSAGE, - command_to_execute, - job_binary_name) - - def negative_try_to_update_protected_jb(self, job_binary_name): - """Test to try to update proteted job binary""" - self.openstack_job_binary_update(job_binary_name, flag='protected') - error_message = ("JobBinary with id '%s' could not be updated because " - "it's marked as protected" % - self._get_resource_id('job binary', - job_binary_name)) - command_to_execute = 'job binary update --name test' - self.check_negative_scenarios(error_message, - command_to_execute, - job_binary_name) - - def filter_job_binaries_in_list(self): - """Filter job binaries list with --column flag""" - job_binaries_list = self.listing_result('job binary list ' - '--column Name') - self.assertTableStruct(job_binaries_list, ['Name']) diff --git a/sahara_tempest_plugin/tests/cli/job_templates.py b/sahara_tempest_plugin/tests/cli/job_templates.py deleted file mode 100644 index b615e6a2..00000000 --- a/sahara_tempest_plugin/tests/cli/job_templates.py +++ /dev/null @@ -1,55 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.cli import base - - -class SaharaJobTemplateCLITest(base.ClientTestBase): - - def openstack_job_template_list(self): - self.assertTableStruct(self.listing_result('job template list'), [ - 'Name', - 'Id', - 'Type' - ]) - - def openstack_job_template_create(self, job_binary_name): - job_template_name = data_utils.rand_name('job-template') - flag = ("%(name)s %(type)s %(main)s " - % {'name': ' --name %s' % job_template_name, - 'type': ' --type Shell', - 'main': ' --main %s' % job_binary_name}) - self.assertTableStruct( - self.listing_result('job template create %s' % flag), - [ - 'Field', - 'Value' - ]) - return job_template_name - - def openstack_job_template_show(self, job_template_name): - self.find_in_listing( - self.listing_result('job template show %s' % job_template_name), - job_template_name) - - def openstack_job_template_update(self, job_template_name): - self.assertTableStruct( - self.listing_result('job template update --description ' - 'cli-tests %s' % job_template_name), [ - 'Field', - 'Value' - ]) - - def openstack_job_template_delete(self, job_template_name): - self.check_if_delete('job template', job_template_name) diff --git a/sahara_tempest_plugin/tests/cli/job_types.py b/sahara_tempest_plugin/tests/cli/job_types.py deleted file mode 100644 index 46fd27cc..00000000 --- a/sahara_tempest_plugin/tests/cli/job_types.py +++ /dev/null @@ -1,51 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from os import remove - -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.cli import base - -GET_CONFIG_RESULT = '"%s" job configs were saved in "%s"file' - - -class SaharaJobTypeCLITest(base.ClientTestBase): - - def openstack_job_type_list(self): - self.assertTableStruct(self.listing_result('job type list'), [ - 'Name', - 'Plugins' - ]) - - def openstack_job_type_configs_get(self, flag=None): - list_job_type = self.listing_result('job type list') - job_type_names = [p['Name'] for p in list_job_type] - if len(job_type_names) == 0: - raise self.skipException('No job types to get configs') - cmd = 'job type configs get' - type_name = job_type_names[0] - if flag == 'file': - file_name = data_utils.rand_name('filename') - cmd = '%s --%s %s %s' % (cmd, flag, file_name, type_name) - else: - file_name = type_name - cmd = '%s %s' % (cmd, type_name) - result = self.openstack('dataprocessing %s' % cmd) - expected_result = GET_CONFIG_RESULT % (type_name, file_name) - self.assertEqual(result, expected_result) - remove(file_name) - - def filter_job_type_in_list(self): - """Filter job types list with --column flag""" - list_job_types = self.listing_result('job type list --column Name') - self.assertTableStruct(list_job_types, ['Name']) diff --git a/sahara_tempest_plugin/tests/cli/jobs.py b/sahara_tempest_plugin/tests/cli/jobs.py deleted file mode 100644 index 665ad7b8..00000000 --- a/sahara_tempest_plugin/tests/cli/jobs.py +++ /dev/null @@ -1,69 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from sahara_tempest_plugin.tests.cli import base - -DELETE_RES = '''\ -job "%s" deletion has been started. -''' - - -class SaharaJobCLITest(base.ClientTestBase): - - def openstack_job_list(self): - self.assertTableStruct(self.listing_result('job list'), [ - 'Id', - 'Cluster id', - 'Job id', - 'Status' - ]) - - def openstack_job_execute(self, cluster_name, job_template_name, input, - output): - flag = ("%(cluster)s %(jt)s %(input)s %(output)s" - % {'cluster': ' --cluster %s' % cluster_name, - 'jt': ' --job-template %s' % job_template_name, - 'input': ' --input %s' % input, - 'output': ' --output %s' % output}) - result = self.listing_result('job execute %s' % flag) - self.assertTableStruct( - result, - [ - 'Field', - 'Value' - ]) - for line in result: - if line['Field'] == 'Id': - return line['Value'] - - def openstack_job_show(self, job_id): - result = self.listing_result('job show %s' % job_id) - check_table = False - for line in result: - if line['Field'] == 'Id': - self.assertEqual(line['Value'], job_id) - check_table = True - if not check_table: - raise self.skipException('No table to show information') - - def openstack_job_update(self, job_id): - self.assertTableStruct( - self.listing_result( - 'job update --public %s' % job_id), [ - 'Field', - 'Value' - ]) - - def openstack_job_delete(self, job_id): - delete_job = self.openstack('dataprocessing job delete', - params=job_id) - self.assertEqual(delete_job.lower(), DELETE_RES % job_id) diff --git a/sahara_tempest_plugin/tests/cli/node_group_templates.py b/sahara_tempest_plugin/tests/cli/node_group_templates.py deleted file mode 100644 index 9dd5c69c..00000000 --- a/sahara_tempest_plugin/tests/cli/node_group_templates.py +++ /dev/null @@ -1,118 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.cli import base - - -class SaharaNodeGroupCLITest(base.ClientTestBase): - - def openstack_node_group_template_list(self): - self.assertTableStruct( - self.listing_result('node group template list'), [ - 'Name', - 'Id', - 'Plugin version', - 'Plugin name' - ]) - - def openstack_node_group_template_create(self, ng_type, flavor_id): - plugin = self.get_default_plugin() - id_net_pool = self.find_id_of_pool() - node_group_name = data_utils.rand_name(ng_type) - plugin_version = plugin['Versions'].split(',')[0].strip() - flags = ("%(ngt_name)s %(plugin)s %(plugin-version)s " - "%(processes)s %(flavor)s %(floating-pool)s" - % {'floating-pool': ' --floating-ip-pool %s' % id_net_pool, - 'flavor': ' --flavor %s' % flavor_id, - 'processes': ' --processes datanode', - 'plugin-version': ' --plugin-version %s' % plugin_version, - 'plugin': ' --plugin %s' % plugin['Name'], - 'ngt_name': '--name %s' % node_group_name}) - self.assertTableStruct( - self.listing_result('node group template create %s' % flags), [ - 'Field', - 'Value' - ]) - return node_group_name - - def openstack_node_group_template_show(self, node_group_name): - self.find_in_listing( - self.listing_result('node group template show %s' - % node_group_name), - node_group_name) - - def openstack_node_group_template_update(self, node_group_name, - update_field=None): - """Update node group template with necessary parameters - Args: - node_group_name (str): name of node group to update - update_field (str): param how to update the node group. Using - this arg, there are several available updates of node group: - name, public/private, protected/unprotected - """ - cmd = 'node group template' - if update_field == 'name': - new_node_group_name = self.update_resource_value(cmd, - node_group_name, - '--name') - return new_node_group_name - elif update_field in ('protected', 'unprotected'): - # here we check only updating with public/protected flags for now - update_cmd = 'update %s --%s' % (node_group_name, update_field) - result = self.listing_result('%s %s' % (cmd, update_cmd)) - is_protected_value = str(update_field == 'protected') - self.find_in_listing(result, is_protected_value, 'is protected') - self.assertTableStruct(result, ['Field', 'Value']) - - def openstack_node_group_template_delete(self, node_group_name): - self.check_if_delete('node group template', node_group_name) - - def negative_delete_removed_node_group(self, node_group_name): - """Test to remove already deleted node group template""" - command_to_execute = 'node group template delete' - self.check_negative_scenarios(base.TEMPEST_ERROR_MESSAGE, - command_to_execute, - node_group_name) - - def negative_try_to_delete_protected_node_group(self, node_group_name): - """Test to delete protected node group template""" - self.openstack_node_group_template_update(node_group_name, - update_field='protected') - error_message = ("NodeGroupTemplate with id '%s' could not be deleted " - "because it's marked as protected" - % self._get_resource_id('node group template', - node_group_name)) - command_to_execute = 'node group template delete' - self.check_negative_scenarios(error_message, - command_to_execute, - node_group_name) - self.openstack_node_group_template_update(node_group_name, - update_field='unprotected') - - def filter_node_group_list_with_plugin(self): - """Test to filter node group templates with the plugin""" - plugins_list = self.listing_result('plugin list') - plugins_names = [p['Name'] for p in plugins_list] - if len(plugins_names) == 0: - raise self.skipException('No plugin to filter node group') - filter_cmd = self.listing_result( - 'node group template list --plugin %s' % plugins_names[0]) - for ng in filter_cmd: - self.assertEqual(plugins_names[0], ng['Plugin name']) - self.assertTableStruct(filter_cmd, [ - 'Name', - 'Id', - 'Plugin version', - 'Plugin name' - ]) diff --git a/sahara_tempest_plugin/tests/cli/plugins.py b/sahara_tempest_plugin/tests/cli/plugins.py deleted file mode 100644 index 5dd6323d..00000000 --- a/sahara_tempest_plugin/tests/cli/plugins.py +++ /dev/null @@ -1,95 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from os import path -from os import remove -import re -import tempfile - -from oslo_serialization import jsonutils - -from sahara_tempest_plugin.tests.cli import base - - -class SaharaPluginCLITest(base.ClientTestBase): - - def openstack_plugin_list(self): - self.assertTableStruct(self.listing_result('plugin list'), [ - 'Name', - 'Versions' - ]) - - def openstack_plugin_show(self): - list_plugin = self.listing_result('plugin list') - name = [p['Name'] for p in list_plugin] - if len(name) == 0: - raise self.skipException('No plugins to show') - self.assertTableStruct( - self.listing_result(''.join(['plugin show ', name[0]])), [ - 'Field', - 'Value' - ]) - - def openstack_plugin_configs_get(self): - list_plugin = self.listing_result('plugin list') - name = [p['Name'] for p in list_plugin] - version = [p['Versions'] for p in list_plugin] - if len(name) == 0: - raise self.skipException('No plugin to get configs') - plugin_name = name[0] - plugin_version = version[0].split(',')[0].strip() - - configs_file = '%s-%s' % (plugin_name, plugin_version) - if path.exists(configs_file): - remove(configs_file) - - outmsg = self.openstack('dataprocessing plugin configs get', - params='%s %s' % (plugin_name, plugin_version)) - outfile_match = re.search('configs was saved in "(.+)"', outmsg) - if outfile_match: - configs_file = outfile_match.group(1) - else: - configs_file = '%s-%s' % (plugin_name, plugin_version) - - result = path.exists(configs_file) - self.assertTrue(result) - remove(configs_file) - - def openstack_plugin_update(self): - # check plugin list and 'fake' is available - list_plugin = self.listing_result('plugin list') - name = [p['Name'] for p in list_plugin] - if len(name) == 0: - raise self.skipException('No plugin to update') - if 'fake' not in name: - raise self.skipException('fake plugin is unavailable') - - # update value of "hidden:status" to False - update_dict = {'plugin_labels': {'hidden': {'status': False}}} - self._update_with_json_file(update_dict) - - # update value and verified it - update_dict = {'plugin_labels': {'hidden': {'status': True}}} - update_info = self._update_with_json_file(update_dict) - update_dict = jsonutils.loads(update_info) - self.assertTrue(update_dict['Plugin: hidden']) - - def _update_with_json_file(self, update_dict): - update_info = jsonutils.dumps(update_dict) - tmp_file = tempfile.mkstemp(suffix='.json')[1] - with open(tmp_file, 'w+') as fd: - fd.write(update_info) - update_result = self.openstack('dataprocessing plugin ' - 'update -f json fake', - params=tmp_file) - remove(tmp_file) - return update_result diff --git a/sahara_tempest_plugin/tests/cli/test_scenario.py b/sahara_tempest_plugin/tests/cli/test_scenario.py deleted file mode 100644 index e05da3d4..00000000 --- a/sahara_tempest_plugin/tests/cli/test_scenario.py +++ /dev/null @@ -1,223 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import testtools - -from tempest import config -from tempest.lib import decorators - -from sahara_tempest_plugin.tests.cli import clusters -from sahara_tempest_plugin.tests.cli import cluster_templates -from sahara_tempest_plugin.tests.cli import images -from sahara_tempest_plugin.tests.cli import node_group_templates -from sahara_tempest_plugin.tests.cli import plugins -from sahara_tempest_plugin.tests.cli import job_binaries -from sahara_tempest_plugin.tests.cli import jobs -from sahara_tempest_plugin.tests.cli import job_templates -from sahara_tempest_plugin.tests.cli import data_sources -from sahara_tempest_plugin.tests.cli import job_types - -TEMPEST_CONF = config.CONF -NODE_GROUP_TEMPLATE = 'node group template' - - -class Scenario(images.SaharaImageCLITest, - node_group_templates.SaharaNodeGroupCLITest, - cluster_templates.SaharaClusterTemplateCLITest, - clusters.SaharaClusterCLITest, - plugins.SaharaPluginCLITest, - job_binaries.SaharaJobBinaryCLITest, - jobs.SaharaJobCLITest, - job_templates.SaharaJobTemplateCLITest, - data_sources.SaharaDataSourceCLITest, - job_types.SaharaJobTypeCLITest): - - def test_plugin_cli(self): - self.openstack_plugin_list() - self.openstack_plugin_show() - self.openstack_plugin_configs_get() - if TEMPEST_CONF.data_processing.plugin_update_support: - self.openstack_plugin_update() - - def test_node_group_cli(self): - master_ngt = self.openstack_node_group_template_create('master', '4') - worker_ngt = self.openstack_node_group_template_create('worker', '3') - self.addCleanup(self.delete_resource, NODE_GROUP_TEMPLATE, master_ngt) - self.addCleanup(self.delete_resource, NODE_GROUP_TEMPLATE, worker_ngt) - self.filter_node_group_list_with_plugin() - self.openstack_node_group_template_list() - new_master_ngt = self.openstack_node_group_template_update( - master_ngt, update_field='name') - self.addCleanup(self.delete_resource, NODE_GROUP_TEMPLATE, - new_master_ngt) - - self.openstack_node_group_template_show(new_master_ngt) - self.openstack_node_group_template_delete(new_master_ngt) - self.negative_try_to_delete_protected_node_group(worker_ngt) - self.openstack_node_group_template_delete(worker_ngt) - self.wait_for_resource_deletion(new_master_ngt, NODE_GROUP_TEMPLATE) - self.wait_for_resource_deletion(worker_ngt, NODE_GROUP_TEMPLATE) - self.negative_delete_removed_node_group(worker_ngt) - - def test_cluster_template_cli(self): - cluster_template_cmd = 'cluster template' - ng_master = ( - self.openstack_node_group_template_create('tmp-master', '4')) - ng_worker = ( - self.openstack_node_group_template_create('tmp-worker', '3')) - self.addCleanup(self.delete_resource, NODE_GROUP_TEMPLATE, - ng_master) - self.addCleanup(self.delete_resource, NODE_GROUP_TEMPLATE, - ng_worker) - - cluster_template_name = ( - self.openstack_cluster_template_create(ng_master, ng_worker)) - self.addCleanup(self.delete_resource, cluster_template_cmd, - cluster_template_name) - - self.openstack_cluster_template_list() - self.openstack_cluster_template_show(cluster_template_name) - new_cluster_template_name = self.openstack_cluster_template_update( - cluster_template_name) - self.addCleanup(self.delete_resource, cluster_template_cmd, - new_cluster_template_name) - - self.openstack_cluster_template_delete(new_cluster_template_name) - self.wait_for_resource_deletion(new_cluster_template_name, - cluster_template_cmd) - self.openstack_node_group_template_delete(ng_master) - self.openstack_node_group_template_delete(ng_worker) - self.wait_for_resource_deletion(ng_master, NODE_GROUP_TEMPLATE) - self.wait_for_resource_deletion(ng_worker, NODE_GROUP_TEMPLATE) - - @decorators.skip_because(bug="1629295") - def test_cluster_cli(self): - image_name = self.openstack_image_register( - TEMPEST_CONF.data_processing.test_image_name, - TEMPEST_CONF.data_processing.test_ssh_user) - self.openstack_image_tags_set(image_name) - self.openstack_image_tags_remove(image_name) - self.openstack_image_tags_add(image_name) - self.openstack_image_show(image_name) - self.openstack_image_list() - flavors_client = self.client_manager_admin.flavors_client - flavor_ref = flavors_client.create_flavor(name='sahara-flavor', - ram=512, vcpus=1, disk=4, - id=20)['flavor'] - self.addCleanup(flavors_client.delete_flavor, flavor_ref['id']) - self.addCleanup(self.openstack_image_unregister, image_name) - - ng_master = self.openstack_node_group_template_create('cli-cluster' - '-master', - 'sahara-flavor') - ng_worker = self.openstack_node_group_template_create('cli-cluster' - '-worker', - 'sahara-flavor') - self.addCleanup(self.delete_resource, 'node group template', - ng_master) - self.addCleanup(self.delete_resource, 'node group template', - ng_worker) - - cluster_template_name = ( - self.openstack_cluster_template_create(ng_master, ng_worker)) - self.addCleanup(self.delete_resource, 'cluster template', - cluster_template_name) - - cluster_name = ( - self.openstack_cluster_create(cluster_template_name, image_name)) - self.addCleanup(self.delete_resource, 'cluster', - cluster_name) - - self._run_job_on_cluster(cluster_name) - self.openstack_cluster_list() - self.openstack_cluster_show(cluster_name) - self.openstack_cluster_update(cluster_name) - self.openstack_cluster_verification_show(cluster_name) - self.openstack_cluster_verification_start(cluster_name) - self.openstack_cluster_scale(cluster_name, ng_worker) - self.openstack_cluster_delete(cluster_name) - self.wait_for_resource_deletion(cluster_name, 'cluster') - self.openstack_cluster_template_delete(cluster_template_name) - self.wait_for_resource_deletion(cluster_template_name, 'cluster ' - 'template') - self.openstack_node_group_template_delete(ng_master) - self.openstack_node_group_template_delete(ng_worker) - self.wait_for_resource_deletion(ng_master, 'node group template') - self.wait_for_resource_deletion(ng_worker, 'node group template') - self.openstack_image_unregister(image_name) - self.negative_unregister_not_existing_image(image_name) - - @testtools.skipIf(TEMPEST_CONF.data_processing.api_version_saharaclient != - '1.1', "Full job binaries testing requires API v1.1") - def test_job_binary_cli(self): - job_binary_name, original_file = self.openstack_job_binary_create() - self.addCleanup(self.delete_resource, 'job binary', job_binary_name) - - self.openstack_job_binary_list() - self.openstack_job_binary_show(job_binary_name) - self.openstack_job_binary_update(job_binary_name, flag='description') - self.openstack_job_binary_download(job_binary_name, original_file) - self.filter_job_binaries_in_list() - self.negative_try_to_update_protected_jb(job_binary_name) - self.openstack_job_binary_update(job_binary_name, flag='unprotected') - self.openstack_job_binary_delete(job_binary_name) - self.negative_delete_removed_job_binary(job_binary_name) - - def test_job_template_cli(self): - job_binary_name, _ = self.openstack_job_binary_create( - job_internal=False) - self.addCleanup(self.delete_resource, 'job binary', job_binary_name) - - job_template_name = self.openstack_job_template_create(job_binary_name) - self.addCleanup(self.delete_resource, 'job template', - job_template_name) - - self.openstack_job_template_list() - self.openstack_job_template_show(job_template_name) - self.openstack_job_template_update(job_template_name) - self.openstack_job_template_delete(job_template_name) - self.openstack_job_binary_delete(job_binary_name) - - def test_data_source_cli(self): - data_source_name = self.openstack_data_source_create() - self.addCleanup(self.delete_resource, 'data source', data_source_name) - - self.openstack_data_source_list() - self.openstack_data_source_show(data_source_name) - self.openstack_data_source_update(data_source_name) - self.openstack_data_source_delete(data_source_name) - - def test_job_type_cli(self): - self.openstack_job_type_list() - self.openstack_job_type_configs_get(flag='file') - self.filter_job_type_in_list() - - def _run_job_on_cluster(self, cluster_name): - job_template_name = self.openstack_job_template_name() - self.addCleanup(self.delete_resource, 'job template', - job_template_name) - - input_file = self.openstack_data_source_create() - output_file = self.openstack_data_source_create() - self.addCleanup(self.delete_resource, 'data source', input_file) - self.addCleanup(self.delete_resource, 'data source', output_file) - job_id = self.openstack_job_execute(cluster_name, job_template_name, - input_file, output_file) - self.addCleanup(self.delete_resource, 'job', job_id) - - self.openstack_job_list() - self.openstack_job_show(job_id) - self.openstack_job_update(job_id) - self.openstack_job_delete(job_id) - self.openstack_data_source_delete(input_file) - self.openstack_data_source_delete(output_file) - self.openstack_job_template_delete(job_template_name) diff --git a/sahara_tempest_plugin/tests/clients/__init__.py b/sahara_tempest_plugin/tests/clients/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tempest_plugin/tests/clients/base.py b/sahara_tempest_plugin/tests/clients/base.py deleted file mode 100644 index 8ca92416..00000000 --- a/sahara_tempest_plugin/tests/clients/base.py +++ /dev/null @@ -1,331 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import time - -from oslo_utils import timeutils -from keystoneauth1.identity import v3 -from keystoneauth1 import session -from saharaclient.api import base as sab -from saharaclient import client as sahara_client -from tempest import config -from tempest.lib import exceptions -import tempest.test - -from sahara_tempest_plugin.common import plugin_utils - -TEMPEST_CONF = config.CONF - -# cluster status -CLUSTER_STATUS_ACTIVE = "Active" -CLUSTER_STATUS_ERROR = "Error" - - -class ClusterErrorException(exceptions.TempestException): - message = "Cluster failed to build and is in ERROR status" - - -class BaseDataProcessingTest(tempest.test.BaseTestCase): - - credentials = ('admin', 'primary') - - @classmethod - def resource_setup(cls): - super(BaseDataProcessingTest, cls).resource_setup() - - endpoint_type = TEMPEST_CONF.data_processing.endpoint_type - catalog_type = TEMPEST_CONF.data_processing.catalog_type - auth_url = TEMPEST_CONF.identity.uri_v3 - - credentials = cls.os_admin.credentials - - auth = v3.Password(auth_url=auth_url, - username=credentials.username, - password=credentials.password, - project_name=credentials.tenant_name, - user_domain_name='default', - project_domain_name='default') - - ses = session.Session(auth=auth) - - cls.client = sahara_client.Client( - TEMPEST_CONF.data_processing.api_version_saharaclient, - session=ses, - service_type=catalog_type, - endpoint_type=endpoint_type) - - if TEMPEST_CONF.data_processing.api_version_saharaclient == '1.1': - sahara_api_version = '1.1' - else: - sahara_api_version = '2.0' - - if TEMPEST_CONF.service_available.glance: - # Check if glance v1 is available to determine which client to use. - if TEMPEST_CONF.image_feature_enabled.api_v1: - cls.image_client = cls.os_admin.image_client - elif TEMPEST_CONF.image_feature_enabled.api_v2: - cls.image_client = cls.os_admin.image_client_v2 - else: - raise lib_exc.InvalidConfiguration( - 'Either api_v1 or api_v2 must be True in ' - '[image-feature-enabled].') - cls.object_client = cls.os_primary.object_client - cls.container_client = cls.os_primary.container_client - cls.networks_client = cls.os_primary.compute_networks_client - - if TEMPEST_CONF.network.floating_network_name: - cls.floating_ip_pool = TEMPEST_CONF.network.floating_network_name - if TEMPEST_CONF.service_available.neutron: - cls.floating_ip_pool = \ - cls.get_floating_ip_pool_id_for_neutron() - else: - cls.floating_ip_pool = TEMPEST_CONF.network.public_network_id - - test_image_name = TEMPEST_CONF.data_processing.test_image_name - - cls.test_image_id = cls.get_image_id(test_image_name) - - default_plugin = cls.get_plugin() - plugin_dict = default_plugin.to_dict() - default_version = plugin_utils.get_default_version(plugin_dict) - - cls.worker_template = ( - plugin_utils.get_node_group_template('worker1', - default_version, - cls.floating_ip_pool, - sahara_api_version)) - - cls.master_template = ( - plugin_utils.get_node_group_template('master1', - default_version, - cls.floating_ip_pool, - sahara_api_version)) - - cls.cluster_template = ( - plugin_utils.get_cluster_template( - default_version=default_version, - api_version=sahara_api_version)) - - cls.swift_data_source_with_creds = { - 'url': 'swift://sahara-container/input-source', - 'description': 'Test data source', - 'type': 'swift', - 'credentials': { - 'user': 'test', - 'password': '123' - } - } - - cls.local_hdfs_data_source = { - 'url': 'input-source', - 'description': 'Test data source', - 'type': 'hdfs', - } - - cls.external_hdfs_data_source = { - 'url': 'hdfs://test-master-node/usr/hadoop/input-source', - 'description': 'Test data source', - 'type': 'hdfs' - } - - @classmethod - def get_floating_ip_pool_id_for_neutron(cls): - net_id = cls._find_network_by_name( - TEMPEST_CONF.network.floating_network_name) - if not net_id: - raise exceptions.NotFound( - 'Floating IP pool \'%s\' not found in pool list.' - % TEMPEST_CONF.network.floating_network_name) - return net_id - - @classmethod - def get_private_network_id(cls): - net_id = cls._find_network_by_name( - TEMPEST_CONF.compute.fixed_network_name) - if not net_id: - raise exceptions.NotFound( - 'Private network \'%s\' not found in network list.' - % TEMPEST_CONF.compute.fixed_network_name) - return net_id - - @classmethod - def _find_network_by_name(cls, network_name): - for network in cls.networks_client.list_networks()['networks']: - if network['label'] == network_name: - return network['id'] - return None - - @classmethod - def get_image_id(cls, image_name): - for image in cls.image_client.list_images()['images']: - if image['name'] == image_name: - return image['id'] - raise exceptions.NotFound('Image \'%s\' not found in the image list.' - % (image_name)) - - @classmethod - def get_plugin(cls): - plugins = cls.client.plugins.list() - plugin_name = plugin_utils.get_default_plugin() - for plugin in plugins: - if plugin.name == plugin_name: - return plugin - raise exceptions.NotFound('No available plugins for testing') - - def create_node_group_template(self, name, **kwargs): - - resp_body = self.client.node_group_templates.create( - name, **kwargs) - - self.addCleanup(self.delete_resource, - self.client.node_group_templates, resp_body.id) - - return resp_body - - def create_cluster_template(self, name, **kwargs): - - resp_body = self.client.cluster_templates.create( - name, **kwargs) - - self.addCleanup(self.delete_resource, - self.client.cluster_templates, resp_body.id) - - return resp_body - - def create_data_source(self, name, url, description, type, - credentials=None): - - user = credentials['user'] if credentials else None - pas = credentials['password'] if credentials else None - - resp_body = self.client.data_sources.create( - name, description, type, url, credential_user=user, - credential_pass=pas) - - self.addCleanup(self.delete_resource, - self.client.data_sources, resp_body.id) - - return resp_body - - def create_job_binary(self, name, url, description, extra=None): - - resp_body = self.client.job_binaries.create( - name, url, description, extra) - - self.addCleanup(self.delete_resource, - self.client.job_binaries, resp_body.id) - - return resp_body - - def create_job_binary_internal(self, name, data): - - resp_body = self.client.job_binary_internals.create(name, data) - - self.addCleanup(self.delete_resource, - self.client.job_binary_internals, resp_body.id) - - return resp_body - - def create_job(self, name, job_type, mains, libs=None, description=None): - if TEMPEST_CONF.data_processing.api_version_saharaclient == '1.1': - base_client_class = self.client.jobs - else: - base_client_class = self.client.job_templates - libs = libs or () - description = description or '' - - resp_body = base_client_class.create( - name, job_type, mains, libs, description) - - self.addCleanup(self.delete_resource, base_client_class, resp_body.id) - - return resp_body - - def create_cluster(self, name, **kwargs): - - resp_body = self.client.clusters.create(name, **kwargs) - - self.addCleanup(self.delete_resource, self.client.clusters, - resp_body.id) - - return resp_body - - def check_cluster_active(self, cluster_id): - timeout = TEMPEST_CONF.data_processing.cluster_timeout - s_time = timeutils.utcnow() - while timeutils.delta_seconds(s_time, timeutils.utcnow()) < timeout: - cluster = self.client.clusters.get(cluster_id) - if cluster.status == CLUSTER_STATUS_ACTIVE: - return - if cluster.status == CLUSTER_STATUS_ERROR: - raise ClusterErrorException( - 'Cluster failed to build and is in %s status.' % - CLUSTER_STATUS_ERROR) - time.sleep(TEMPEST_CONF.data_processing.request_timeout) - raise exceptions.TimeoutException( - 'Cluster failed to get to %s status within %d seconds.' - % (CLUSTER_STATUS_ACTIVE, timeout)) - - def create_job_execution(self, **kwargs): - if TEMPEST_CONF.data_processing.api_version_saharaclient == '1.1': - base_client_class = self.client.job_executions - else: - base_client_class = self.client.jobs - - resp_body = base_client_class.create(**kwargs) - - self.addCleanup(self.delete_resource, base_client_class, resp_body.id) - - return resp_body - - def create_container(self, name): - - self.container_client.create_container(name) - - self.addCleanup(self.delete_swift_container, name) - - def delete_resource(self, resource_client, resource_id): - try: - resource_client.delete(resource_id) - except sab.APIException: - pass - else: - self.delete_timeout(resource_client, resource_id) - - def delete_timeout( - self, resource_client, resource_id, - timeout=TEMPEST_CONF.data_processing.cluster_timeout): - - start = timeutils.utcnow() - while timeutils.delta_seconds(start, timeutils.utcnow()) < timeout: - try: - resource_client.get(resource_id) - except sab.APIException as sahara_api_exception: - if 'not found' in str(sahara_api_exception): - return - raise sahara_api_exception - - time.sleep(TEMPEST_CONF.data_processing.request_timeout) - - raise exceptions.TimeoutException( - 'Failed to delete resource "%s" in %d seconds.' - % (resource_id, timeout)) - - def delete_swift_container(self, container): - objects = ([obj['name'] for obj in - self.container_client.list_all_container_objects( - container)]) - for obj in objects: - self.object_client.delete_object(container, obj) - self.container_client.delete_container(container) diff --git a/sahara_tempest_plugin/tests/clients/test_cluster_templates.py b/sahara_tempest_plugin/tests/clients/test_cluster_templates.py deleted file mode 100644 index 7fcb76ac..00000000 --- a/sahara_tempest_plugin/tests/clients/test_cluster_templates.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from tempest import config -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.clients import base - - -class ClusterTemplateTest(base.BaseDataProcessingTest): - def _check_create_cluster_template(self): - ng_template_name = data_utils.rand_name('sahara-ng-template') - ng_template = self.create_node_group_template(ng_template_name, - **self.worker_template) - - full_cluster_template = self.cluster_template.copy() - - # The 'node_groups' field in the response body - # has some extra info that post body does not have. - del self.cluster_template['node_groups'] - - template_name = data_utils.rand_name('sahara-cluster-template') - - # create cluster template - resp_body = self.create_cluster_template(template_name, - **full_cluster_template) - - # check that template created successfully - self.assertEqual(template_name, resp_body.name) - self.assertDictContainsSubset(self.cluster_template, - resp_body.__dict__) - - return resp_body.id, template_name - - def _check_cluster_template_list(self, template_id, template_name): - # check for cluster template in list - template_list = self.client.cluster_templates.list() - templates_info = [(template.id, template.name) - for template in template_list] - self.assertIn((template_id, template_name), templates_info) - - def _check_cluster_template_get(self, template_id, template_name): - # check cluster template fetch by id - template = self.client.cluster_templates.get( - template_id) - self.assertEqual(template_name, template.name) - self.assertDictContainsSubset(self.cluster_template, template.__dict__) - - def _check_cluster_template_update(self, template_id): - values = { - 'name': data_utils.rand_name('updated-sahara-ct'), - 'description': 'description', - } - - # check updating of cluster template - template = self.client.cluster_templates.update( - template_id, **values) - self.assertDictContainsSubset(values, template.__dict__) - - def _check_cluster_template_delete(self, template_id): - # delete cluster template by id - self.client.cluster_templates.delete( - template_id) - - # check that cluster template really deleted - templates = self.client.cluster_templates.list() - self.assertNotIn(template_id, [template.id for template in templates]) - - def test_cluster_templates(self): - template_id, template_name = self._check_create_cluster_template() - self._check_cluster_template_list(template_id, template_name) - self._check_cluster_template_get(template_id, template_name) - self._check_cluster_template_update(template_id) - self._check_cluster_template_delete(template_id) diff --git a/sahara_tempest_plugin/tests/clients/test_data_sources.py b/sahara_tempest_plugin/tests/clients/test_data_sources.py deleted file mode 100644 index b01cdbe2..00000000 --- a/sahara_tempest_plugin/tests/clients/test_data_sources.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.clients import base - - -class DataSourceTest(base.BaseDataProcessingTest): - def _check_data_source_create(self, source_body): - source_name = data_utils.rand_name('sahara-data-source') - # create data source - resp_body = self.create_data_source(source_name, **source_body) - # check that source created successfully - self.assertEqual(source_name, resp_body.name) - if source_body['type'] == 'swift': - source_body = self.swift_data_source - self.assertDictContainsSubset(source_body, resp_body.__dict__) - - return resp_body.id, source_name - - def _check_data_source_list(self, source_id, source_name): - # check for data source in list - source_list = self.client.data_sources.list() - sources_info = [(source.id, source.name) for source in source_list] - self.assertIn((source_id, source_name), sources_info) - - def _check_data_source_get(self, source_id, source_name, source_body): - # check data source fetch by id - source = self.client.data_sources.get(source_id) - self.assertEqual(source_name, source.name) - self.assertDictContainsSubset(source_body, source.__dict__) - - def _check_data_source_update(self, source_id): - values = { - 'name': data_utils.rand_name('updated-sahara-data-source'), - 'description': 'description', - 'type': 'hdfs', - 'url': 'hdfs://user/foo' - } - - source = self.client.data_sources.update(source_id, values) - - self.assertDictContainsSubset(values, source.data_source) - - def _check_data_source_delete(self, source_id): - # delete data source - self.client.data_sources.delete(source_id) - # check that data source really deleted - source_list = self.client.data_sources.list() - self.assertNotIn(source_id, [source.id for source in source_list]) - - def test_swift_data_source(self): - # Create extra self.swift_data_source variable to use for comparison to - # data source response body because response body has no 'credentials' - # field. - self.swift_data_source = self.swift_data_source_with_creds.copy() - del self.swift_data_source['credentials'] - source_id, source_name = self._check_data_source_create( - self.swift_data_source_with_creds) - self._check_data_source_list(source_id, source_name) - self._check_data_source_get(source_id, source_name, - self.swift_data_source) - self._check_data_source_delete(source_id) - - def test_local_hdfs_data_source(self): - source_id, source_name = self._check_data_source_create( - self.local_hdfs_data_source) - self._check_data_source_list(source_id, source_name) - self._check_data_source_get(source_id, source_name, - self.local_hdfs_data_source) - self._check_data_source_delete(source_id) - - def test_external_hdfs_data_source(self): - source_id, source_name = self._check_data_source_create( - self.external_hdfs_data_source) - self._check_data_source_list(source_id, source_name) - self._check_data_source_get(source_id, source_name, - self.external_hdfs_data_source) - self._check_data_source_update(source_id) - self._check_data_source_delete(source_id) diff --git a/sahara_tempest_plugin/tests/clients/test_job_binaries.py b/sahara_tempest_plugin/tests/clients/test_job_binaries.py deleted file mode 100644 index 20a8bbc8..00000000 --- a/sahara_tempest_plugin/tests/clients/test_job_binaries.py +++ /dev/null @@ -1,141 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import testtools - -from tempest import config -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.clients import base - - -CONF = config.CONF - - -class JobBinariesTest(base.BaseDataProcessingTest): - def _check_job_binary_create(self, binary_body): - binary_name = data_utils.rand_name('sahara-job-binary') - - # create job binary - resp_body = self.create_job_binary(binary_name, **binary_body) - - # ensure that binary created successfully - self.assertEqual(binary_name, resp_body.name) - if 'swift' in binary_body['url']: - binary_body = self.swift_job_binary - else: - binary_body = self.internal_db_binary - self.assertDictContainsSubset(binary_body, resp_body.__dict__) - - return resp_body.id, binary_name - - def _check_job_binary_list(self, binary_id, binary_name): - # check for job binary in list - binary_list = self.client.job_binaries.list() - binaries_info = [(binary.id, binary.name) for binary in binary_list] - self.assertIn((binary_id, binary_name), binaries_info) - - def _check_job_binary_delete(self, binary_id): - # delete job binary by id - self.client.job_binaries.delete(binary_id) - # check that job binary really deleted - binary_list = self.client.job_binaries.list() - self.assertNotIn(binary_id, [binary.id for binary in binary_list]) - - def _check_swift_job_binary_create(self): - self.swift_job_binary_with_extra = { - 'url': 'swift://sahara-container/example.jar', - 'description': 'Test job binary', - 'extra': { - 'user': 'test', - 'password': '123' - } - } - # Create extra self.swift_job_binary variable to use for comparison to - # job binary response body because response body has no 'extra' field. - self.swift_job_binary = self.swift_job_binary_with_extra.copy() - del self.swift_job_binary['extra'] - return self._check_job_binary_create(self.swift_job_binary_with_extra) - - def _check_swift_job_binary_get(self, binary_id, binary_name): - # check job binary fetch by id - binary = self.client.job_binaries.get(binary_id) - self.assertEqual(binary_name, binary.name) - self.assertDictContainsSubset(self.swift_job_binary, binary.__dict__) - - def _check_swift_job_binary_update(self, binary_id): - values = { - 'url': 'swift://user/foo', - 'description': 'description' - } - # check updating of job binary in swift - binary = self.client.job_binaries.update(binary_id, values) - self.assertDictContainsSubset(values, binary.__dict__) - - def _check_internal_db_job_binary_create(self): - name = data_utils.rand_name('sahara-internal-job-binary') - self.job_binary_data = b'Some data' - job_binary_internal = ( - self.create_job_binary_internal(name, self.job_binary_data)) - self.internal_db_binary_with_extra = { - 'url': 'internal-db://%s' % job_binary_internal.id, - 'description': 'Test job binary', - 'extra': { - 'user': 'test', - 'password': '123' - } - } - # Create extra self.internal_db_binary variable to use for comparison - # to job binary response body because response body has no 'extra' - # field. - self.internal_db_binary = self.internal_db_binary_with_extra.copy() - del self.internal_db_binary['extra'] - return self._check_job_binary_create( - self.internal_db_binary_with_extra) - - def _check_internal_db_job_binary_get(self, binary_id, binary_name): - # check job binary fetch by id - binary = self.client.job_binaries.get(binary_id) - self.assertEqual(binary_name, binary.name) - self.assertDictContainsSubset(self.internal_db_binary, binary.__dict__) - - def _check_internal_db_job_binary_update(self, binary_id): - values = { - 'description': 'description' - } - # check updating of job binary in internal db - binary = self.client.job_binaries.update(binary_id, values) - self.assertDictContainsSubset(values, binary.__dict__) - - def _check_job_binary_get_file(self, binary_id): - data = self.client.job_binaries.get_file(binary_id) - self.assertEqual(self.job_binary_data, data) - - def test_swift_job_binaries(self): - binary_id, binary_name = self._check_swift_job_binary_create() - self._check_job_binary_list(binary_id, binary_name) - self._check_swift_job_binary_get(binary_id, binary_name) - self._check_swift_job_binary_update(binary_id) - self._check_job_binary_delete(binary_id) - - @testtools.skipIf(CONF.data_processing.api_version_saharaclient != '1.1', - 'Job binaries stored on the internal db are available ' - 'only with API v1.1') - def test_internal_job_binaries(self): - binary_id, binary_name = self._check_internal_db_job_binary_create() - self._check_job_binary_list(binary_id, binary_name) - self._check_internal_db_job_binary_get(binary_id, binary_name) - self._check_job_binary_get_file(binary_id) - self._check_internal_db_job_binary_update(binary_id) - self._check_job_binary_delete(binary_id) diff --git a/sahara_tempest_plugin/tests/clients/test_job_binary_internals.py b/sahara_tempest_plugin/tests/clients/test_job_binary_internals.py deleted file mode 100644 index 653408fd..00000000 --- a/sahara_tempest_plugin/tests/clients/test_job_binary_internals.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from tempest import config -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.clients import base - - -CONF = config.CONF - - -class JobBinaryInternalsTest(base.BaseDataProcessingTest): - @classmethod - def skip_checks(cls): - super(JobBinaryInternalsTest, cls).skip_checks() - if CONF.data_processing.api_version_saharaclient != '1.1': - raise cls.skipException('Job binaries stored on the internal db ' - 'are available only with API v1.1') - - def _check_job_binary_internal_create(self): - name = data_utils.rand_name('sahara-internal-job-binary') - self.job_binary_data = b'Some data' - # create job binary internal - resp_body = self.create_job_binary_internal(name, self.job_binary_data) - # check that job_binary_internal created successfully - self.assertEqual(name, resp_body.name) - return resp_body.id, resp_body.name - - def _check_job_binary_internal_list(self, binary_id, binary_name): - # check for job binary internal in list - binary_list = self.client.job_binary_internals.list() - binaries_info = [(binary.id, binary.name) for binary in binary_list] - self.assertIn((binary_id, binary_name), binaries_info) - - def _check_job_binary_internal_get(self, binary_id, binary_name): - # check job binary internal fetch by id - binary = self.client.job_binary_internals.get(binary_id) - self.assertEqual(binary_name, binary.name) - - def _check_job_binary_internal_update(self, binary_id): - values = { - 'name': data_utils.rand_name('sahara-internal-job-binary'), - 'is_public': True - } - binary = self.client.job_binary_internals.update(binary_id, **values) - self.assertDictContainsSubset(values, binary.job_binary_internal) - - def _check_job_binary_internal_delete(self, binary_id): - # delete job binary internal by id - self.client.job_binary_internals.delete(binary_id) - # check that job binary internal really deleted - binary_list = self.client.job_binary_internals.list() - self.assertNotIn(binary_id, [binary.id for binary in binary_list]) - - def test_job_binary_internal(self): - binary_id, binary_name = self._check_job_binary_internal_create() - self._check_job_binary_internal_list(binary_id, binary_name) - self._check_job_binary_internal_get(binary_id, binary_name) - self._check_job_binary_internal_update(binary_id) - self._check_job_binary_internal_delete(binary_id) diff --git a/sahara_tempest_plugin/tests/clients/test_job_executions.py b/sahara_tempest_plugin/tests/clients/test_job_executions.py deleted file mode 100644 index 20fb8804..00000000 --- a/sahara_tempest_plugin/tests/clients/test_job_executions.py +++ /dev/null @@ -1,320 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import time -from testtools import testcase as tc - -from oslo_utils import timeutils -from saharaclient.api import base as sab -from tempest import config -from tempest.lib.common.utils import data_utils -from tempest.lib import decorators -from tempest.lib import exceptions - -from sahara_tempest_plugin.tests.clients import base - - -TEMPEST_CONF = config.CONF - - -class JobExecutionTest(base.BaseDataProcessingTest): - def _check_register_image(self, image_id): - self.client.images.update_image( - image_id, TEMPEST_CONF.data_processing.test_ssh_user, '') - reg_image = self.client.images.get(image_id) - - self.assertDictContainsSubset( - {'_sahara_username': TEMPEST_CONF.data_processing.test_ssh_user}, - reg_image.metadata) - - def _check_image_get(self, image_id): - image = self.client.images.get(image_id) - - self.assertEqual(image_id, image.id) - - def _check_image_list(self, image_id): - # check for image in list - image_list = self.client.images.list() - images_info = [image.id for image in image_list] - - self.assertIn(image_id, images_info) - - def _check_adding_tags(self, image_id): - # adding new tags - self.client.images.update_tags(image_id, ['fake', '0.1']) - image = self.client.images.get(image_id) - - self.assertDictContainsSubset({'_sahara_tag_fake': 'True', - '_sahara_tag_0.1': 'True'}, - image.metadata) - - def _check_deleting_tags(self, image_id): - # deleting tags - self.client.images.update_tags(image_id, []) - image = self.client.images.get(image_id) - - self.assertNotIn('_sahara_tag_fake', image.metadata) - self.assertNotIn('_sahara_tag_0.1', image.metadata) - - def _check_unregister_image(self, image_id): - # unregister image - self.client.images.unregister_image(image_id) - - # check that image really unregistered - image_list = self.client.images.list() - self.assertNotIn(image_id, [image.id for image in image_list]) - - def _check_cluster_create(self): - worker = self.create_node_group_template( - data_utils.rand_name('sahara-ng-template'), **self.worker_template) - - master = self.create_node_group_template( - data_utils.rand_name('sahara-ng-template'), **self.master_template) - - cluster_templ = self.cluster_template.copy() - cluster_templ['node_groups'] = [ - { - 'name': 'master', - 'node_group_template_id': master.id, - 'count': 1 - }, - { - 'name': 'worker', - 'node_group_template_id': worker.id, - 'count': 3 - } - ] - if TEMPEST_CONF.service_available.neutron: - cluster_templ['net_id'] = self.get_private_network_id() - - cluster_template = self.create_cluster_template( - data_utils.rand_name('sahara-cluster-template'), **cluster_templ) - cluster_name = data_utils.rand_name('sahara-cluster') - self.cluster_info = { - 'name': cluster_name, - 'plugin_name': 'fake', - 'cluster_template_id': cluster_template.id, - 'default_image_id': self.test_image_id - } - plugin_version_option = 'plugin_version' - if CONF.data_processing.api_version_saharaclient == '1.1': - plugin_version_option = 'hadoop_version' - self.cluster_info[plugin_version_option] = '0.1' - - # create cluster - cluster = self.create_cluster(**self.cluster_info) - - # wait until cluster moves to active state - self.check_cluster_active(cluster.id) - - # check that cluster created successfully - self.assertEqual(cluster_name, cluster.name) - self.assertDictContainsSubset(self.cluster_info, cluster.__dict__) - - return cluster.id, cluster.name - - def _check_cluster_list(self, cluster_id, cluster_name): - # check for cluster in list - cluster_list = self.client.clusters.list() - clusters_info = [(clust.id, clust.name) for clust in cluster_list] - self.assertIn((cluster_id, cluster_name), clusters_info) - - def _check_cluster_get(self, cluster_id, cluster_name): - # check cluster fetch by id - cluster = self.client.clusters.get(cluster_id) - self.assertEqual(cluster_name, cluster.name) - self.assertDictContainsSubset(self.cluster_info, cluster.__dict__) - - def _check_cluster_update(self, cluster_id): - values = { - 'name': data_utils.rand_name('updated-sahara-cluster'), - 'description': 'description' - } - # check updating of cluster - cluster = self.client.clusters.update(cluster_id) - self.assertDictContainsSubset(values, cluster.__dict__) - - def _check_cluster_scale(self, cluster_id): - big_worker = self.create_node_group_template( - data_utils.rand_name('sahara-ng-template'), **self.worker_template) - - scale_body = { - 'resize_node_groups': [ - { - 'count': 2, - 'name': 'worker' - }, - { - "count": 2, - "name": 'master' - } - ], - 'add_node_groups': [ - { - 'count': 1, - 'name': 'big-worker', - 'node_group_template_id': big_worker.id - - } - ] - } - - self.client.clusters.scale(cluster_id, scale_body) - self.check_cluster_active(cluster_id) - - cluster = self.client.clusters.get(cluster_id) - for ng in cluster.node_groups: - if ng['name'] == scale_body['resize_node_groups'][0]['name']: - self.assertDictContainsSubset( - scale_body['resize_node_groups'][0], ng) - elif ng['name'] == scale_body['resize_node_groups'][1]['name']: - self.assertDictContainsSubset( - scale_body['resize_node_groups'][1], ng) - elif ng['name'] == scale_body['add_node_groups'][0]['name']: - self.assertDictContainsSubset( - scale_body['add_node_groups'][0], ng) - - def _check_cluster_delete(self, cluster_id): - self.client.clusters.delete(cluster_id) - - # check that cluster moved to deleting state - cluster = self.client.clusters.get(cluster_id) - self.assertEqual('Deleting', cluster.status) - - timeout = TEMPEST_CONF.data_processing.cluster_timeout - s_time = timeutils.utcnow() - while timeutils.delta_seconds(s_time, timeutils.utcnow()) < timeout: - try: - self.client.clusters.get(cluster_id) - except sab.APIException: - # cluster is deleted - return - time.sleep(TEMPEST_CONF.data_processing.request_timeout) - - raise exceptions.TimeoutException('Cluster failed to terminate' - 'in %d seconds.' % timeout) - - def _check_job_execution_create(self, cluster_id): - # create swift container - container_name = data_utils.rand_name('test-container') - self.create_container(container_name) - - # create input data source - input_file_name = data_utils.rand_name('input') - self.object_client.create_object(container_name, input_file_name, - 'some-data') - - input_file_url = 'swift://%s/%s' % (container_name, input_file_name) - input_source_name = data_utils.rand_name('input-data-source') - input_source = self.create_data_source( - input_source_name, input_file_url, '', 'swift', - {'user': 'test', 'password': '123'}) - - # create output data source - output_dir_name = data_utils.rand_name('output') - output_dir_url = 'swift://%s/%s' % (container_name, output_dir_name) - output_source_name = data_utils.rand_name('output-data-source') - output_source = self.create_data_source( - output_source_name, output_dir_url, '', 'swift', - {'user': 'test', 'password': '123'}) - - job_binary = { - 'name': data_utils.rand_name('sahara-job-binary'), - 'url': input_file_url, - 'description': 'Test job binary', - 'extra': { - 'user': 'test', - 'password': '123' - } - } - # create job_binary - job_binary = self.create_job_binary(**job_binary) - - # create job - job_name = data_utils.rand_name('test-job') - job = self.create_job(job_name, 'Pig', [job_binary.id]) - - self.job_exec_info = { - 'job_id': job.id, - 'cluster_id': cluster_id, - 'input_id': input_source.id, - 'output_id': output_source.id, - 'configs': {} - } - # create job execution - job_execution = self.create_job_execution(**self.job_exec_info) - - return job_execution.id - - def _check_job_execution_list(self, job_exec_id): - # check for job_execution in list - job_exec_list = self.client.job_executions.list() - self.assertIn(job_exec_id, [job_exec.id for job_exec in job_exec_list]) - - def _check_job_execution_get(self, job_exec_id): - # check job_execution fetch by id - job_exec = self.client.job_executions.get(job_exec_id) - # Create extra cls.swift_job_binary variable to use for comparison to - # job binary response body because response body has no 'extra' field. - job_exec_info = self.job_exec_info.copy() - del job_exec_info['configs'] - self.assertDictContainsSubset(job_exec_info, job_exec.__dict__) - - def _check_job_execution_update(self, job_exec_id): - values = { - 'is_public': True - } - job_exec = self.client.job_executions.update(job_exec_id, **values) - self.assertDictContainsSubset(values, job_exec.__dict__) - - def _check_job_execution_delete(self, job_exec_id): - # delete job_execution by id - self.client.job_executions.delete(job_exec_id) - # check that job_execution really deleted - job_exec_list = self.client.jobs.list() - self.assertNotIn(job_exec_id, [job_exec.id for - job_exec in job_exec_list]) - - @decorators.skip_because(bug="1430252") - @tc.attr('slow') - def test_job_executions(self): - image_id = self.test_image_id - self._check_register_image(image_id) - self._check_image_get(image_id) - self._check_image_list(image_id) - self._check_adding_tags(image_id) - - cluster_id, cluster_name = self._check_cluster_create() - self._check_cluster_list(cluster_id, cluster_name) - self._check_cluster_get(cluster_id, cluster_name) - self._check_cluster_update(cluster_id) - self._check_cluster_scale(cluster_id) - - job_exec_id = self._check_job_execution_create(cluster_id) - self._check_job_execution_list(job_exec_id) - self._check_job_execution_get(job_exec_id) - self._check_job_execution_update(job_exec_id) - - self._check_job_execution_delete(job_exec_id) - self._check_cluster_delete(cluster_id) - self._check_deleting_tags(image_id) - self._check_unregister_image(image_id) - - @classmethod - def tearDownClass(cls): - image_list = cls.client.images.list() - image_id = cls.test_image_id - if image_id in [image.id for image in image_list]: - cls.client.images.unregister_image(image_id) - super(JobExecutionTest, cls).tearDownClass() diff --git a/sahara_tempest_plugin/tests/clients/test_jobs.py b/sahara_tempest_plugin/tests/clients/test_jobs.py deleted file mode 100644 index 4cc60192..00000000 --- a/sahara_tempest_plugin/tests/clients/test_jobs.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from tempest import config -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.clients import base - - -CONF = config.CONF - - -class JobTest(base.BaseDataProcessingTest): - - def _get_job_template_client(self): - if CONF.data_processing.api_version_saharaclient == '1.1': - job_template_client_class = self.client.jobs - else: - job_template_client_class = self.client.job_templates - return job_template_client_class - - def _get_job_template_items(self, job_template_object): - if CONF.data_processing.api_version_saharaclient == '1.1': - return job_template_object.job - else: - return job_template_object.job_template - - def _check_create_job(self): - job_binary = { - 'name': data_utils.rand_name('sahara-job-binary'), - 'url': 'swift://sahara-container.sahara/example.jar', - 'description': 'Test job binary', - 'extra': { - 'user': 'test', - 'password': '123' - } - } - # create job_binary - job_binary = self.create_job_binary(**job_binary) - - self.job = { - 'job_type': 'Pig', - 'mains': [job_binary.id] - } - job_name = data_utils.rand_name('sahara-job') - # create job - job = self.create_job(job_name, **self.job) - # check that job created successfully - self.assertEqual(job_name, job.name) - - return job.id, job.name - - def _check_job_list(self, job_id, job_name): - # check for job in list - job_list = self._get_job_template_client().list() - jobs_info = [(job.id, job.name) for job in job_list] - self.assertIn((job_id, job_name), jobs_info) - - def _check_get_job(self, job_id, job_name): - # check job fetch by id - job = self._get_job_template_client().get(job_id) - self.assertEqual(job_name, job.name) - - def _check_job_update(self, job_id): - # check updating of job - values = { - 'name': data_utils.rand_name('updated-sahara-job'), - 'description': 'description' - - } - job = self._get_job_template_client().update(job_id, **values) - self.assertDictContainsSubset(values, - self._get_job_template_items(job)) - - def _check_delete_job(self, job_id): - # delete job by id - self._get_job_template_client().delete(job_id) - # check that job really deleted - job_list = self._get_job_template_client().list() - self.assertNotIn(job_id, [job.id for job in job_list]) - - def test_job(self): - job_id, job_name = self._check_create_job() - self._check_job_list(job_id, job_name) - self._check_get_job(job_id, job_name) - self._check_job_update(job_id) - self._check_delete_job(job_id) diff --git a/sahara_tempest_plugin/tests/clients/test_node_group_templates.py b/sahara_tempest_plugin/tests/clients/test_node_group_templates.py deleted file mode 100644 index 14bcf7a3..00000000 --- a/sahara_tempest_plugin/tests/clients/test_node_group_templates.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from saharaclient.api import base as sab -from tempest.lib.common.utils import data_utils - -from sahara_tempest_plugin.tests.clients import base - - -class NodeGroupTemplateTest(base.BaseDataProcessingTest): - def _check_create_node_group_template(self): - template_name = data_utils.rand_name('sahara-ng-template') - - # create node group template - resp_body = self.create_node_group_template(template_name, - **self.worker_template) - # check that template created successfully - self.assertEqual(template_name, resp_body.name) - self.assertDictContainsSubset(self.worker_template, - resp_body.__dict__) - - return resp_body.id, template_name - - def _check_node_group_template_list(self, template_id, template_name): - # check for node group template in list - template_list = self.client.node_group_templates.list() - templates_info = [(template.id, template.name) - for template in template_list] - self.assertIn((template_id, template_name), templates_info) - - def _check_node_group_template_get(self, template_id, template_name): - # check node group template fetch by id - template = self.client.node_group_templates.get( - template_id) - self.assertEqual(template_name, template.name) - self.assertDictContainsSubset(self.worker_template, - template.__dict__) - - def _check_node_group_template_update(self, template_id): - values = { - 'name': data_utils.rand_name('updated-sahara-ng-template'), - 'description': 'description', - 'volumes_per_node': 2, - 'volumes_size': 2, - } - try: - resp_body = self.client.node_group_templates.update(template_id, - **values) - except sab.APIException: - del values['volumes_per_node'] - del values['volumes_size'] - resp_body = self.client.node_group_templates.update(template_id, - **values) - # check that template updated successfully - self.assertDictContainsSubset(values, - resp_body.__dict__) - - def _check_node_group_template_delete(self, template_id): - # delete node group template by id - self.client.node_group_templates.delete(template_id) - - # check that node group really deleted - templates = self.client.node_group_templates.list() - self.assertNotIn(template_id, [template.id for template in templates]) - - def test_node_group_templates(self): - template_id, template_name = self._check_create_node_group_template() - self._check_node_group_template_list(template_id, template_name) - self._check_node_group_template_get(template_id, template_name) - self._check_node_group_template_update(template_id) - self._check_node_group_template_delete(template_id) diff --git a/sahara_tempest_plugin/tests/clients/test_plugins.py b/sahara_tempest_plugin/tests/clients/test_plugins.py deleted file mode 100644 index ff759066..00000000 --- a/sahara_tempest_plugin/tests/clients/test_plugins.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) 2014 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from sahara_tempest_plugin.tests.clients import base - - -class PluginsTest(base.BaseDataProcessingTest): - - def _check_plugins_list(self): - plugins = self.client.plugins.list() - plugins_names = [plugin.name for plugin in plugins] - plugin = self.get_plugin() - self.assertIn(plugin.name, plugins_names) - - return plugins_names - - def _check_plugins_get(self, plugins_names): - for plugin_name in plugins_names: - plugin = self.client.plugins.get(plugin_name) - self.assertEqual(plugin_name, plugin.name) - - # check get_version_details - for plugin_version in plugin.versions: - detailed_plugin = self.client.plugins.get_version_details( - plugin_name, plugin_version) - self.assertEqual(plugin_name, detailed_plugin.name) - - # check that required image tags contains name and version - image_tags = detailed_plugin.required_image_tags - self.assertIn(plugin_name, image_tags) - self.assertIn(plugin_version, image_tags) - - def test_plugins(self): - plugins_names = self._check_plugins_list() - self._check_plugins_get(plugins_names) diff --git a/sahara_tests/__init__.py b/sahara_tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tests/scenario/README.rst b/sahara_tests/scenario/README.rst deleted file mode 100644 index 7d49286d..00000000 --- a/sahara_tests/scenario/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -System(scenario) tests for Sahara project -========================================= diff --git a/sahara_tests/scenario/__init__.py b/sahara_tests/scenario/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tests/scenario/base.py b/sahara_tests/scenario/base.py deleted file mode 100644 index a25f2155..00000000 --- a/sahara_tests/scenario/base.py +++ /dev/null @@ -1,909 +0,0 @@ -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import print_function -import functools -import logging -import os -import sys -import time -import traceback - -import fixtures -from oslo_utils import timeutils -import prettytable -import six -from tempest.lib import base -from tempest.lib.common import ssh as connection -from tempest.lib import exceptions as exc - -from sahara_tests.scenario import clients -from sahara_tests.scenario import timeouts -from sahara_tests.scenario import utils -from sahara_tests.utils import crypto as ssh -from sahara_tests.utils import url as utils_url - -logger = logging.getLogger('swiftclient') -logger.setLevel(logging.CRITICAL) - -CHECK_OK_STATUS = "OK" -CHECK_FAILED_STATUS = "FAILED" -CLUSTER_STATUS_ACTIVE = "Active" -CLUSTER_STATUS_ERROR = "Error" -HEALTH_CHECKS = ["RED", "YELLOW", "GREEN"] - - -def track_result(check_name, exit_with_error=True): - def decorator(fct): - @functools.wraps(fct) - def wrapper(self, *args, **kwargs): - started_at = timeutils.utcnow() - test_info = { - 'check_name': check_name, - 'status': CHECK_OK_STATUS, - 'start_time': started_at, - 'duration': None, - 'traceback': None, - 'exception_time': None - } - self._results.append(test_info) - try: - return fct(self, *args, **kwargs) - except Exception: - test_info['exception_time'] = timeutils.utcnow().strftime( - '%Y%m%d_%H%M%S') - test_info['status'] = CHECK_FAILED_STATUS - test_info['traceback'] = traceback.format_exception( - *sys.exc_info()) - if exit_with_error: - raise - finally: - test_time = timeutils.utcnow() - started_at - test_info['duration'] = test_time.seconds - return wrapper - return decorator - - -class BaseTestCase(base.BaseTestCase): - - @classmethod - def setUpClass(cls): - super(BaseTestCase, cls).setUpClass() - cls.network = None - cls.credentials = None - cls.testcase = None - cls._results = [] - cls.report = False - cls.results_dir = '.' - cls.default_templ_dir = '.' - cls.use_api_v2 = False - - def setUp(self): - super(BaseTestCase, self).setUp() - self._init_clients() - timeouts.Defaults.init_defaults(self.testcase) - self.testcase['ssh_username'] = self.sahara.register_image( - self.glance.get_image_id(self.testcase['image']), - self.testcase).username - self.key = self.testcase.get('key_name') - if self.key is None: - self.private_key, self.public_key = ssh.generate_key_pair() - self.key_name = self.__create_keypair() - # save the private key if retain_resources is specified - # (useful for debugging purposes) - if self.testcase['retain_resources'] or self.key is None: - private_key_file_name = os.path.join(self.results_dir, - self.key_name + '.key') - with open(private_key_file_name, 'w+') as private_key_file: - private_key_file.write(self.private_key) - os.chmod(private_key_file_name, 0o600) - self.plugin_version_option = 'plugin_version' - if not self.use_api_v2: - self.plugin_version_option = 'hadoop_version' - self.plugin_opts = { - 'plugin_name': self.testcase['plugin_name'], - self.plugin_version_option: self.testcase['plugin_version'] - } - self.cinder = True - self.proxy = False - - def _init_clients(self): - username = self.credentials['os_username'] - password = self.credentials['os_password'] - tenant_name = self.credentials['os_tenant'] - auth_url = self.credentials['os_auth_url'] - sahara_service_type = self.credentials['sahara_service_type'] - sahara_url = self.credentials['sahara_url'] - auth_version = '3.0' if 'v3' in auth_url else '2.0' - session = clients.get_session(auth_url, username, password, - tenant_name, - self.credentials.get('ssl_verify', - False), - self._get_file_with_defaults( - self.credentials.get('ssl_cert'))) - - api_version = '2' if self.use_api_v2 else '1.1' - self.sahara = clients.SaharaClient(session=session, - service_type=sahara_service_type, - sahara_url=sahara_url, - api_version=api_version) - self.nova = clients.NovaClient(session=session) - self.neutron = clients.NeutronClient(session=session) - # swiftclient doesn't support keystone sessions - self.swift = clients.SwiftClient( - auth_version=auth_version, - authurl=auth_url, - user=username, - key=password, - insecure=not self.credentials.get('ssl_verify', False), - cacert=self.credentials.get('ssl_cert'), - tenant_name=tenant_name) - self.glance = clients.GlanceClient(session=session) - # boto is not an OpenStack client, but we can handle it as well - self.boto = None - if self.credentials.get("s3_endpoint", None): - self.boto = clients.BotoClient( - endpoint=self.credentials["s3_endpoint"], - accesskey=self.credentials["s3_accesskey"], - secretkey=self.credentials["s3_secretkey"]) - - def create_cluster(self): - self.cluster_id = self.sahara.get_cluster_id( - self.testcase.get('existing_cluster')) - self.ng_id_map = {} - if self.cluster_id is None: - self.ng_id_map = self._create_node_group_templates() - cl_tmpl_id = self._create_cluster_template() - self.cluster_id = self._create_cluster(cl_tmpl_id) - elif self.key is None: - self.cinder = False - self._poll_cluster_status_tracked(self.cluster_id) - cluster = self.sahara.get_cluster(self.cluster_id, show_progress=True) - self._get_proxy(cluster) - self.check_cinder() - if self.check_feature_available("provision_progress"): - self._check_event_logs(cluster) - - def _get_proxy(self, cluster): - for ng in cluster.node_groups: - if ng['is_proxy_gateway']: - for instance in ng['instances']: - if instance['management_ip'] != ( - instance['internal_ip']): - self.proxy = instance['management_ip'] - - @track_result("Check transient") - def check_transient(self): - with fixtures.Timeout( - timeouts.Defaults.instance.timeout_check_transient, - gentle=True): - while True: - if self.sahara.is_resource_deleted( - self.sahara.get_cluster_status, self.cluster_id): - break - time.sleep(5) - - def _inject_datasources_data(self, arg, input_url, output_url): - return arg.format( - input_datasource=input_url, output_datasource=output_url) - - def _put_io_data_to_configs(self, configs, input_id, output_id): - input_url, output_url = None, None - if input_id is not None: - input_url = self.sahara.get_datasource( - data_source_id=input_id).url - if output_id is not None: - output_url = self.sahara.get_datasource( - data_source_id=output_id).url - pl = lambda x: ( # noqa: E731 - self._inject_datasources_data(x, input_url, output_url)) - args = list(map(pl, configs.get('args', []))) - configs['args'] = args - return configs - - def _prepare_job_running(self, job): - input_id, output_id = self._create_datasources(job) - main_libs, additional_libs = self._create_job_binaries(job) - job_id = self._create_job(job['type'], main_libs, additional_libs) - configs = self._parse_job_configs(job) - configs = self._put_io_data_to_configs( - configs, input_id, output_id) - return [job_id, input_id, output_id, configs] - - @track_result("Check EDP jobs", False) - def check_run_jobs(self): - batching = self.testcase.get('edp_batching', - len(self.testcase['edp_jobs_flow'])) - batching_size = batching - jobs = self.testcase.get('edp_jobs_flow', []) - - pre_exec = [] - for job in jobs: - pre_exec.append(self._prepare_job_running(job)) - batching -= 1 - if not batching: - self._job_batching(pre_exec) - pre_exec = [] - batching = batching_size - self.check_verification(self.cluster_id) - - def _job_batching(self, pre_exec): - job_exec_ids = [] - for job_exec in pre_exec: - job_exec_ids.append(self._run_job(*job_exec)) - - self._poll_jobs_status(job_exec_ids) - - def _create_datasources(self, job): - def create(ds, name): - credential_vars = {} - source = ds.get('source', None) - destination = None if source else utils.rand_name( - ds['destination']) - if ds['type'] == 'swift': - url = self._create_swift_data(source, destination) - credential_vars = { - 'credential_user': self.credentials['os_username'], - 'credential_pass': self.credentials['os_password'] - } - elif ds['type'] == 's3': - url = self._create_s3_data(source, destination) - credential_vars = { - 's3_credentials': { - 'accesskey': self.credentials['s3_accesskey'], - 'secretkey': self.credentials['s3_secretkey'], - 'endpoint': utils_url.url_schema_remover( - self.credentials['s3_endpoint']), - 'ssl': self.credentials['s3_endpoint_ssl'], - 'bucket_in_path': self.credentials['s3_bucket_path'] - } - } - elif ds['type'] == 'hdfs': - url = self._create_dfs_data(source, destination, - self.testcase.get('hdfs_username', - 'hadoop'), - ds['type']) - elif ds['type'] == 'maprfs': - url = self._create_dfs_data(source, destination, - ds.get('maprfs_username', 'mapr'), - ds['type']) - return self.__create_datasource( - name=utils.rand_name(name), - description='', - data_source_type=ds['type'], url=url, - **credential_vars) - - input_id, output_id = None, None - if job.get('input_datasource'): - ds = job['input_datasource'] - input_id = create(ds, 'input') - - if job.get('output_datasource'): - ds = job['output_datasource'] - output_id = create(ds, 'output') - - return input_id, output_id - - def _create_job_binaries(self, job): - main_libs = [] - additional_libs = [] - if job.get('main_lib'): - main_libs.append(self._create_job_binary(job['main_lib'])) - for add_lib in job.get('additional_libs', []): - lib_id = self._create_job_binary(add_lib) - additional_libs.append(lib_id) - - return main_libs, additional_libs - - def _create_job_binary(self, job_binary): - url = None - extra = {} - if job_binary['type'] == 'swift': - url = self._create_swift_data(job_binary['source']) - extra['user'] = self.credentials['os_username'] - extra['password'] = self.credentials['os_password'] - elif job_binary['type'] == 's3': - url = self._create_s3_data(job_binary['source']) - extra['accesskey'] = self.credentials['s3_accesskey'] - extra['secretkey'] = self.credentials['s3_secretkey'] - extra['endpoint'] = self.credentials['s3_endpoint'] - elif job_binary['type'] == 'database': - url = self._create_internal_db_data(job_binary['source']) - - job_binary_name = '%s-%s' % ( - utils.rand_name('test'), os.path.basename(job_binary['source'])) - return self.__create_job_binary(job_binary_name, url, '', extra) - - def _create_job(self, type, mains, libs): - return self.__create_job(utils.rand_name('test'), type, mains, - libs, '') - - def _parse_job_configs(self, job): - configs = {} - if job.get('configs'): - configs['configs'] = {} - for param, value in six.iteritems(job['configs']): - configs['configs'][param] = str(value) - if job.get('args'): - configs['args'] = list(map(str, job['args'])) - return configs - - def _run_job(self, job_id, input_id, output_id, configs): - return self.__run_job(job_id, self.cluster_id, input_id, output_id, - configs) - - def _poll_jobs_status(self, exec_ids): - try: - with fixtures.Timeout( - timeouts.Defaults.instance.timeout_poll_jobs_status, - gentle=True): - success = False - polling_ids = list(exec_ids) - while not success: - current_ids = list(polling_ids) - success = True - for exec_id in polling_ids: - status = self.sahara.get_job_status(exec_id) - if status not in ['FAILED', 'KILLED', 'DONEWITHERROR', - "SUCCEEDED"]: - success = False - else: - current_ids.remove(exec_id) - polling_ids = list(current_ids) - time.sleep(5) - finally: - report = [] - for exec_id in exec_ids: - status = self.sahara.get_job_status(exec_id) - if status != "SUCCEEDED": - info = self.sahara.get_job_info(exec_id) - report.append("Job with id={id}, name={name}, " - "type={type} has status " - "{status}".format(id=exec_id, - name=info.name, - type=info.type, - status=status)) - if report: - self.fail("\n".join(report)) - - def _get_file_with_defaults(self, file_path): - """ Check if the file exists; if it is a relative path, check also - among the default files. - """ - if not file_path: - return '' - - all_files = [file_path] - if not os.path.isabs(file_path): - # relative path: look into default templates too, if defined - default_file = os.path.join(self.default_templ_dir, file_path) - if os.path.abspath(default_file) != os.path.abspath(file_path): - all_files.append(default_file) - for checked_file in all_files: - if os.path.isfile(checked_file): - return checked_file - raise Exception('File %s not found while looking into %s' % - (file_path, all_files)) - - def _read_source_file(self, source): - if not source: - return None - with open(self._get_file_with_defaults(source), 'rb') as source_fd: - data = source_fd.read() - return data - - def _create_swift_data(self, source=None, destination=None): - container = self._get_swift_container() - path = utils.rand_name(destination if destination else 'test') - data = self._read_source_file(source) - - self.__upload_to_container(container, path, data) - - return 'swift://%s.sahara/%s' % (container, path) - - def _create_s3_data(self, source=None, destination=None): - bucket = self._get_s3_bucket() - path = utils.rand_name(destination if destination else 'test') - data = self._read_source_file(source) - - self.__upload_to_bucket(bucket, path, data) - - return 's3://%s/%s' % (bucket, path) - - def _create_dfs_data(self, source, destination, hdfs_username, fs): - - def to_hex_present(string): - return "".join(map(lambda x: hex(ord(x)).replace("0x", "\\x"), - string.decode('utf-8'))) - if destination: - return destination - - command_prefixes = {'hdfs': 'hdfs dfs', - 'maprfs': 'hadoop fs'} - - hdfs_dir = utils.rand_name("/user/%s/data" % hdfs_username) - instances = self._get_nodes_with_process('namenode') - if len(instances) == 0: - instances = self._get_nodes_with_process('CLDB') - inst_ip = instances[0]["management_ip"] - self._run_command_on_node( - inst_ip, - "sudo su - -c \"%(prefix)s -mkdir -p %(path)s \" %(user)s" % { - "prefix": command_prefixes[fs], - "path": hdfs_dir, - "user": hdfs_username}) - hdfs_filepath = utils.rand_name(hdfs_dir + "/file") - data = self._read_source_file(source) - if not data: - data = '' - self._run_command_on_node( - inst_ip, - ("echo -e \"%(data)s\" | sudo su - -c \"%(prefix)s" - " -put - %(path)s\" %(user)s") % { - "data": to_hex_present(data), - "prefix": command_prefixes[fs], - "path": hdfs_filepath, - "user": hdfs_username}) - return hdfs_filepath - - def _create_internal_db_data(self, source): - data = self._read_source_file(source) - id = self.__create_internal_db_data(utils.rand_name('test'), data) - return 'internal-db://%s' % id - - def _get_swift_container(self): - if not getattr(self, '__swift_container', None): - self.__swift_container = self.__create_container( - utils.rand_name('sahara-tests')) - return self.__swift_container - - def _get_s3_bucket(self): - if not getattr(self, '__s3_bucket', None): - self.__s3_bucket = self.__create_bucket( - utils.rand_name('sahara-tests')) - return self.__s3_bucket - - @track_result("Cluster scaling", False) - def check_scale(self): - scale_ops = [] - ng_before_scale = self.sahara.get_cluster(self.cluster_id).node_groups - scale_ops = self.testcase['scaling'] - - body = {} - for op in scale_ops: - node_scale = op['node_group'] - if op['operation'] == 'add': - if 'add_node_groups' not in body: - body['add_node_groups'] = [] - body['add_node_groups'].append({ - 'node_group_template_id': - self.ng_id_map.get(node_scale, - self.sahara.get_node_group_template_id( - node_scale)), - 'count': op['size'], - 'name': utils.rand_name(node_scale) - }) - if op['operation'] == 'resize': - if 'resize_node_groups' not in body: - body['resize_node_groups'] = [] - body['resize_node_groups'].append({ - 'name': self.ng_name_map.get( - node_scale, - self.sahara.get_node_group_template_id(node_scale)), - 'count': op['size'] - }) - - if body: - self.sahara.scale_cluster(self.cluster_id, body) - self._poll_cluster_status(self.cluster_id) - ng_after_scale = self.sahara.get_cluster( - self.cluster_id).node_groups - self._validate_scaling(ng_after_scale, - self._get_expected_count_of_nodes( - ng_before_scale, body)) - - def _validate_scaling(self, after, expected_count): - for (key, value) in six.iteritems(expected_count): - ng = {} - for after_ng in after: - if after_ng['name'] == key: - ng = after_ng - break - self.assertEqual(value, ng.get('count', 0)) - - def _get_expected_count_of_nodes(self, before, body): - expected_mapper = {} - for ng in before: - expected_mapper[ng['name']] = ng['count'] - for ng in body.get('add_node_groups', []): - expected_mapper[ng['name']] = ng['count'] - for ng in body.get('resize_node_groups', []): - expected_mapper[ng['name']] = ng['count'] - return expected_mapper - - @track_result("Check cinder volumes") - def check_cinder(self): - if not self._get_node_list_with_volumes() or not self.cinder: - print("All tests for Cinder were skipped") - return - for node_with_volumes in self._get_node_list_with_volumes(): - volume_count_on_node = int(self._run_command_on_node( - node_with_volumes['node_ip'], - 'mount | grep %s | wc -l' % - node_with_volumes['volume_mount_prefix'] - )) - self.assertEqual( - node_with_volumes['volume_count'], volume_count_on_node, - 'Some volumes were not mounted to node.\n' - 'Expected count of mounted volumes to node is %s.\n' - 'Actual count of mounted volumes to node is %s.' - % (node_with_volumes['volume_count'], volume_count_on_node) - ) - - def _get_node_list_with_volumes(self): - node_groups = self.sahara.get_cluster(self.cluster_id).node_groups - node_list_with_volumes = [] - for node_group in node_groups: - if node_group['volumes_per_node'] != 0: - for instance in node_group['instances']: - node_list_with_volumes.append({ - 'node_ip': instance['management_ip'], - 'volume_count': node_group['volumes_per_node'], - 'volume_mount_prefix': - node_group['volume_mount_prefix'] - }) - return node_list_with_volumes - - @track_result("Create node group templates") - def _create_node_group_templates(self): - ng_id_map = {} - floating_ip_pool = None - security_group = None - proxy_exist = False - - if self.network['public_network']: - floating_ip_pool = self.neutron.get_network_id( - self.network['public_network']) - - node_groups = [] - for ng in self.testcase['node_group_templates']: - node_groups.append(ng) - if ng.get('is_proxy_gateway', False): - proxy_exist = True - - for ng in node_groups: - kwargs = dict(ng) - kwargs.update(self.plugin_opts) - kwargs['flavor_id'] = self._get_flavor_id(kwargs['flavor']) - del kwargs['flavor'] - kwargs['name'] = utils.rand_name(kwargs['name']) - if (not proxy_exist) or (proxy_exist and kwargs.get( - 'is_proxy_gateway', False)): - kwargs['floating_ip_pool'] = floating_ip_pool - if not kwargs.get('auto_security_group', True): - if security_group is None: - sg_name = utils.rand_name('scenario') - security_group = self.__create_security_group(sg_name) - self.neutron.add_security_group_rule_for_neutron( - security_group) - kwargs['security_groups'] = [security_group] - - # boot_from_volume requires APIv2 - if kwargs.get('boot_from_volume', False) and not self.use_api_v2: - raise Exception('boot_from_volume is set for %s but it ' - 'requires APIv2' % (kwargs['name'])) - - ng_id = self.__create_node_group_template(**kwargs) - ng_id_map[ng['name']] = ng_id - return ng_id_map - - @track_result("Set flavor") - def _get_flavor_id(self, flavor): - if isinstance(flavor, six.string_types): - return self.nova.get_flavor_id(flavor) - else: - # if the name already exists, use it - if flavor.get('name'): - try: - return self.nova.get_flavor_id(flavor['name']) - except exc.NotFound: - print("Custom flavor %s not found, it will be created" % - (flavor['name'])) - - flavor_id = self.nova.create_flavor(flavor).id - self.addCleanup(self.nova.delete_flavor, flavor_id) - return flavor_id - - @track_result("Create cluster template") - def _create_cluster_template(self): - self.ng_name_map = {} - template = self.testcase['cluster_template'] - - kwargs = dict(template) - ngs = kwargs['node_group_templates'] - del kwargs['node_group_templates'] - kwargs['node_groups'] = [] - for ng, count in ngs.items(): - ng_name = utils.rand_name(ng) - self.ng_name_map[ng] = ng_name - kwargs['node_groups'].append({ - 'name': ng_name, - 'node_group_template_id': self.ng_id_map[ng], - 'count': count}) - - kwargs.update(self.plugin_opts) - kwargs['name'] = utils.rand_name(kwargs.get('name', 'ct')) - kwargs['net_id'] = self.neutron.get_network_id( - self.network['private_network']) - - return self.__create_cluster_template(**kwargs) - - @track_result("Check event logs") - def _check_event_logs(self, cluster): - invalid_steps = [] - if cluster.is_transient: - # skip event log testing - return - - for step in cluster.provision_progress: - if not step['successful']: - invalid_steps.append(step) - - if len(invalid_steps) > 0: - invalid_steps_info = "\n".join(six.text_type(e) - for e in invalid_steps) - steps_info = "\n".join(six.text_type(e) - for e in cluster.provision_progress) - raise exc.TempestException( - "Issues with event log work: " - "\n Incomplete steps: \n\n {invalid_steps}" - "\n All steps: \n\n {steps}".format( - steps=steps_info, - invalid_steps=invalid_steps_info)) - - @track_result("Create cluster") - def _create_cluster(self, cluster_template_id): - if self.testcase.get('cluster'): - kwargs = dict(self.testcase['cluster']) - else: - kwargs = {} # default template - - kwargs.update(self.plugin_opts) - kwargs['name'] = utils.rand_name(kwargs.get('name', 'test')) - kwargs['cluster_template_id'] = cluster_template_id - kwargs['default_image_id'] = self.glance.get_image_id( - self.testcase['image']) - kwargs['user_keypair_id'] = self.key_name - - return self.__create_cluster(**kwargs) - - @track_result("Check cluster state") - def _poll_cluster_status_tracked(self, cluster_id): - self._poll_cluster_status(cluster_id) - - def _poll_cluster_status(self, cluster_id): - with fixtures.Timeout( - timeouts.Defaults.instance.timeout_poll_cluster_status, - gentle=True): - while True: - status = self.sahara.get_cluster_status(cluster_id) - if status == CLUSTER_STATUS_ACTIVE: - break - if status == CLUSTER_STATUS_ERROR: - cluster = self.sahara.get_cluster(cluster_id) - failure_desc = cluster.status_description - message = ("Cluster in %s state with" - " a message below:\n%s") % (status, - failure_desc) - - raise exc.TempestException(message) - time.sleep(3) - - def _run_command_on_node(self, node_ip, command): - host_ip = node_ip - if self.proxy: - host_ip = self.proxy - command = ("echo '{pkey}' > {filename} && chmod 600 {filename} && " - "ssh -o StrictHostKeyChecking=no {ip} -i {filename} " - "'{cmd}' && rm {filename}".format( - pkey=self.private_key, filename='scenario.pem', - ip=node_ip, cmd=command)) - ssh_session = connection.Client(host_ip, self.testcase['ssh_username'], - pkey=self.private_key) - return ssh_session.exec_command(command) - - def _get_nodes_with_process(self, process=None): - if process is not None: - process = process.lower() - nodegroups = self.sahara.get_cluster(self.cluster_id).node_groups - nodes_with_process = [] - for nodegroup in nodegroups: - for node_process in nodegroup['node_processes']: - if not process or process in node_process.lower(): - nodes_with_process.extend(nodegroup['instances']) - return nodes_with_process - - def _get_health_status(self, cluster): - try: - return cluster.verification['status'] - except (AttributeError, KeyError): - return 'UNKNOWN' - - def _poll_verification_status(self, cluster_id): - with fixtures.Timeout( - timeouts.Defaults.instance.timeout_poll_cluster_status, - gentle=True): - while True: - cluster = self.sahara.get_cluster(cluster_id) - status = self._get_health_status(cluster) - if status == 'UNKNOWN': - print("Cluster verification did not start") - break - if status in HEALTH_CHECKS: - break - time.sleep(3) - - @track_result("Check cluster verification") - def check_verification(self, cluster_id): - if self.check_feature_available("verification"): - self._poll_cluster_status(cluster_id) - - # need to check if previous verification check is not - # in the status CHECKING - self._poll_verification_status(cluster_id) - self.sahara.start_cluster_verification(cluster_id) - - # check if this verification check finished without errors - self._poll_verification_status(cluster_id) - else: - print("All tests for cluster verification were skipped") - - # client ops - - def __create_node_group_template(self, *args, **kwargs): - id = self.sahara.create_node_group_template(*args, **kwargs) - if not self.testcase['retain_resources']: - self.addCleanup(self.sahara.delete_node_group_template, id) - return id - - def __create_security_group(self, sg_name): - id = self.neutron.create_security_group_for_neutron(sg_name) - if not self.testcase['retain_resources']: - self.addCleanup(self.neutron.delete_security_group_for_neutron, id) - return id - - def __create_cluster_template(self, *args, **kwargs): - id = self.sahara.create_cluster_template(*args, **kwargs) - if not self.testcase['retain_resources']: - self.addCleanup(self.sahara.delete_cluster_template, id) - return id - - def __create_cluster(self, *args, **kwargs): - id = self.sahara.create_cluster(*args, **kwargs) - if not self.testcase['retain_resources']: - self.addCleanup(self.sahara.delete_cluster, id) - return id - - def __create_datasource(self, *args, **kwargs): - id = self.sahara.create_datasource(*args, **kwargs) - if not self.testcase['retain_resources']: - self.addCleanup(self.sahara.delete_datasource, id) - return id - - def __create_internal_db_data(self, *args, **kwargs): - id = self.sahara.create_job_binary_internal(*args, **kwargs) - if not self.testcase['retain_resources']: - self.addCleanup(self.sahara.delete_job_binary_internal, id) - return id - - def __create_job_binary(self, *args, **kwargs): - id = self.sahara.create_job_binary(*args, **kwargs) - if not self.testcase['retain_resources']: - self.addCleanup(self.sahara.delete_job_binary, id) - return id - - def __create_job(self, *args, **kwargs): - id = self.sahara.create_job_template(*args, **kwargs) - if not self.testcase['retain_resources']: - self.addCleanup(self.sahara.delete_job_template, id) - return id - - def __run_job(self, *args, **kwargs): - id = self.sahara.run_job(*args, **kwargs) - if not self.testcase['retain_resources']: - self.addCleanup(self.sahara.delete_job_execution, id) - return id - - def __create_container(self, container_name): - self.swift.create_container(container_name) - if not self.testcase['retain_resources']: - self.addCleanup(self.swift.delete_container, container_name) - return container_name - - def __upload_to_container(self, container_name, object_name, data=None): - if data: - self.swift.upload_data(container_name, object_name, data) - if not self.testcase['retain_resources']: - self.addCleanup(self.swift.delete_object, container_name, - object_name) - - def __create_bucket(self, bucket_name): - self.boto.create_bucket(bucket_name) - if not self.testcase['retain_resources']: - self.addCleanup(self.boto.delete_bucket, bucket_name) - return bucket_name - - def __upload_to_bucket(self, bucket_name, object_name, data=None): - if data: - self.boto.upload_data(bucket_name, object_name, data) - if not self.testcase['retain_resources']: - self.addCleanup(self.boto.delete_object, bucket_name, - object_name) - - def __create_keypair(self): - key = utils.rand_name('scenario_key') - self.nova.nova_client.keypairs.create(key, - public_key=self.public_key) - if not self.testcase['retain_resources']: - self.addCleanup(self.nova.delete_keypair, key) - return key - - def check_feature_available(self, feature_name): - if not getattr(self.sahara.get_cluster(self.cluster_id), - feature_name, None): - return False - return True - - def tearDown(self): - tbs = [] - table = prettytable.PrettyTable(["Check", "Status", "Duration, s", - "Start time"]) - table.align["Check"] = "l" - for check in self._results: - table.add_row( - [check['check_name'], check['status'], check['duration'], - check['start_time']]) - if check['status'] == CHECK_FAILED_STATUS: - tbs.append(check['exception_time']) - tbs.extend(check['traceback']) - tbs.append("") - print("Results of testing plugin", self.plugin_opts['plugin_name'], - self.plugin_opts[self.plugin_version_option]) - print(table) - print("\n".join(tbs), file=sys.stderr) - - super(BaseTestCase, self).tearDown() - - test_failed = any([c['status'] == CHECK_FAILED_STATUS - for c in self._results]) - - if self.report: - filename = {"time": time.strftime('%Y%m%d%H%M%S', - time.localtime())} - filename.update(self.plugin_opts) - # let's normalize this variable so that we can use - # a stable name as formatter later. - if 'hadoop_version' in filename: - filename['plugin_version'] = filename['hadoop_version'] - del filename['hadoop_version'] - report_file_name = os.path.join( - self.results_dir, - '{plugin_name}_{plugin_version}-{time}'.format(**filename)) - time.strftime('%Y%m%d%H%M%S', time.localtime()) - with open(report_file_name, 'w+') as report_file: - report_file.write(str(self._results)) - print("Results can be found in %s" % report_file_name) - if test_failed: - self.fail("Scenario tests failed") diff --git a/sahara_tests/scenario/clients.py b/sahara_tests/scenario/clients.py deleted file mode 100644 index 28b718f3..00000000 --- a/sahara_tests/scenario/clients.py +++ /dev/null @@ -1,451 +0,0 @@ -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import print_function -import time - -import fixtures -from botocore.exceptions import ClientError as BotoClientError -from botocore import session as botocore_session -from glanceclient import client as glance_client -from keystoneauth1.identity import v3 as identity_v3 -from keystoneauth1 import session -from neutronclient.neutron import client as neutron_client -from novaclient import client as nova_client -from novaclient import exceptions as nova_exc -from oslo_utils import uuidutils -from saharaclient.api import base as saharaclient_base -from saharaclient import client as sahara_client -import six -from swiftclient import client as swift_client -from swiftclient import exceptions as swift_exc -from tempest.lib import exceptions as exc - -from sahara_tests.scenario import timeouts -from sahara_tests.scenario import utils - - -def get_session(auth_url=None, username=None, password=None, - project_name=None, verify=True, cert=None): - auth_url_fixed = auth_url.replace('/v2.0', '/v3') - if not auth_url_fixed.endswith('/v3'): - auth_url_fixed += '/v3' - auth = identity_v3.Password(auth_url=auth_url_fixed, - username=username, - password=password, - project_name=project_name, - user_domain_name='default', - project_domain_name='default') - return session.Session(auth=auth, verify=verify, cert=cert) - - -class Client(object): - def is_resource_deleted(self, method, *args, **kwargs): - raise NotImplementedError - - def delete_resource(self, method, *args, **kwargs): - with fixtures.Timeout( - timeouts.Defaults.instance.timeout_delete_resource, - gentle=True): - while True: - if self.is_resource_deleted(method, *args, **kwargs): - break - time.sleep(5) - - -class SaharaClient(Client): - def __init__(self, *args, **kwargs): - self.api_version = '1.1' - if 'api_version' in kwargs: - self.api_version = kwargs['api_version'] - del kwargs['api_version'] - self.sahara_client = sahara_client.Client(self.api_version, *args, - **kwargs) - - def create_node_group_template(self, *args, **kwargs): - data = self.sahara_client.node_group_templates.create(*args, **kwargs) - return data.id - - def delete_node_group_template(self, node_group_template_id): - return self.delete_resource( - self.sahara_client.node_group_templates.delete, - node_group_template_id) - - def create_cluster_template(self, *args, **kwargs): - data = self.sahara_client.cluster_templates.create(*args, **kwargs) - return data.id - - def delete_cluster_template(self, cluster_template_id): - return self.delete_resource( - self.sahara_client.cluster_templates.delete, - cluster_template_id) - - def create_cluster(self, *args, **kwargs): - data = self.sahara_client.clusters.create(*args, **kwargs) - return data.id - - def delete_cluster(self, cluster_id): - return self.delete_resource( - self.sahara_client.clusters.delete, - cluster_id) - - def scale_cluster(self, cluster_id, body): - return self.sahara_client.clusters.scale(cluster_id, body) - - def start_cluster_verification(self, cluster_id): - return self.sahara_client.clusters.verification_update(cluster_id, - 'START') - - def create_datasource(self, *args, **kwargs): - data = self.sahara_client.data_sources.create(*args, **kwargs) - return data.id - - def get_datasource(self, *args, **kwargs): - return self.sahara_client.data_sources.get(*args, **kwargs) - - def delete_datasource(self, datasource_id): - return self.delete_resource( - self.sahara_client.data_sources.delete, - datasource_id) - - def create_job_binary_internal(self, *args, **kwargs): - data = self.sahara_client.job_binary_internals.create(*args, **kwargs) - return data.id - - def delete_job_binary_internal(self, job_binary_internal_id): - return self.delete_resource( - self.sahara_client.job_binary_internals.delete, - job_binary_internal_id) - - def create_job_binary(self, *args, **kwargs): - data = self.sahara_client.job_binaries.create(*args, **kwargs) - return data.id - - def delete_job_binary(self, job_binary_id): - return self.delete_resource( - self.sahara_client.job_binaries.delete, - job_binary_id) - - def create_job_template(self, *args, **kwargs): - if self.api_version == '1.1': - data = self.sahara_client.jobs.create(*args, **kwargs) - else: - data = self.sahara_client.job_templates.create(*args, **kwargs) - return data.id - - def delete_job_template(self, job_id): - if self.api_version == '1.1': - delete_function = self.sahara_client.jobs.delete - else: - delete_function = self.sahara_client.job_templates.delete - return self.delete_resource(delete_function, job_id) - - def run_job(self, *args, **kwargs): - if self.api_version == '1.1': - data = self.sahara_client.job_executions.create(*args, **kwargs) - else: - data = self.sahara_client.jobs.create(*args, **kwargs) - return data.id - - def delete_job_execution(self, job_execution_id): - if self.api_version == '1.1': - delete_function = self.sahara_client.job_executions.delete - else: - delete_function = self.sahara_client.jobs.delete - return self.delete_resource(delete_function, job_execution_id) - - def get_cluster(self, cluster_id, show_progress=False): - return self.sahara_client.clusters.get(cluster_id, show_progress) - - def get_cluster_status(self, cluster_id): - data = self.sahara_client.clusters.get(cluster_id) - return str(data.status) - - def get_job_status(self, exec_id): - if self.api_version == '1.1': - data = self.sahara_client.job_executions.get(exec_id) - else: - data = self.sahara_client.jobs.get(exec_id) - return str(data.info['status']) - - def get_job_info(self, exec_id): - if self.api_version == '1.1': - job_execution = self.sahara_client.job_executions.get(exec_id) - else: - job_execution = self.sahara_client.jobs.get(exec_id) - return self.sahara_client.jobs.get(job_execution.job_id) - - def get_cluster_id(self, name): - if uuidutils.is_uuid_like(name): - return name - for cluster in self.sahara_client.clusters.list(): - if cluster.name == name: - return cluster.id - - def get_node_group_template_id(self, name): - for nodegroup in self.sahara_client.node_group_templates.list(): - if nodegroup.name == name: - return nodegroup.id - - def register_image(self, image_id, testcase): - try: - return self.sahara_client.images.get(image_id) - except saharaclient_base.APIException: - print("Image not registered in sahara. Registering and run tests") - if testcase.get('image_username') is not None: - self.sahara_client.images.update_image( - image_id, testcase.get('image_username'), - "Registered by scenario tests") - self.sahara_client.images.update_tags( - image_id, [testcase["plugin_name"], - testcase["plugin_version"]]) - else: - raise exc.InvalidContentType( - "Registering of image failed. Please, specify " - "'image_username'. For details see README in scenario " - "tests.") - return self.sahara_client.images.get(image_id) - - def is_resource_deleted(self, method, *args, **kwargs): - try: - method(*args, **kwargs) - except saharaclient_base.APIException as ex: - return ex.error_code == 404 - - return False - - -class NovaClient(Client): - def __init__(self, *args, **kwargs): - self.nova_client = nova_client.Client('2', *args, **kwargs) - - def get_flavor_id(self, flavor_name): - if (uuidutils.is_uuid_like(flavor_name) or - (isinstance(flavor_name, six.string_types) and - flavor_name.isdigit())): - return flavor_name - for flavor in self.nova_client.flavors.list(): - if flavor.name == flavor_name: - return flavor.id - - raise exc.NotFound(flavor_name) - - def create_flavor(self, flavor_object): - return self.nova_client.flavors.create( - flavor_object.get('name', utils.rand_name('scenario')), - flavor_object.get('ram', 1), - flavor_object.get('vcpus', 1), - flavor_object.get('root_disk', 0), - ephemeral=flavor_object.get('ephemeral_disk', 0), - swap=flavor_object.get('swap_disk', 0), - flavorid=flavor_object.get('id', 'auto')) - - def delete_flavor(self, flavor_id): - return self.delete_resource(self.nova_client.flavors.delete, flavor_id) - - def delete_keypair(self, key_name): - return self.delete_resource( - self.nova_client.keypairs.delete, key_name) - - def is_resource_deleted(self, method, *args, **kwargs): - try: - method(*args, **kwargs) - except nova_exc.NotFound as ex: - return ex.code == 404 - - return False - - -class NeutronClient(Client): - def __init__(self, *args, **kwargs): - self.neutron_client = neutron_client.Client('2.0', *args, **kwargs) - - def get_network_id(self, network_name): - if uuidutils.is_uuid_like(network_name): - return network_name - networks = self.neutron_client.list_networks(name=network_name) - networks = networks['networks'] - if len(networks) < 1: - raise exc.NotFound(network_name) - return networks[0]['id'] - - def create_security_group_for_neutron(self, sg_name): - security_group = self.neutron_client.create_security_group({ - "security_group": - { - "name": sg_name, - "description": "Just for test" - } - }) - return security_group['security_group']['id'] - - def get_security_group_id(self, sg_name): - for sg in (self.neutron_client.list_security_groups() - ["security_groups"]): - if sg['name'] == sg_name: - return sg['id'] - - raise exc.NotFound(sg_name) - - def add_security_group_rule_for_neutron(self, sg_id): - return self.neutron_client.create_security_group_rule({ - "security_group_rules": [ - { - "direction": "ingress", - "ethertype": "IPv4", - "port_range_max": 65535, - "port_range_min": 1, - "protocol": "TCP", - "remote_group_id": None, - "remote_ip_prefix": None, - "security_group_id": sg_id - }, - { - "direction": "egress", - "ethertype": "IPv4", - "port_range_max": 65535, - "port_range_min": 1, - "protocol": "TCP", - "remote_group_id": None, - "remote_ip_prefix": None, - "security_group_id": sg_id - } - ] - }) - - def delete_security_group_for_neutron(self, sg_id): - return self.neutron_client.delete_security_group(sg_id) - - -class SwiftClient(Client): - def __init__(self, *args, **kwargs): - self.swift_client = swift_client.Connection(*args, **kwargs) - - def create_container(self, container_name): - return self.swift_client.put_container(container_name) - - def delete_container(self, container_name): - objects = self._get_objects(container_name) - for obj in objects: - self.delete_object(container_name, obj) - return self.delete_resource( - self.swift_client.delete_container, container_name) - - def _get_objects(self, container_name): - metadata = self.swift_client.get_container(container_name) - objects = [] - for obj in metadata[1]: - objects.append(obj['name']) - return objects[::-1] - - def upload_data(self, container_name, object_name, data): - return self.swift_client.put_object(container_name, object_name, data) - - def delete_object(self, container_name, object_name): - return self.delete_resource( - self.swift_client.delete_object, - container_name, - object_name) - - def is_resource_deleted(self, method, *args, **kwargs): - try: - method(*args, **kwargs) - except swift_exc.ClientException as ex: - return ex.http_status == 404 - - return False - - -class BotoClient(Client): - def __init__(self, *args, **kwargs): - sess = botocore_session.get_session() - self.boto_client = sess.create_client( - 's3', - endpoint_url=kwargs['endpoint'], - aws_access_key_id=kwargs['accesskey'], - aws_secret_access_key=kwargs['secretkey'] - ) - - def create_bucket(self, bucket_name): - return self.boto_client.create_bucket(Bucket=bucket_name) - - def _delete_and_check_bucket(self, bucket_name): - bucket_deleted = False - operation_parameters = {'Bucket': bucket_name} - try: - # While list_objects_v2 is the suggested function, pagination - # does not seems to work properly with RadosGW when it's used. - paginator = self.boto_client.get_paginator('list_objects') - page_iterator = paginator.paginate(**operation_parameters) - for page in page_iterator: - if 'Contents' not in page: - continue - for item in page['Contents']: - self.boto_client.delete_object(Bucket=bucket_name, - Key=item['Key']) - self.boto_client.delete_bucket(Bucket=bucket_name) - except BotoClientError as ex: - error = ex.response.get('Error', {}) - # without the conversion the value is a tuple - error_code = '%s' % (error.get('Code', '')) - if error_code == 'NoSuchBucket': - bucket_deleted = True - return bucket_deleted - - def delete_bucket(self, bucket_name): - return self.delete_resource( - self._delete_and_check_bucket, bucket_name) - - def upload_data(self, bucket_name, object_name, data): - return self.boto_client.put_object( - Bucket=bucket_name, - Key=object_name, - Body=data) - - def _delete_and_check_object(self, bucket_name, object_name): - self.boto_client.delete_object(Bucket=bucket_name, Key=object_name) - object_deleted = False - try: - self.boto_client.head_object(Bucket=bucket_name, Key=object_name) - except BotoClientError as ex: - error = ex.response.get('Error', {}) - # without the conversion the value is a tuple - error_code = '%s' % (error.get('Code', '')) - if error_code == '404': - object_deleted = True - return object_deleted - - def delete_object(self, bucket_name, object_name): - return self.delete_resource( - self._delete_and_check_object, - bucket_name, object_name) - - def is_resource_deleted(self, method, *args, **kwargs): - # Exceptions are handled directly inside the call to "method", - # because they are not the same for objects and buckets. - return method(*args, **kwargs) - - -class GlanceClient(Client): - def __init__(self, *args, **kwargs): - self.glance_client = glance_client.Client('2', *args, **kwargs) - - def get_image_id(self, image_name): - if uuidutils.is_uuid_like(image_name): - return image_name - for image in self.glance_client.images.list(): - if image.name == image_name: - return image.id - raise exc.NotFound(image_name) diff --git a/sahara_tests/scenario/custom_checks/__init__.py b/sahara_tests/scenario/custom_checks/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tests/scenario/custom_checks/check_cinder.py b/sahara_tests/scenario/custom_checks/check_cinder.py deleted file mode 100644 index 22301c55..00000000 --- a/sahara_tests/scenario/custom_checks/check_cinder.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -def check(self): - self.check_cinder() diff --git a/sahara_tests/scenario/custom_checks/check_kafka.py b/sahara_tests/scenario/custom_checks/check_kafka.py deleted file mode 100644 index d8c922fa..00000000 --- a/sahara_tests/scenario/custom_checks/check_kafka.py +++ /dev/null @@ -1,148 +0,0 @@ -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from sahara_tests.scenario import base as base_scenario -from sahara_tests.scenario import utils - - -class CustomCheckKafka(object): - def __init__(self, base_class): - self.base = base_class - - def _run_command_on_node(self, *args, **kwargs): - return self.base._run_command_on_node(*args, **kwargs) - - def _get_nodes_with_process(self, *args, **kwargs): - return self.base._get_nodes_with_process(*args, **kwargs) - - def fail(self, *args, **kwargs): - return self.base.fail(*args, **kwargs) - - def _prepare_job_running(self, *args, **kwargs): - return self.base._prepare_job_running(*args, **kwargs) - - def _job_batching(self, *args, **kwargs): - return self.base._job_batching(*args, **kwargs) - - @property - def _results(self): - return self.base._results - - @_results.setter - def _results(self, value): - self.base._results = value - - @staticmethod - def _get_nodes_desc_list(nodes, node_domain, port): - data = [] - for node in nodes: - fqdn = "{0}.{1}".format( - node["instance_name"], node_domain) - data.append("{0}:{1}".format(fqdn, port)) - return ",".join(data) - - def _get_node_ip(self, process): - node = self._get_nodes_with_process(process)[0] - return node["management_ip"] - - def _search_file_on_node(self, ip, file): - file_path = self._run_command_on_node( - ip, 'find / -name "{file}" 2>/dev/null -print | head -n 1' - .format(file=file)) - if not file_path: - self.fail("Cannot find file: {file}".format(file)) - return file_path.rstrip() - - def _create_test_topic(self, broker, topic, zookeepers): - ip = self._get_node_ip(broker) - scr = self._search_file_on_node(ip, "kafka-topics.sh") - # TODO(vgridnev): Avoid hardcoded values in future - self._run_command_on_node( - ip, "{script} --create --zookeeper {zoo} --replication-factor " - "1 --partitions 1 --topic {topic}".format( - script=scr, zoo=zookeepers, topic=topic)) - - def _send_messages(self, broker, topic, broker_list): - ip = self._get_node_ip(broker) - - scr = self._search_file_on_node(ip, "kafka-console-producer.sh") - messages = ["<_image} - name of image for each plugin; -* flavor ids: - * ${ci_flavor} - 2GB RAM, 1 VCPU, 40GB Root disk; - * ${medium_flavor} - 4GB RAM, 2 VCPUs, 40GB Root disk; - * ${large_flavor} - 8GB RAM, 4 VCPUs, 80GB Root disk; - -Main URLs ---------- - -https://sahara.mirantis.com/jenkins - Sahara CI Jenkins -https://github.com/openstack/sahara-ci-config/ - Sahara CI Config repo diff --git a/sahara_tests/scenario/defaults/ambari-2.3.yaml.mako b/sahara_tests/scenario/defaults/ambari-2.3.yaml.mako deleted file mode 100644 index a19de68a..00000000 --- a/sahara_tests/scenario/defaults/ambari-2.3.yaml.mako +++ /dev/null @@ -1,69 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.3' - image: ${ambari_23_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - spark_pi diff --git a/sahara_tests/scenario/defaults/ambari-2.4.yaml.mako b/sahara_tests/scenario/defaults/ambari-2.4.yaml.mako deleted file mode 100644 index a95e88e4..00000000 --- a/sahara_tests/scenario/defaults/ambari-2.4.yaml.mako +++ /dev/null @@ -1,72 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.4' - image: ${ambari_24_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - name: mapreduce_job_s3 - features: - - s3 - - spark_pi diff --git a/sahara_tests/scenario/defaults/ambari-2.5.yaml.mako b/sahara_tests/scenario/defaults/ambari-2.5.yaml.mako deleted file mode 100644 index fab951cb..00000000 --- a/sahara_tests/scenario/defaults/ambari-2.5.yaml.mako +++ /dev/null @@ -1,72 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.5' - image: ${ambari_25_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - name: mapreduce_job_s3 - features: - - s3 - - spark_pi diff --git a/sahara_tests/scenario/defaults/ambari-2.6.yaml.mako b/sahara_tests/scenario/defaults/ambari-2.6.yaml.mako deleted file mode 100644 index a06aac88..00000000 --- a/sahara_tests/scenario/defaults/ambari-2.6.yaml.mako +++ /dev/null @@ -1,72 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.6' - image: ${ambari_26_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - name: mapreduce_job_s3 - features: - - s3 - - spark_pi diff --git a/sahara_tests/scenario/defaults/cdh-5.11.0.yaml.mako b/sahara_tests/scenario/defaults/cdh-5.11.0.yaml.mako deleted file mode 100644 index 1ee7b190..00000000 --- a/sahara_tests/scenario/defaults/cdh-5.11.0.yaml.mako +++ /dev/null @@ -1,97 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.11.0 - image: ${cdh_5110_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In >=5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh5110 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - name: mapreduce_job_s3 - features: - - s3 - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/cdh-5.13.0.yaml.mako b/sahara_tests/scenario/defaults/cdh-5.13.0.yaml.mako deleted file mode 100644 index 9631ba72..00000000 --- a/sahara_tests/scenario/defaults/cdh-5.13.0.yaml.mako +++ /dev/null @@ -1,97 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.13.0 - image: ${cdh_5130_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In >=5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh5130 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - name: mapreduce_job_s3 - features: - - s3 - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/cdh-5.9.0.yaml.mako b/sahara_tests/scenario/defaults/cdh-5.9.0.yaml.mako deleted file mode 100644 index 22c40d8e..00000000 --- a/sahara_tests/scenario/defaults/cdh-5.9.0.yaml.mako +++ /dev/null @@ -1,97 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.9.0 - image: ${cdh_590_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In 5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh590 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - name: mapreduce_job_s3 - features: - - s3 - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/cdh-ha.yaml.mako b/sahara_tests/scenario/defaults/cdh-ha.yaml.mako deleted file mode 100644 index 4c1be8a7..00000000 --- a/sahara_tests/scenario/defaults/cdh-ha.yaml.mako +++ /dev/null @@ -1,81 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.4.0 - image: ${cdh_540_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - - HDFS_JOURNALNODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - - HDFS_JOURNALNODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${medium_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - ZOOKEEPER_SERVER - - YARN_JOBHISTORY - - HDFS_SECONDARYNAMENODE - - HDFS_JOURNALNODE - auto_security_group: ${use_auto_security_group} - cluster_template: - name: cdh540 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 2 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - general: - 'Require Anti Affinity': False - cluster: - name: ${cluster_name} - scenario: - - run_jobs - edp_jobs_flow: - - mapreduce_job - - java_job diff --git a/sahara_tests/scenario/defaults/credentials.yaml.mako b/sahara_tests/scenario/defaults/credentials.yaml.mako deleted file mode 100644 index a22ae77d..00000000 --- a/sahara_tests/scenario/defaults/credentials.yaml.mako +++ /dev/null @@ -1,5 +0,0 @@ -<%page args="network_public_name=''"/> - -network: - private_network: '${network_private_name}' - public_network: '${network_public_name}' diff --git a/sahara_tests/scenario/defaults/credentials_s3.yaml.mako b/sahara_tests/scenario/defaults/credentials_s3.yaml.mako deleted file mode 100644 index 48e01da7..00000000 --- a/sahara_tests/scenario/defaults/credentials_s3.yaml.mako +++ /dev/null @@ -1,6 +0,0 @@ -credentials: - s3_accesskey: ${s3_accesskey} - s3_secretkey: ${s3_secretkey} - s3_endpoint: ${s3_endpoint} - s3_endpoint_ssl: ${s3_endpoint_ssl} - s3_bucket_path: ${s3_bucket_path} diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-hive/expected_output.csv b/sahara_tests/scenario/defaults/edp-examples/edp-hive/expected_output.csv deleted file mode 100644 index be6bf27c..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-hive/expected_output.csv +++ /dev/null @@ -1,2 +0,0 @@ -Boris -Homer diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-hive/input.csv b/sahara_tests/scenario/defaults/edp-examples/edp-hive/input.csv deleted file mode 100644 index d682e130..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-hive/input.csv +++ /dev/null @@ -1,4 +0,0 @@ -Mike,20 -Boris,42 -Bart,12 -Homer,40 diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-hive/script.q b/sahara_tests/scenario/defaults/edp-examples/edp-hive/script.q deleted file mode 100644 index 22a0428b..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-hive/script.q +++ /dev/null @@ -1,6 +0,0 @@ -CREATE DATABASE IF NOT EXISTS tests LOCATION '/user/hive/warehouse/tests.db'; -CREATE EXTERNAL TABLE IF NOT EXISTS tests.students (name STRING, age INT) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' STORED AS TEXTFILE; -LOAD DATA INPATH '${INPUT}' INTO TABLE tests.students; -INSERT OVERWRITE DIRECTORY '${OUTPUT}' SELECT name FROM tests.students WHERE age > 30; -DROP TABLE IF EXISTS tests.students; -DROP DATABASE IF EXISTS tests; diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-java/README.rst b/sahara_tests/scenario/defaults/edp-examples/edp-java/README.rst deleted file mode 100644 index 740229b6..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-java/README.rst +++ /dev/null @@ -1,56 +0,0 @@ -===================== -EDP WordCount Example -===================== - -Overview --------- - -``WordCount.java`` is a modified version of the WordCount example bundled with -version 1.2.1 of Apache Hadoop. It has been extended for use from a java action -in an Oozie workflow. The modification below allows any configuration values -from the ```` tag in an Oozie workflow to be set in the -Configuration object:: - - // This will add properties from the tag specified - // in the Oozie workflow. For java actions, Oozie writes the - // configuration values to a file pointed to by ooze.action.conf.xml - conf.addResource(new Path("file:///", - System.getProperty("oozie.action.conf.xml"))); - -In the example workflow, we use the ```` tag to specify user and -password configuration values for accessing swift objects. - -Compiling ---------- - -To build the jar, add ``hadoop-core`` and ``commons-cli`` to the classpath. - -On a node running Ubuntu 13.04 with hadoop 1.2.1 the following commands -will compile ``WordCount.java`` from within the ``src`` directory:: - - $ mkdir wordcount_classes - $ javac -classpath /usr/share/hadoop/hadoop-core-1.2.1.jar:/usr/share/hadoop/lib/commons-cli-1.2.jar -d wordcount_classes WordCount.java - $ jar -cvf edp-java.jar -C wordcount_classes/ . - -Note, on a node with hadoop 2.3.0 the ``javac`` command above can be replaced with: - - $ javac -classpath /opt/hadoop-2.3.0/share/hadoop/common/hadoop-common-2.3.0.jar:/opt/hadoop-2.3.0/share/hadoop/mapreduce/hadoop-mapreduce-client-core-2.3.0.jar:/opt/hadoop-2.3.0/share/hadoop/common/lib/commons-cli-1.2.jar:/opt/hadoop-2.3.0/share/hadoop/mapreduce/lib/hadoop-annotations-2.3.0.jar -d wordcount_classes WordCount.java - -Running from the Sahara UI --------------------------- - -Running the WordCount example from the sahara UI is very similar to running a -Pig, Hive, or MapReduce job. - -1. Create a job binary that points to the ``edp-java.jar`` file -2. Create a ``Java`` job type and add the job binary to the ``libs`` value -3. Launch the job: - - 1. Add the input and output paths to ``args`` - 2. If swift input or output paths are used, set the - ``fs.swift.service.sahara.username`` and - ``fs.swift.service.sahara.password`` configuration values - 3. The Sahara UI will prompt for the required ``main_class`` value and - the optional ``java_opts`` value - - diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-java/edp-java.jar b/sahara_tests/scenario/defaults/edp-examples/edp-java/edp-java.jar deleted file mode 100644 index 910a24aae8b0850f42cd05007e962d8df6fecc68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4039 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 section. - -4. Upload your ``wordcount`` directory to the ``oozie.wf.application.path`` HDFS directory - (the ``oozie.wf.application.path`` directory is specified in the ``job.properties`` file): - - $ hadoop fs -put wordcount oozie.wf.application.path - -5. Launch the job, specifying the correct oozie server and port: - - $ oozie job -oozie http://oozie_server:port/oozie -config wordcount/job.properties -run - -6. Don't forget to create your swift input path! A sahara swift url looks - like ``swift://container.sahara/object``. diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-java/oozie_command_line/wordcount/job.properties b/sahara_tests/scenario/defaults/edp-examples/edp-java/oozie_command_line/wordcount/job.properties deleted file mode 100644 index 5e9f4fb7..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-java/oozie_command_line/wordcount/job.properties +++ /dev/null @@ -1,23 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -nameNode=hdfs://1.2.3.4:8020 -jobTracker=1.2.3.4:8021 -queueName=default - -oozie.wf.application.path=${nameNode}/user/${user.name}/wordcount diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-java/oozie_command_line/wordcount/workflow.xml b/sahara_tests/scenario/defaults/edp-examples/edp-java/oozie_command_line/wordcount/workflow.xml deleted file mode 100644 index d8bc820f..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-java/oozie_command_line/wordcount/workflow.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - ${jobTracker} - ${nameNode} - - - mapred.job.queue.name - ${queueName} - - - fs.swift.service.sahara.username - swiftuser - - - fs.swift.service.sahara.password - swiftpassword - - - org.openstack.sahara.examples.WordCount - swift://user.sahara/input - swift://user.sahara/output - - - - - - Java failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - - - diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-java/src/NOTICE.txt b/sahara_tests/scenario/defaults/edp-examples/edp-java/src/NOTICE.txt deleted file mode 100644 index 62fc5816..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-java/src/NOTICE.txt +++ /dev/null @@ -1,2 +0,0 @@ -This product includes software developed by The Apache Software -Foundation (http://www.apache.org/). diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-java/src/WordCount.java b/sahara_tests/scenario/defaults/edp-examples/edp-java/src/WordCount.java deleted file mode 100644 index 8f834b5b..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-java/src/WordCount.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openstack.sahara.examples; - -import java.io.IOException; -import java.util.StringTokenizer; - -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.io.IntWritable; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapreduce.Job; -import org.apache.hadoop.mapreduce.Mapper; -import org.apache.hadoop.mapreduce.Reducer; -import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; -import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; -import org.apache.hadoop.util.GenericOptionsParser; - -public class WordCount { - - public static class TokenizerMapper - extends Mapper{ - - private final static IntWritable one = new IntWritable(1); - private Text word = new Text(); - - public void map(Object key, Text value, Context context - ) throws IOException, InterruptedException { - StringTokenizer itr = new StringTokenizer(value.toString()); - while (itr.hasMoreTokens()) { - word.set(itr.nextToken()); - context.write(word, one); - } - } - } - - public static class IntSumReducer - extends Reducer { - private IntWritable result = new IntWritable(); - - public void reduce(Text key, Iterable values, - Context context - ) throws IOException, InterruptedException { - int sum = 0; - for (IntWritable val : values) { - sum += val.get(); - } - result.set(sum); - context.write(key, result); - } - } - - public static void main(String[] args) throws Exception { - Configuration conf = new Configuration(); - String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs(); - if (otherArgs.length != 2) { - System.err.println("Usage: wordcount "); - System.exit(2); - } - - // ---- Begin modifications for EDP ---- - // This will add properties from the tag specified - // in the Oozie workflow. For java actions, Oozie writes the - // configuration values to a file pointed to by ooze.action.conf.xml - conf.addResource(new Path("file:///", - System.getProperty("oozie.action.conf.xml"))); - // ---- End modifications for EDP ---- - - Job job = new Job(conf, "word count"); - job.setJarByClass(WordCount.class); - job.setMapperClass(TokenizerMapper.class); - job.setCombinerClass(IntSumReducer.class); - job.setReducerClass(IntSumReducer.class); - job.setOutputKeyClass(Text.class); - job.setOutputValueClass(IntWritable.class); - FileInputFormat.addInputPath(job, new Path(otherArgs[0])); - FileOutputFormat.setOutputPath(job, new Path(otherArgs[1])); - System.exit(job.waitForCompletion(true) ? 0 : 1); - } -} diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-mapreduce/edp-mapreduce.jar b/sahara_tests/scenario/defaults/edp-examples/edp-mapreduce/edp-mapreduce.jar deleted file mode 100644 index 04388a958325568618c807f4e5d764f303a765f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15641 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 XB*9*b5`lo;zJAVM?~s4QPeA_<2*LpM diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/README.rst b/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/README.rst deleted file mode 100644 index b3d1a187..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/README.rst +++ /dev/null @@ -1,35 +0,0 @@ -========================= -Pig StringCleaner Example -========================= - -Overview --------- -This is an (almost useless) example of Pig job which uses a custom UDF (User -Defined Function). - -- ``StringCleaner.java`` is a Pig UDF which strips some characters from the - input. -- ``example.pig`` is the main Pig code which uses the UDF; - - -Compiling the UDF ------------------ - -To build the jar, add ``pig`` to the classpath. - - $ cd src - $ mkdir build - $ javac -source 1.6 -target 1.6 -cp /path/to/pig.jar -d build StringCleaner.java - $ jar -cvf edp-pig-stringcleaner.jar -C build/ . - -Running from the Sahara UI --------------------------- - -The procedure does not differ from the usual steps for other Pig jobs. - -Create a job template where: -- the main library points to the job binary for ``example.pig``; -- additional library contains the job binary for ``edp-pig-udf-stringcleaner.jar``. - -Create a job from that job template and attach the input and output data -sources. diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/data/expected_output b/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/data/expected_output deleted file mode 100644 index e7d6db23..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/data/expected_output +++ /dev/null @@ -1,3 +0,0 @@ -StrangeVariable01WithGarbage -a-confusedInput -this_will_be_more_compact diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/data/input b/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/data/input deleted file mode 100644 index 8e59da0b..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/data/input +++ /dev/null @@ -1,3 +0,0 @@ -Strange==Variable01 With Garbage -a"-confused Input - this _will_ be_ mo re _compact diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/edp-pig-udf-stringcleaner.jar b/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/edp-pig-udf-stringcleaner.jar deleted file mode 100644 index 464602cec176f1af2cfc7e149eccccdef58f118c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1539 zcmWIWW@Zs#;Nak3SkNuz$$$hn8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1gihn?0PS!EVvsAi7#M&q zn}+5E7NBf?QMx{E6?{+?`30$Y#U+W!*?827VyG=n%t$Os#G^$HmzLCu#N2|M)M7k( zl?duB$b@(e5`XBPHzi7ca7j^SUb=HmYGPh$kzR66VsUZr#8_`>N0GMsC$FpB-g0c~ zwYrnMd^vUz_}Bq%JiNPkBH5lyJF#_g+`4z>S41V)aHiGV&b<}f1=KR zpxX1mUHP9P>Q+Xz?D;HSxv~;dd34PGUg*kE2wn3>?_>n~r_EPw=e|&U%O6@{VDRr; z&ilwz^W4jQY}b}E&z<#A&HkHHP}>`~T?>1JV$A;iP2c>{*Yw;y({0l3_OF;U9_xm& zXk9)&?W2T0OG?#I4YkYuQ|h&TuUfrkl1b2BizWYlT;t!xx#rL5DF+VinA!K__+k6= zhvjsRF8s7ovW+Rae&&(-vuvUB>IGj;Z}rG6t~s(g_1qirpI@~*xb;}7*}0N0K0ZD;vld4j|M8rji59ARYjy CJgnaU diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/example.pig b/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/example.pig deleted file mode 100644 index 9aa69f09..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/example.pig +++ /dev/null @@ -1,3 +0,0 @@ -A = load '$INPUT' using PigStorage() as (lines: chararray); -B = foreach A generate org.openstack.sahara.examples.pig.StringCleaner(lines); -store B into '$OUTPUT' USING PigStorage(); diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/src/StringCleaner.java b/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/src/StringCleaner.java deleted file mode 100644 index 333f539e..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-pig/cleanup-string/src/StringCleaner.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package org.openstack.sahara.examples.pig; - -import org.apache.pig.PrimitiveEvalFunc; - -public class StringCleaner extends PrimitiveEvalFunc -{ - public String exec(String input) { - // Useless example which removes all but few basic latin characters - // and separators - return input.replaceAll("[^A-Za-z0-9-_]+", ""); - } -} diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/README.rst b/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/README.rst deleted file mode 100644 index 2c1cb3b9..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/README.rst +++ /dev/null @@ -1,68 +0,0 @@ -Top TODOers Pig job -=================== - -This script calculates top TODOers in input sources. - -Example of usage ----------------- - -This pig script can process as many input files (sources) as you want. -Just put all input files in a directory in HDFS or container in Swift and -give the path of the HDFS directory (Swift object) as input DataSource for EDP. - -Here are steps how to prepare input data: - -1. Create dir 'input' - -.. sourcecode:: console - - $ mkdir input - -2. Get some sources from GitHub and put it to 'input' directory: - -.. sourcecode:: console - - $ cd input - $ git clone "https://github.com/openstack/swift.git" - $ git clone "https://github.com/openstack/nova.git" - $ git clone "https://github.com/openstack/glance.git" - $ git clone "https://github.com/openstack/image-api.git" - $ git clone "https://github.com/openstack/neutron.git" - $ git clone "https://github.com/openstack/horizon.git" - $ git clone "https://github.com/openstack/python-novaclient.git" - $ git clone "https://github.com/openstack/python-keystoneclient.git" - $ git clone "https://github.com/openstack/oslo-incubator.git" - $ git clone "https://github.com/openstack/python-neutronclient.git" - $ git clone "https://github.com/openstack/python-glanceclient.git" - $ git clone "https://github.com/openstack/python-swiftclient.git" - $ git clone "https://github.com/openstack/python-cinderclient.git" - $ git clone "https://github.com/openstack/ceilometer.git" - $ git clone "https://github.com/openstack/cinder.git" - $ git clone "https://github.com/openstack/heat.git" - $ git clone "https://github.com/openstack/python-heatclient.git" - $ git clone "https://github.com/openstack/python-ceilometerclient.git" - $ git clone "https://github.com/openstack/oslo.config.git" - $ git clone "https://github.com/openstack/ironic.git" - $ git clone "https://github.com/openstack/python-ironicclient.git" - $ git clone "https://github.com/openstack/operations-guide.git" - $ git clone "https://github.com/openstack/keystone.git" - $ git clone "https://github.com/openstack/oslo.messaging.git" - $ git clone "https://github.com/openstack/oslo.sphinx.git" - $ git clone "https://github.com/openstack/oslo.version.git" - $ git clone "https://github.com/openstack/sahara.git" - $ git clone "https://github.com/openstack/python-saharaclient.git" - $ git clone "https://github.com/openstack/openstack.git" - $ cd .. - -3. Create single file containing all sources: - -.. sourcecode:: console - - tar -cf input.tar input/* - -.. note:: - - Pig can operate with raw files as well as with compressed data, so in this - step you might want to create *.gz file with sources and it should work. - -4. Upload input.tar to Swift or HDFS as input data source for EDP processing \ No newline at end of file diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/data/expected_output b/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/data/expected_output deleted file mode 100644 index f2eb5ac3..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/data/expected_output +++ /dev/null @@ -1,3 +0,0 @@ -2 https://launchpad.net/~slukjanov -1 https://launchpad.net/~aignatov -1 https://launchpad.net/~mimccune \ No newline at end of file diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/data/input b/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/data/input deleted file mode 100644 index 91f25eea..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/data/input +++ /dev/null @@ -1,18 +0,0 @@ -# There is some source file with TODO labels inside - - -def sum(a, b): - # TODO(slukjanov): implement how to add numbers - return None - -def sum(a, b): - # TODO(slukjanov): implement how to subtract numbers - return None - -def divide(a, b): - # TODO(aignatov): implement how to divide numbers - return None - -def mul(a, b): - # TODO(mimccune): implement how to multiply numbers - return None diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/example.pig b/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/example.pig deleted file mode 100644 index e35d05aa..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-pig/top-todoers/example.pig +++ /dev/null @@ -1,17 +0,0 @@ -input_lines = LOAD '$INPUT' AS (line:chararray); - --- filter out any lines that are not with TODO -todo_lines = FILTER input_lines BY line MATCHES '.*TODO\\s*\\(\\w+\\)+.*'; -ids = FOREACH todo_lines GENERATE FLATTEN(REGEX_EXTRACT($0, '(.*)\\((.*)\\)(.*)', 2)); - --- create a group for each word -id_groups = GROUP ids BY $0; - --- count the entries in each group -atc_count = FOREACH id_groups GENERATE COUNT(ids) AS count, group AS atc; - --- order the records by count -result = ORDER atc_count BY count DESC; -result = FOREACH result GENERATE count, CONCAT('https://launchpad.net/~', atc); - -STORE result INTO '$OUTPUT' USING PigStorage(); diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/trim-spaces/data/expected_output b/sahara_tests/scenario/defaults/edp-examples/edp-pig/trim-spaces/data/expected_output deleted file mode 100644 index 60a01427..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-pig/trim-spaces/data/expected_output +++ /dev/null @@ -1,4 +0,0 @@ -pomegranate -banana -apple -lychee \ No newline at end of file diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-pig/trim-spaces/data/input b/sahara_tests/scenario/defaults/edp-examples/edp-pig/trim-spaces/data/input deleted file mode 100644 index d2079b57..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-pig/trim-spaces/data/input +++ /dev/null @@ -1,4 +0,0 @@ - pomegranate - banana - apple - lychee \ No newline at end of file diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-shell/shell-example.sh b/sahara_tests/scenario/defaults/edp-examples/edp-shell/shell-example.sh deleted file mode 100644 index cee6e3f9..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-shell/shell-example.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -cat $EXTRA_FILE > $1 -echo $USER >> $1 \ No newline at end of file diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-shell/shell-example.txt b/sahara_tests/scenario/defaults/edp-examples/edp-shell/shell-example.txt deleted file mode 100644 index fe9a086a..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-shell/shell-example.txt +++ /dev/null @@ -1 +0,0 @@ -The user running this shell script is: \ No newline at end of file diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-spark/NOTICE.txt b/sahara_tests/scenario/defaults/edp-examples/edp-spark/NOTICE.txt deleted file mode 100644 index 87f2397f..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-spark/NOTICE.txt +++ /dev/null @@ -1,2 +0,0 @@ -This example includes software developed by The Apache Software -Foundation (http://www.apache.org/). diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-spark/README.rst b/sahara_tests/scenario/defaults/edp-examples/edp-spark/README.rst deleted file mode 100644 index 0cbf1d47..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-spark/README.rst +++ /dev/null @@ -1,66 +0,0 @@ -Example Spark Job -================= - -This example contains the compiled classes for SparkPi extracted from -the example jar distributed with Apache Spark version 1.3.1. - -SparkPi example estimates Pi. It can take a single optional integer -argument specifying the number of slices (tasks) to use. - -Example spark-wordcount Job -=========================== - -spark-wordcount is a modified version of the WordCount example from Apache -Spark. It can read input data from hdfs or swift container, then output the -number of occurrences of each word to standard output or hdfs. - -Launching wordcount job from Sahara UI --------------------------------------- - -1. Create a job binary that points to ``spark-wordcount.jar``. -2. Create a job template and set ``spark-wordcount.jar`` as the main binary - of the job template. -3. Create a Swift container with your input file. As example, you can upload - ``sample_input.txt``. -3. Launch job: - - 1. Put path to input file in ``args`` - 2. Put path to output file in ``args`` - 3. Fill the ``Main class`` input with the following class: - ``sahara.edp.spark.SparkWordCount`` - 4. Put the following values in the job's configs: - ``edp.spark.adapt_for_swift`` with value ``True``, - ``fs.swift.service.sahara.password`` with password for your username, - and ``fs.swift.service.sahara.username`` with your username. These - values are required for correct access to your input file, located in - Swift. - 5. Execute the job. You will be able to view your output in hdfs. - -Launching spark-kafka-example ------------------------------ - -0. Create a cluster with ``Kafka Broker``, ``ZooKeeper`` and - ``Spark History Server``. The Ambari plugin can be used for that purpose. - Please, use your keypair during cluster creation to have the ability to - ssh in instances with that processes. For simplicity, these services - should located on same the node. -1. Ssh to the node with the ``Kafka Broker`` service. Create a sample topic - using the following command: - ``path/kafka-topics.sh --create --zookeeper localhost:2181 \ - --replication-factor 1 --partitions 1 --topic test-topic``. - Also execute ``path/kafka-console-producer.sh --broker-list \ - localhost:6667 --topic test-topic`` and then put several messages in the - topic. Please, note that you need to replace the values ``localhost`` - and ``path`` with your own values. -2. Download the Spark Streaming utils to the node with your - ``Spark History Server`` from this URL: - ``https://repo1.maven.org/maven2/org/apache/spark/spark-streaming-kafka-assembly_2.10/1.4.1/spark-streaming-kafka-assembly_2.10-1.4.1.jar``. - Now you are ready to launch your job from sahara UI. -3. Create a job binary that points to ``spark-kafka-example.py``. - Also you need to create a job that uses this job binary as a main binary. -4. Execute the job with the following job configs: - ``edp.spark.driver.classpath`` with a value that points to the utils - downloaded during step 2. Also the job should be run with the following - arguments: ``localhost:2181`` as the first argument, ``test-topic`` as - the second, and ``30`` as the third. -5. Congratulations, your job was successfully launched! diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-spark/sample_input.txt b/sahara_tests/scenario/defaults/edp-examples/edp-spark/sample_input.txt deleted file mode 100644 index 25774450..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-spark/sample_input.txt +++ /dev/null @@ -1,10 +0,0 @@ -one -one -one -one -two -two -two -three -three -four diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-spark/spark-kafka-example.py b/sahara_tests/scenario/defaults/edp-examples/edp-spark/spark-kafka-example.py deleted file mode 100644 index a344283e..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-spark/spark-kafka-example.py +++ /dev/null @@ -1,48 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import print_function - -import sys - -from pyspark import SparkContext -from pyspark.streaming.kafka import KafkaUtils -from pyspark.streaming import StreamingContext - - -def main(): - if len(sys.argv) != 4: - print("Usage: kafka_wordcount.py ", - file=sys.stderr) - exit(-1) - - sc = SparkContext(appName="PythonStreamingKafkaWordCount") - ssc = StreamingContext(sc, 1) - timeout = None - if len(sys.argv) == 4: - zk, topic, timeout = sys.argv[1:] - timeout = int(timeout) - else: - zk, topic = sys.argv[1:] - kvs = KafkaUtils.createStream( - ssc, zk, "spark-streaming-consumer", {topic: 1}) - lines = kvs.map(lambda x: x[1]) - counts = lines.flatMap(lambda line: (line.split(" ")) - .map(lambda word: (word, 1)) - .reduceByKey(lambda a, b: a + b)) - counts.pprint() - kwargs = {} - if timeout: - kwargs['timeout'] = timeout - ssc.start() - ssc.awaitTermination(**kwargs) diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-spark/spark-pi.py b/sahara_tests/scenario/defaults/edp-examples/edp-spark/spark-pi.py deleted file mode 100644 index 9c7627e0..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-spark/spark-pi.py +++ /dev/null @@ -1,39 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import print_function - -import sys -from random import random -from operator import add - -from pyspark import SparkContext - - -if __name__ == "__main__": - """ - Usage: pi [partitions] - """ - sc = SparkContext(appName="PythonPi") - partitions = int(sys.argv[1]) if len(sys.argv) > 1 else 2 - n = 100000 * partitions - - def f(_): - x = random() * 2 - 1 - y = random() * 2 - 1 - return 1 if x ** 2 + y ** 2 < 1 else 0 - - count = sc.parallelize(range(1, n + 1), partitions).map(f).reduce(add) - print("Pi is roughly %f" % (4.0 * count / n)) - - sc.stop() diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-spark/spark-wordcount.jar b/sahara_tests/scenario/defaults/edp-examples/edp-spark/spark-wordcount.jar deleted file mode 100644 index 9b2b28b19f1a55cc23c15c583d819bb7f2b6ba9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6900 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 - - - 4.0.0 - sahara.edp.spark - sparkwordcount - 0.0.1-SNAPSHOT - jar - "Spark Word Count" - - - - - org.scala-tools - maven-scala-plugin - 2.15.2 - - - - compile - - - - - - maven-compiler-plugin - 3.1 - - 1.6 - 1.6 - - - - - - - - org.scala-lang - scala-library - 2.10.4 - - - - org.apache.spark - spark-core_2.10 - 1.6.0 - - - diff --git a/sahara_tests/scenario/defaults/edp-examples/edp-spark/wordcountapp/src/main/scala/sahara/edp/spark/SparkWordCount.scala b/sahara_tests/scenario/defaults/edp-examples/edp-spark/wordcountapp/src/main/scala/sahara/edp/spark/SparkWordCount.scala deleted file mode 100644 index 5a655aca..00000000 --- a/sahara_tests/scenario/defaults/edp-examples/edp-spark/wordcountapp/src/main/scala/sahara/edp/spark/SparkWordCount.scala +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sahara.edp.spark - -import org.apache.spark.SparkContext -import org.apache.spark.SparkContext._ -import org.apache.spark.SparkConf - -object SparkWordCount { - def main(args: Array[String]) { - val sc = new SparkContext(new SparkConf().setAppName("Spark Count")) - val dfsFileName = args(1) - // split each document into words - val tokenized = sc.textFile(args(0)).flatMap(_.split(" ")) - - // count the occurrence of each word - val wordCounts = tokenized.map((_, 1)).reduceByKey(_ + _) - - val fileRDD = sc.parallelize(wordCounts.collect()) - fileRDD.saveAsTextFile(dfsFileName) - } -} diff --git a/sahara_tests/scenario/defaults/edp-examples/hadoop2/edp-java/hadoop-mapreduce-examples-2.6.0.jar b/sahara_tests/scenario/defaults/edp-examples/hadoop2/edp-java/hadoop-mapreduce-examples-2.6.0.jar deleted file mode 100644 index 80f958818334f212f46d0cd0636e1c301857275b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270322 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
    tN&FMlXsQJOw(muFoppA06y!*t$(2G>|h07BKI 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

    _>OV( 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н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�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 P_>TnSzvc;0%pm - -clusters: - - plugin_name: fake - plugin_version: "0.1" - image: ${fake_plugin_image} - node_group_templates: - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - tasktracker - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master - flavor: ${ci_flavor_id} - node_processes: - - jobtracker - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - name: fake01 - node_group_templates: - master: 1 - worker: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: pig_job diff --git a/sahara_tests/scenario/defaults/mapr-5.2.0.mrv2.yaml.mako b/sahara_tests/scenario/defaults/mapr-5.2.0.mrv2.yaml.mako deleted file mode 100644 index b1064d4f..00000000 --- a/sahara_tests/scenario/defaults/mapr-5.2.0.mrv2.yaml.mako +++ /dev/null @@ -1,59 +0,0 @@ -<%page args="use_auto_security_group='true', mapr_master_flavor_id='mapr.master', mapr_worker_flavor_id='mapr.worker'"/> - -clusters: - - plugin_name: mapr - plugin_version: 5.2.0.mrv2 - image: ${mapr_520mrv2_image} - node_group_templates: - - name: master - flavor: - name: ${mapr_master_flavor_id} - vcpus: 4 - ram: 8192 - root_disk: 80 - ephemeral_disk: 40 - node_processes: - - Metrics - - Webserver - - ZooKeeper - - HTTPFS - - Oozie - - FileServer - - CLDB - - Flume - - Hue - - NodeManager - - HistoryServer - - ResourceManager - - HiveServer2 - - HiveMetastore - - Sqoop2-Client - - Sqoop2-Server - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: - name: ${mapr_worker_flavor_id} - vcpus: 2 - ram: 4096 - root_disk: 40 - ephemeral_disk: 40 - node_processes: - - NodeManager - - FileServer - auto_security_group: ${use_auto_security_group} - cluster_template: - name: mapr520mrv2 - node_group_templates: - master: 1 - worker: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - mapr - - name: mapreduce_job_s3 - features: - - s3 diff --git a/sahara_tests/scenario/defaults/pike/ambari-2.4.yaml.mako b/sahara_tests/scenario/defaults/pike/ambari-2.4.yaml.mako deleted file mode 100644 index 07754fb9..00000000 --- a/sahara_tests/scenario/defaults/pike/ambari-2.4.yaml.mako +++ /dev/null @@ -1,69 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.4' - image: ${ambari_22_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - spark_pi diff --git a/sahara_tests/scenario/defaults/pike/cdh-5.11.0.yaml.mako b/sahara_tests/scenario/defaults/pike/cdh-5.11.0.yaml.mako deleted file mode 100644 index 4f9aebdf..00000000 --- a/sahara_tests/scenario/defaults/pike/cdh-5.11.0.yaml.mako +++ /dev/null @@ -1,94 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.11.0 - image: ${cdh_5110_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In >=5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh5110 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/pike/cdh-5.7.0.yaml.mako b/sahara_tests/scenario/defaults/pike/cdh-5.7.0.yaml.mako deleted file mode 100644 index 89c9c142..00000000 --- a/sahara_tests/scenario/defaults/pike/cdh-5.7.0.yaml.mako +++ /dev/null @@ -1,94 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.7.0 - image: ${cdh_570_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In 5.7 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh570 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/pike/cdh-5.9.0.yaml.mako b/sahara_tests/scenario/defaults/pike/cdh-5.9.0.yaml.mako deleted file mode 100644 index e5c6c92c..00000000 --- a/sahara_tests/scenario/defaults/pike/cdh-5.9.0.yaml.mako +++ /dev/null @@ -1,94 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.9.0 - image: ${cdh_590_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In 5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh590 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/pike/mapr-5.2.0.mrv2.yaml.mako b/sahara_tests/scenario/defaults/pike/mapr-5.2.0.mrv2.yaml.mako deleted file mode 100644 index 777a73ab..00000000 --- a/sahara_tests/scenario/defaults/pike/mapr-5.2.0.mrv2.yaml.mako +++ /dev/null @@ -1,56 +0,0 @@ -<%page args="use_auto_security_group='true', mapr_master_flavor_id='mapr.master', mapr_worker_flavor_id='mapr.worker'"/> - -clusters: - - plugin_name: mapr - plugin_version: 5.2.0.mrv2 - image: ${mapr_520mrv2_image} - node_group_templates: - - name: master - flavor: - name: ${mapr_master_flavor_id} - vcpus: 4 - ram: 8192 - root_disk: 80 - ephemeral_disk: 40 - node_processes: - - Metrics - - Webserver - - ZooKeeper - - HTTPFS - - Oozie - - FileServer - - CLDB - - Flume - - Hue - - NodeManager - - HistoryServer - - ResourceManager - - HiveServer2 - - HiveMetastore - - Sqoop2-Client - - Sqoop2-Server - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: - name: ${mapr_worker_flavor_id} - vcpus: 2 - ram: 4096 - root_disk: 40 - ephemeral_disk: 40 - node_processes: - - NodeManager - - FileServer - auto_security_group: ${use_auto_security_group} - cluster_template: - name: mapr520mrv2 - node_group_templates: - master: 1 - worker: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - mapr diff --git a/sahara_tests/scenario/defaults/pike/spark-1.6.0.yaml.mako b/sahara_tests/scenario/defaults/pike/spark-1.6.0.yaml.mako deleted file mode 100644 index c3ad84c8..00000000 --- a/sahara_tests/scenario/defaults/pike/spark-1.6.0.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: spark - plugin_version: 1.6.0 - image: ${spark_160_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - master - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - slave - auto_security_group: ${use_auto_security_group} - cluster_template: - name: spark160 - node_group_templates: - master: 1 - worker: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - spark_pi - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/pike/spark-2.1.0.yaml.mako b/sahara_tests/scenario/defaults/pike/spark-2.1.0.yaml.mako deleted file mode 100644 index f7be74cd..00000000 --- a/sahara_tests/scenario/defaults/pike/spark-2.1.0.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: spark - plugin_version: 2.1.0 - image: ${spark_160_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - master - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - slave - auto_security_group: ${use_auto_security_group} - cluster_template: - name: spark210 - node_group_templates: - master: 1 - worker: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - spark_pi - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/pike/storm-1.1.0.yaml.mako b/sahara_tests/scenario/defaults/pike/storm-1.1.0.yaml.mako deleted file mode 100644 index cff00c53..00000000 --- a/sahara_tests/scenario/defaults/pike/storm-1.1.0.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium'"/> - -clusters: - - plugin_name: storm - plugin_version: 1.1.0 - image: ${storm_110_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - nimbus - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - supervisor - auto_security_group: ${use_auto_security_group} - - name: zookeeper - flavor: ${medium_flavor_id} - node_processes: - - zookeeper - auto_security_group: ${use_auto_security_group} - cluster_template: - name: storm110 - node_group_templates: - master: 1 - worker: 1 - zookeeper: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - scenario: - - scale diff --git a/sahara_tests/scenario/defaults/pike/vanilla-2.7.1.yaml.mako b/sahara_tests/scenario/defaults/pike/vanilla-2.7.1.yaml.mako deleted file mode 100644 index 77d70baf..00000000 --- a/sahara_tests/scenario/defaults/pike/vanilla-2.7.1.yaml.mako +++ /dev/null @@ -1,85 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.1 - image: ${vanilla_271_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - name: vanilla271 - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/queens/ambari-2.4.yaml.mako b/sahara_tests/scenario/defaults/queens/ambari-2.4.yaml.mako deleted file mode 100644 index 08e04c7c..00000000 --- a/sahara_tests/scenario/defaults/queens/ambari-2.4.yaml.mako +++ /dev/null @@ -1,69 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.4' - image: ${ambari_24_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - spark_pi diff --git a/sahara_tests/scenario/defaults/queens/cdh-5.11.0.yaml.mako b/sahara_tests/scenario/defaults/queens/cdh-5.11.0.yaml.mako deleted file mode 100644 index 4f9aebdf..00000000 --- a/sahara_tests/scenario/defaults/queens/cdh-5.11.0.yaml.mako +++ /dev/null @@ -1,94 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.11.0 - image: ${cdh_5110_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In >=5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh5110 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/queens/cdh-5.7.0.yaml.mako b/sahara_tests/scenario/defaults/queens/cdh-5.7.0.yaml.mako deleted file mode 100644 index 89c9c142..00000000 --- a/sahara_tests/scenario/defaults/queens/cdh-5.7.0.yaml.mako +++ /dev/null @@ -1,94 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.7.0 - image: ${cdh_570_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In 5.7 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh570 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/queens/cdh-5.9.0.yaml.mako b/sahara_tests/scenario/defaults/queens/cdh-5.9.0.yaml.mako deleted file mode 100644 index e5c6c92c..00000000 --- a/sahara_tests/scenario/defaults/queens/cdh-5.9.0.yaml.mako +++ /dev/null @@ -1,94 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.9.0 - image: ${cdh_590_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In 5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh590 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/queens/mapr-5.2.0.mrv2.yaml.mako b/sahara_tests/scenario/defaults/queens/mapr-5.2.0.mrv2.yaml.mako deleted file mode 100644 index 777a73ab..00000000 --- a/sahara_tests/scenario/defaults/queens/mapr-5.2.0.mrv2.yaml.mako +++ /dev/null @@ -1,56 +0,0 @@ -<%page args="use_auto_security_group='true', mapr_master_flavor_id='mapr.master', mapr_worker_flavor_id='mapr.worker'"/> - -clusters: - - plugin_name: mapr - plugin_version: 5.2.0.mrv2 - image: ${mapr_520mrv2_image} - node_group_templates: - - name: master - flavor: - name: ${mapr_master_flavor_id} - vcpus: 4 - ram: 8192 - root_disk: 80 - ephemeral_disk: 40 - node_processes: - - Metrics - - Webserver - - ZooKeeper - - HTTPFS - - Oozie - - FileServer - - CLDB - - Flume - - Hue - - NodeManager - - HistoryServer - - ResourceManager - - HiveServer2 - - HiveMetastore - - Sqoop2-Client - - Sqoop2-Server - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: - name: ${mapr_worker_flavor_id} - vcpus: 2 - ram: 4096 - root_disk: 40 - ephemeral_disk: 40 - node_processes: - - NodeManager - - FileServer - auto_security_group: ${use_auto_security_group} - cluster_template: - name: mapr520mrv2 - node_group_templates: - master: 1 - worker: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - mapr diff --git a/sahara_tests/scenario/defaults/queens/spark-2.1.0.yaml.mako b/sahara_tests/scenario/defaults/queens/spark-2.1.0.yaml.mako deleted file mode 100644 index 94264c1c..00000000 --- a/sahara_tests/scenario/defaults/queens/spark-2.1.0.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: spark - plugin_version: 2.1.0 - image: ${spark_210_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - master - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - slave - auto_security_group: ${use_auto_security_group} - cluster_template: - name: spark210 - node_group_templates: - master: 1 - worker: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - spark_pi - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/queens/spark-2.2.yaml.mako b/sahara_tests/scenario/defaults/queens/spark-2.2.yaml.mako deleted file mode 100644 index e0a0e38f..00000000 --- a/sahara_tests/scenario/defaults/queens/spark-2.2.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: spark - plugin_version: '2.2' - image: ${spark_22_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - master - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - slave - auto_security_group: ${use_auto_security_group} - cluster_template: - name: spark220 - node_group_templates: - master: 1 - worker: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - spark_pi - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/queens/storm-1.1.0.yaml.mako b/sahara_tests/scenario/defaults/queens/storm-1.1.0.yaml.mako deleted file mode 100644 index cff00c53..00000000 --- a/sahara_tests/scenario/defaults/queens/storm-1.1.0.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium'"/> - -clusters: - - plugin_name: storm - plugin_version: 1.1.0 - image: ${storm_110_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - nimbus - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - supervisor - auto_security_group: ${use_auto_security_group} - - name: zookeeper - flavor: ${medium_flavor_id} - node_processes: - - zookeeper - auto_security_group: ${use_auto_security_group} - cluster_template: - name: storm110 - node_group_templates: - master: 1 - worker: 1 - zookeeper: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - scenario: - - scale diff --git a/sahara_tests/scenario/defaults/queens/vanilla-2.7.1.yaml.mako b/sahara_tests/scenario/defaults/queens/vanilla-2.7.1.yaml.mako deleted file mode 100644 index 77d70baf..00000000 --- a/sahara_tests/scenario/defaults/queens/vanilla-2.7.1.yaml.mako +++ /dev/null @@ -1,85 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.1 - image: ${vanilla_271_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - name: vanilla271 - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/queens/vanilla-2.8.2.yaml.mako b/sahara_tests/scenario/defaults/queens/vanilla-2.8.2.yaml.mako deleted file mode 100644 index 34512993..00000000 --- a/sahara_tests/scenario/defaults/queens/vanilla-2.8.2.yaml.mako +++ /dev/null @@ -1,84 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', cluster_name='vanilla-282', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.8.2 - image: ${vanilla_282_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/rocky/ambari-2.3.yaml.mako b/sahara_tests/scenario/defaults/rocky/ambari-2.3.yaml.mako deleted file mode 100644 index a19de68a..00000000 --- a/sahara_tests/scenario/defaults/rocky/ambari-2.3.yaml.mako +++ /dev/null @@ -1,69 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.3' - image: ${ambari_23_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - spark_pi diff --git a/sahara_tests/scenario/defaults/rocky/ambari-2.4.yaml.mako b/sahara_tests/scenario/defaults/rocky/ambari-2.4.yaml.mako deleted file mode 100644 index a95e88e4..00000000 --- a/sahara_tests/scenario/defaults/rocky/ambari-2.4.yaml.mako +++ /dev/null @@ -1,72 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.4' - image: ${ambari_24_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - name: mapreduce_job_s3 - features: - - s3 - - spark_pi diff --git a/sahara_tests/scenario/defaults/rocky/ambari-2.5.yaml.mako b/sahara_tests/scenario/defaults/rocky/ambari-2.5.yaml.mako deleted file mode 100644 index fab951cb..00000000 --- a/sahara_tests/scenario/defaults/rocky/ambari-2.5.yaml.mako +++ /dev/null @@ -1,72 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.5' - image: ${ambari_25_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - name: mapreduce_job_s3 - features: - - s3 - - spark_pi diff --git a/sahara_tests/scenario/defaults/rocky/ambari-2.6.yaml.mako b/sahara_tests/scenario/defaults/rocky/ambari-2.6.yaml.mako deleted file mode 100644 index a06aac88..00000000 --- a/sahara_tests/scenario/defaults/rocky/ambari-2.6.yaml.mako +++ /dev/null @@ -1,72 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.6' - image: ${ambari_26_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - name: mapreduce_job_s3 - features: - - s3 - - spark_pi diff --git a/sahara_tests/scenario/defaults/rocky/cdh-5.11.0.yaml.mako b/sahara_tests/scenario/defaults/rocky/cdh-5.11.0.yaml.mako deleted file mode 100644 index 1ee7b190..00000000 --- a/sahara_tests/scenario/defaults/rocky/cdh-5.11.0.yaml.mako +++ /dev/null @@ -1,97 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.11.0 - image: ${cdh_5110_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In >=5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh5110 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - name: mapreduce_job_s3 - features: - - s3 - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/rocky/cdh-5.13.0.yaml.mako b/sahara_tests/scenario/defaults/rocky/cdh-5.13.0.yaml.mako deleted file mode 100644 index 9631ba72..00000000 --- a/sahara_tests/scenario/defaults/rocky/cdh-5.13.0.yaml.mako +++ /dev/null @@ -1,97 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.13.0 - image: ${cdh_5130_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In >=5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh5130 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - name: mapreduce_job_s3 - features: - - s3 - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/rocky/cdh-5.9.0.yaml.mako b/sahara_tests/scenario/defaults/rocky/cdh-5.9.0.yaml.mako deleted file mode 100644 index 22c40d8e..00000000 --- a/sahara_tests/scenario/defaults/rocky/cdh-5.9.0.yaml.mako +++ /dev/null @@ -1,97 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.9.0 - image: ${cdh_590_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In 5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh590 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - name: mapreduce_job_s3 - features: - - s3 - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/rocky/mapr-5.2.0.mrv2.yaml.mako b/sahara_tests/scenario/defaults/rocky/mapr-5.2.0.mrv2.yaml.mako deleted file mode 100644 index b1064d4f..00000000 --- a/sahara_tests/scenario/defaults/rocky/mapr-5.2.0.mrv2.yaml.mako +++ /dev/null @@ -1,59 +0,0 @@ -<%page args="use_auto_security_group='true', mapr_master_flavor_id='mapr.master', mapr_worker_flavor_id='mapr.worker'"/> - -clusters: - - plugin_name: mapr - plugin_version: 5.2.0.mrv2 - image: ${mapr_520mrv2_image} - node_group_templates: - - name: master - flavor: - name: ${mapr_master_flavor_id} - vcpus: 4 - ram: 8192 - root_disk: 80 - ephemeral_disk: 40 - node_processes: - - Metrics - - Webserver - - ZooKeeper - - HTTPFS - - Oozie - - FileServer - - CLDB - - Flume - - Hue - - NodeManager - - HistoryServer - - ResourceManager - - HiveServer2 - - HiveMetastore - - Sqoop2-Client - - Sqoop2-Server - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: - name: ${mapr_worker_flavor_id} - vcpus: 2 - ram: 4096 - root_disk: 40 - ephemeral_disk: 40 - node_processes: - - NodeManager - - FileServer - auto_security_group: ${use_auto_security_group} - cluster_template: - name: mapr520mrv2 - node_group_templates: - master: 1 - worker: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - mapr - - name: mapreduce_job_s3 - features: - - s3 diff --git a/sahara_tests/scenario/defaults/rocky/spark-2.2.yaml.mako b/sahara_tests/scenario/defaults/rocky/spark-2.2.yaml.mako deleted file mode 100644 index e0a0e38f..00000000 --- a/sahara_tests/scenario/defaults/rocky/spark-2.2.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: spark - plugin_version: '2.2' - image: ${spark_22_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - master - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - slave - auto_security_group: ${use_auto_security_group} - cluster_template: - name: spark220 - node_group_templates: - master: 1 - worker: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - spark_pi - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/rocky/spark-2.3.yaml.mako b/sahara_tests/scenario/defaults/rocky/spark-2.3.yaml.mako deleted file mode 100644 index 134b5732..00000000 --- a/sahara_tests/scenario/defaults/rocky/spark-2.3.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: spark - plugin_version: '2.3' - image: ${spark_22_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - master - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - slave - auto_security_group: ${use_auto_security_group} - cluster_template: - name: spark220 - node_group_templates: - master: 1 - worker: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - spark_pi - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/rocky/storm-1.1.0.yaml.mako b/sahara_tests/scenario/defaults/rocky/storm-1.1.0.yaml.mako deleted file mode 100644 index cff00c53..00000000 --- a/sahara_tests/scenario/defaults/rocky/storm-1.1.0.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium'"/> - -clusters: - - plugin_name: storm - plugin_version: 1.1.0 - image: ${storm_110_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - nimbus - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - supervisor - auto_security_group: ${use_auto_security_group} - - name: zookeeper - flavor: ${medium_flavor_id} - node_processes: - - zookeeper - auto_security_group: ${use_auto_security_group} - cluster_template: - name: storm110 - node_group_templates: - master: 1 - worker: 1 - zookeeper: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - scenario: - - scale diff --git a/sahara_tests/scenario/defaults/rocky/storm-1.2.0.yaml.mako b/sahara_tests/scenario/defaults/rocky/storm-1.2.0.yaml.mako deleted file mode 100644 index bbcd7a88..00000000 --- a/sahara_tests/scenario/defaults/rocky/storm-1.2.0.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium'"/> - -clusters: - - plugin_name: storm - plugin_version: '1.2' - image: ${storm_120_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - nimbus - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - supervisor - auto_security_group: ${use_auto_security_group} - - name: zookeeper - flavor: ${medium_flavor_id} - node_processes: - - zookeeper - auto_security_group: ${use_auto_security_group} - cluster_template: - name: storm110 - node_group_templates: - master: 1 - worker: 1 - zookeeper: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - scenario: - - scale diff --git a/sahara_tests/scenario/defaults/rocky/vanilla-2.7.1.yaml.mako b/sahara_tests/scenario/defaults/rocky/vanilla-2.7.1.yaml.mako deleted file mode 100644 index 77d70baf..00000000 --- a/sahara_tests/scenario/defaults/rocky/vanilla-2.7.1.yaml.mako +++ /dev/null @@ -1,85 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.1 - image: ${vanilla_271_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - name: vanilla271 - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/rocky/vanilla-2.7.5.yaml.mako b/sahara_tests/scenario/defaults/rocky/vanilla-2.7.5.yaml.mako deleted file mode 100644 index c2570e9f..00000000 --- a/sahara_tests/scenario/defaults/rocky/vanilla-2.7.5.yaml.mako +++ /dev/null @@ -1,84 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', cluster_name='vanilla-275', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.5 - image: ${vanilla_275_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/rocky/vanilla-2.8.2.yaml.mako b/sahara_tests/scenario/defaults/rocky/vanilla-2.8.2.yaml.mako deleted file mode 100644 index 34512993..00000000 --- a/sahara_tests/scenario/defaults/rocky/vanilla-2.8.2.yaml.mako +++ /dev/null @@ -1,84 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', cluster_name='vanilla-282', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.8.2 - image: ${vanilla_282_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/spark-2.2.yaml.mako b/sahara_tests/scenario/defaults/spark-2.2.yaml.mako deleted file mode 100644 index e0a0e38f..00000000 --- a/sahara_tests/scenario/defaults/spark-2.2.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: spark - plugin_version: '2.2' - image: ${spark_22_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - master - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - slave - auto_security_group: ${use_auto_security_group} - cluster_template: - name: spark220 - node_group_templates: - master: 1 - worker: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - spark_pi - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/spark-2.3.yaml.mako b/sahara_tests/scenario/defaults/spark-2.3.yaml.mako deleted file mode 100644 index 134b5732..00000000 --- a/sahara_tests/scenario/defaults/spark-2.3.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: spark - plugin_version: '2.3' - image: ${spark_22_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - master - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - slave - auto_security_group: ${use_auto_security_group} - cluster_template: - name: spark220 - node_group_templates: - master: 1 - worker: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - spark_pi - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/stein/ambari-2.3.yaml.mako b/sahara_tests/scenario/defaults/stein/ambari-2.3.yaml.mako deleted file mode 100644 index a19de68a..00000000 --- a/sahara_tests/scenario/defaults/stein/ambari-2.3.yaml.mako +++ /dev/null @@ -1,69 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.3' - image: ${ambari_23_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - spark_pi diff --git a/sahara_tests/scenario/defaults/stein/ambari-2.4.yaml.mako b/sahara_tests/scenario/defaults/stein/ambari-2.4.yaml.mako deleted file mode 100644 index a95e88e4..00000000 --- a/sahara_tests/scenario/defaults/stein/ambari-2.4.yaml.mako +++ /dev/null @@ -1,72 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.4' - image: ${ambari_24_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - name: mapreduce_job_s3 - features: - - s3 - - spark_pi diff --git a/sahara_tests/scenario/defaults/stein/ambari-2.5.yaml.mako b/sahara_tests/scenario/defaults/stein/ambari-2.5.yaml.mako deleted file mode 100644 index fab951cb..00000000 --- a/sahara_tests/scenario/defaults/stein/ambari-2.5.yaml.mako +++ /dev/null @@ -1,72 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.5' - image: ${ambari_25_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - name: mapreduce_job_s3 - features: - - s3 - - spark_pi diff --git a/sahara_tests/scenario/defaults/stein/ambari-2.6.yaml.mako b/sahara_tests/scenario/defaults/stein/ambari-2.6.yaml.mako deleted file mode 100644 index a06aac88..00000000 --- a/sahara_tests/scenario/defaults/stein/ambari-2.6.yaml.mako +++ /dev/null @@ -1,72 +0,0 @@ -<%page args="use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: ambari - plugin_version: '2.6' - image: ${ambari_26_image} - node_group_templates: - - name: master - flavor: ${medium_flavor_id} - node_processes: - - Ambari - - MapReduce History Server - - Spark History Server - - NameNode - - ResourceManager - - SecondaryNameNode - - YARN Timeline Server - - ZooKeeper - - Kafka Broker - auto_security_group: ${use_auto_security_group} - - name: master-edp - flavor: ${ci_flavor_id} - node_processes: - - Hive Metastore - - HiveServer - - Oozie - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - DataNode - - NodeManager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - cluster_template: - name: ambari21 - node_group_templates: - master: 1 - master-edp: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.datanode.du.reserved: 0 - custom_checks: - check_kafka: - zookeeper_process: ZooKeeper - kafka_process: Kafka Broker - spark_flow: - - type: Spark - main_lib: - type: database - source: edp-examples/edp-spark/spark-kafka-example.jar - args: - - '{zookeeper_list}' - - '{topic}' - - '{timeout}' - timeout: 30 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - java_job - - name: mapreduce_job_s3 - features: - - s3 - - spark_pi diff --git a/sahara_tests/scenario/defaults/stein/cdh-5.11.0.yaml.mako b/sahara_tests/scenario/defaults/stein/cdh-5.11.0.yaml.mako deleted file mode 100644 index 1ee7b190..00000000 --- a/sahara_tests/scenario/defaults/stein/cdh-5.11.0.yaml.mako +++ /dev/null @@ -1,97 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.11.0 - image: ${cdh_5110_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In >=5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh5110 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - name: mapreduce_job_s3 - features: - - s3 - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/stein/cdh-5.13.0.yaml.mako b/sahara_tests/scenario/defaults/stein/cdh-5.13.0.yaml.mako deleted file mode 100644 index 9631ba72..00000000 --- a/sahara_tests/scenario/defaults/stein/cdh-5.13.0.yaml.mako +++ /dev/null @@ -1,97 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.13.0 - image: ${cdh_5130_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In >=5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh5130 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - name: mapreduce_job_s3 - features: - - s3 - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/stein/cdh-5.9.0.yaml.mako b/sahara_tests/scenario/defaults/stein/cdh-5.9.0.yaml.mako deleted file mode 100644 index 22c40d8e..00000000 --- a/sahara_tests/scenario/defaults/stein/cdh-5.9.0.yaml.mako +++ /dev/null @@ -1,97 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', large_flavor_id='m1.large', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: cdh - plugin_version: 5.9.0 - image: ${cdh_590_image} - node_group_templates: - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - DATANODE: - dfs_datanode_du_reserved: 0 - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - auto_security_group: ${use_auto_security_group} - - name: worker-nm-dn - flavor: ${ci_flavor_id} - node_processes: - - YARN_NODEMANAGER - - HDFS_DATANODE - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - *ng_configs - - name: manager - flavor: ${large_flavor_id} - node_processes: - - CLOUDERA_MANAGER - - KMS - is_proxy_gateway: ${is_proxy_gateway} - auto_security_group: ${use_auto_security_group} - - name: master-core - flavor: ${large_flavor_id} - node_processes: - - HDFS_NAMENODE - - YARN_RESOURCEMANAGER - - SENTRY_SERVER - - YARN_NODEMANAGER - - ZOOKEEPER_SERVER - auto_security_group: ${use_auto_security_group} - - name: master-additional - flavor: ${large_flavor_id} - node_processes: - - OOZIE_SERVER - - YARN_JOBHISTORY - - YARN_NODEMANAGER - - HDFS_SECONDARYNAMENODE - - HIVE_METASTORE - - HIVE_SERVER2 - - SPARK_YARN_HISTORY_SERVER - auto_security_group: ${use_auto_security_group} - # In 5.9 the defaults of following configs are too large, - # restrict them to save memory for scenario testing. - node_configs: - HIVEMETASTORE: - hive_metastore_java_heapsize: 2147483648 - HIVESERVER: - hiveserver2_java_heapsize: 2147483648 - cluster_template: - name: cdh590 - node_group_templates: - manager: 1 - master-core: 1 - master-additional: 1 - worker-nm-dn: 1 - worker-nm: 1 - worker-dn: 1 - cluster_configs: - HDFS: - dfs_replication: 1 - cluster: - name: ${cluster_name} - scenario: - - run_jobs - - sentry - edp_jobs_flow: - - pig_job - - mapreduce_job - - name: mapreduce_job_s3 - features: - - s3 - - mapreduce_streaming_job - - java_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/stein/mapr-5.2.0.mrv2.yaml.mako b/sahara_tests/scenario/defaults/stein/mapr-5.2.0.mrv2.yaml.mako deleted file mode 100644 index b1064d4f..00000000 --- a/sahara_tests/scenario/defaults/stein/mapr-5.2.0.mrv2.yaml.mako +++ /dev/null @@ -1,59 +0,0 @@ -<%page args="use_auto_security_group='true', mapr_master_flavor_id='mapr.master', mapr_worker_flavor_id='mapr.worker'"/> - -clusters: - - plugin_name: mapr - plugin_version: 5.2.0.mrv2 - image: ${mapr_520mrv2_image} - node_group_templates: - - name: master - flavor: - name: ${mapr_master_flavor_id} - vcpus: 4 - ram: 8192 - root_disk: 80 - ephemeral_disk: 40 - node_processes: - - Metrics - - Webserver - - ZooKeeper - - HTTPFS - - Oozie - - FileServer - - CLDB - - Flume - - Hue - - NodeManager - - HistoryServer - - ResourceManager - - HiveServer2 - - HiveMetastore - - Sqoop2-Client - - Sqoop2-Server - auto_security_group: ${use_auto_security_group} - - name: worker - flavor: - name: ${mapr_worker_flavor_id} - vcpus: 2 - ram: 4096 - root_disk: 40 - ephemeral_disk: 40 - node_processes: - - NodeManager - - FileServer - auto_security_group: ${use_auto_security_group} - cluster_template: - name: mapr520mrv2 - node_group_templates: - master: 1 - worker: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - mapr - - name: mapreduce_job_s3 - features: - - s3 diff --git a/sahara_tests/scenario/defaults/stein/spark-2.2.yaml.mako b/sahara_tests/scenario/defaults/stein/spark-2.2.yaml.mako deleted file mode 100644 index e0a0e38f..00000000 --- a/sahara_tests/scenario/defaults/stein/spark-2.2.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: spark - plugin_version: '2.2' - image: ${spark_22_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - master - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - slave - auto_security_group: ${use_auto_security_group} - cluster_template: - name: spark220 - node_group_templates: - master: 1 - worker: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - spark_pi - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/stein/spark-2.3.yaml.mako b/sahara_tests/scenario/defaults/stein/spark-2.3.yaml.mako deleted file mode 100644 index 134b5732..00000000 --- a/sahara_tests/scenario/defaults/stein/spark-2.3.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small'"/> - -clusters: - - plugin_name: spark - plugin_version: '2.3' - image: ${spark_22_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - master - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - slave - auto_security_group: ${use_auto_security_group} - cluster_template: - name: spark220 - node_group_templates: - master: 1 - worker: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - edp_jobs_flow: - - spark_pi - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/stein/storm-1.1.0.yaml.mako b/sahara_tests/scenario/defaults/stein/storm-1.1.0.yaml.mako deleted file mode 100644 index cff00c53..00000000 --- a/sahara_tests/scenario/defaults/stein/storm-1.1.0.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium'"/> - -clusters: - - plugin_name: storm - plugin_version: 1.1.0 - image: ${storm_110_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - nimbus - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - supervisor - auto_security_group: ${use_auto_security_group} - - name: zookeeper - flavor: ${medium_flavor_id} - node_processes: - - zookeeper - auto_security_group: ${use_auto_security_group} - cluster_template: - name: storm110 - node_group_templates: - master: 1 - worker: 1 - zookeeper: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - scenario: - - scale diff --git a/sahara_tests/scenario/defaults/stein/storm-1.2.0.yaml.mako b/sahara_tests/scenario/defaults/stein/storm-1.2.0.yaml.mako deleted file mode 100644 index 1ac688fb..00000000 --- a/sahara_tests/scenario/defaults/stein/storm-1.2.0.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium'"/> - -clusters: - - plugin_name: storm - plugin_version: '1.2' - image: ${storm_120_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - nimbus - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - supervisor - auto_security_group: ${use_auto_security_group} - - name: zookeeper - flavor: ${medium_flavor_id} - node_processes: - - zookeeper - auto_security_group: ${use_auto_security_group} - cluster_template: - name: storm120 - node_group_templates: - master: 1 - worker: 1 - zookeeper: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - scenario: - - scale diff --git a/sahara_tests/scenario/defaults/stein/vanilla-2.7.1.yaml.mako b/sahara_tests/scenario/defaults/stein/vanilla-2.7.1.yaml.mako deleted file mode 100644 index 77d70baf..00000000 --- a/sahara_tests/scenario/defaults/stein/vanilla-2.7.1.yaml.mako +++ /dev/null @@ -1,85 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.1 - image: ${vanilla_271_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - name: vanilla271 - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/stein/vanilla-2.7.5.yaml.mako b/sahara_tests/scenario/defaults/stein/vanilla-2.7.5.yaml.mako deleted file mode 100644 index c2570e9f..00000000 --- a/sahara_tests/scenario/defaults/stein/vanilla-2.7.5.yaml.mako +++ /dev/null @@ -1,84 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', cluster_name='vanilla-275', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.5 - image: ${vanilla_275_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/stein/vanilla-2.8.2.yaml.mako b/sahara_tests/scenario/defaults/stein/vanilla-2.8.2.yaml.mako deleted file mode 100644 index 34512993..00000000 --- a/sahara_tests/scenario/defaults/stein/vanilla-2.8.2.yaml.mako +++ /dev/null @@ -1,84 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', cluster_name='vanilla-282', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.8.2 - image: ${vanilla_282_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/storm-1.0.1.yaml.mako b/sahara_tests/scenario/defaults/storm-1.0.1.yaml.mako deleted file mode 100644 index e17bc6e6..00000000 --- a/sahara_tests/scenario/defaults/storm-1.0.1.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium'"/> - -clusters: - - plugin_name: storm - plugin_version: 1.0.1 - image: ${storm_101_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - nimbus - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - supervisor - auto_security_group: ${use_auto_security_group} - - name: zookeeper - flavor: ${medium_flavor_id} - node_processes: - - zookeeper - auto_security_group: ${use_auto_security_group} - cluster_template: - name: storm101 - node_group_templates: - master: 1 - worker: 1 - zookeeper: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - scenario: - - scale diff --git a/sahara_tests/scenario/defaults/storm-1.1.0.yaml.mako b/sahara_tests/scenario/defaults/storm-1.1.0.yaml.mako deleted file mode 100644 index cff00c53..00000000 --- a/sahara_tests/scenario/defaults/storm-1.1.0.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium'"/> - -clusters: - - plugin_name: storm - plugin_version: 1.1.0 - image: ${storm_110_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - nimbus - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - supervisor - auto_security_group: ${use_auto_security_group} - - name: zookeeper - flavor: ${medium_flavor_id} - node_processes: - - zookeeper - auto_security_group: ${use_auto_security_group} - cluster_template: - name: storm110 - node_group_templates: - master: 1 - worker: 1 - zookeeper: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - scenario: - - scale diff --git a/sahara_tests/scenario/defaults/storm-1.2.0.yaml.mako b/sahara_tests/scenario/defaults/storm-1.2.0.yaml.mako deleted file mode 100644 index 1ac688fb..00000000 --- a/sahara_tests/scenario/defaults/storm-1.2.0.yaml.mako +++ /dev/null @@ -1,37 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', medium_flavor_id='m1.medium'"/> - -clusters: - - plugin_name: storm - plugin_version: '1.2' - image: ${storm_120_image} - node_group_templates: - - name: master - flavor: ${ci_flavor_id} - node_processes: - - nimbus - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - supervisor - auto_security_group: ${use_auto_security_group} - - name: zookeeper - flavor: ${medium_flavor_id} - node_processes: - - zookeeper - auto_security_group: ${use_auto_security_group} - cluster_template: - name: storm120 - node_group_templates: - master: 1 - worker: 1 - zookeeper: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: add - node_group: worker - size: 1 - scenario: - - scale diff --git a/sahara_tests/scenario/defaults/transient.yaml.mako b/sahara_tests/scenario/defaults/transient.yaml.mako deleted file mode 100644 index 921df8d6..00000000 --- a/sahara_tests/scenario/defaults/transient.yaml.mako +++ /dev/null @@ -1,56 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.1 - image: ${vanilla_271_image} - node_group_templates: - - name: worker - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - node_configs: - &ng_configs - MapReduce: - yarn.app.mapreduce.am.resource.mb: 256 - yarn.app.mapreduce.am.command-opts: -Xmx256m - YARN: - yarn.scheduler.minimum-allocation-mb: 256 - yarn.scheduler.maximum-allocation-mb: 1024 - yarn.nodemanager.vmem-check-enabled: false - - name: master - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - resourcemanager - - namenode - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - name: transient - node_group_templates: - master: 1 - worker: 3 - cluster_configs: - HDFS: - dfs.replication: 1 - MapReduce: - mapreduce.tasktracker.map.tasks.maximum: 16 - mapreduce.tasktracker.reduce.tasks.maximum: 16 - YARN: - yarn.resourcemanager.scheduler.class: org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler - cluster: - name: ${cluster_name} - is_transient: true - scenario: - - run_jobs - - transient - edp_jobs_flow: pig_job - diff --git a/sahara_tests/scenario/defaults/vanilla-2.7.1.yaml.mako b/sahara_tests/scenario/defaults/vanilla-2.7.1.yaml.mako deleted file mode 100644 index 747dcf09..00000000 --- a/sahara_tests/scenario/defaults/vanilla-2.7.1.yaml.mako +++ /dev/null @@ -1,84 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', cluster_name='vanilla-271', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.1 - image: ${vanilla_271_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/vanilla-2.7.5.yaml.mako b/sahara_tests/scenario/defaults/vanilla-2.7.5.yaml.mako deleted file mode 100644 index c2570e9f..00000000 --- a/sahara_tests/scenario/defaults/vanilla-2.7.5.yaml.mako +++ /dev/null @@ -1,84 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', cluster_name='vanilla-275', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.5 - image: ${vanilla_275_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/defaults/vanilla-2.8.2.yaml.mako b/sahara_tests/scenario/defaults/vanilla-2.8.2.yaml.mako deleted file mode 100644 index 34512993..00000000 --- a/sahara_tests/scenario/defaults/vanilla-2.8.2.yaml.mako +++ /dev/null @@ -1,84 +0,0 @@ -<%page args="is_proxy_gateway='true', use_auto_security_group='true', ci_flavor_id='m1.small', cluster_name='vanilla-282', availability_zone='nova', volumes_availability_zone='nova'"/> - -clusters: - - plugin_name: vanilla - plugin_version: 2.8.2 - image: ${vanilla_282_image} - node_group_templates: - - name: worker-dn-nm - flavor: ${ci_flavor_id} - node_processes: - - datanode - - nodemanager - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: worker-nm - flavor: ${ci_flavor_id} - node_processes: - - nodemanager - auto_security_group: ${use_auto_security_group} - - name: worker-dn - flavor: ${ci_flavor_id} - node_processes: - - datanode - volumes_per_node: 2 - volumes_size: 2 - availability_zone: ${availability_zone} - volumes_availability_zone: ${volumes_availability_zone} - auto_security_group: ${use_auto_security_group} - - name: master-rm-nn-hvs-sp - flavor: ${ci_flavor_id} - node_processes: - - namenode - - resourcemanager - - hiveserver - - nodemanager - - spark history server - auto_security_group: ${use_auto_security_group} - - name: master-oo-hs-sn - flavor: ${ci_flavor_id} - node_processes: - - oozie - - historyserver - - secondarynamenode - - nodemanager - auto_security_group: ${use_auto_security_group} - is_proxy_gateway: ${is_proxy_gateway} - cluster_template: - node_group_templates: - master-rm-nn-hvs-sp: 1 - master-oo-hs-sn: 1 - worker-dn-nm: 2 - worker-dn: 1 - worker-nm: 1 - cluster_configs: - HDFS: - dfs.replication: 1 - cluster: - name: ${cluster_name} - scaling: - - operation: resize - node_group: worker-dn-nm - size: 1 - - operation: resize - node_group: worker-dn - size: 0 - - operation: resize - node_group: worker-nm - size: 0 - - operation: add - node_group: worker-dn - size: 1 - - operation: add - node_group: worker-nm - size: 2 - edp_jobs_flow: - - pig_job - - mapreduce_job - - mapreduce_streaming_job - - java_job - - hive_job - - spark_wordcount diff --git a/sahara_tests/scenario/runner.py b/sahara_tests/scenario/runner.py deleted file mode 100755 index c24a912f..00000000 --- a/sahara_tests/scenario/runner.py +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import print_function -import argparse -import collections -import os -import shutil -import subprocess -import sys -import tempfile - -from oslo_utils import fileutils -import os_client_config -import pkg_resources as pkg -import six -import yaml - -from sahara_tests.scenario import utils -from sahara_tests.scenario import validation -from sahara_tests import version - - -def set_defaults(config): - # set up credentials - config['credentials'] = config.get('credentials', {}) - creds = config['credentials'] - creds.setdefault('sahara_service_type', 'data-processing') - creds['sahara_url'] = creds.get('sahara_url', None) - creds['ssl_verify'] = creds.get('ssl_verify', False) - creds['ssl_cert'] = creds.get('ssl_cert', None) - - # set up network - config['network'] = config.get('network', {}) - net = config['network'] - net['private_network'] = net.get('private_network', 'private') - net['auto_assignment_floating_ip'] = net.get('auto_assignment_floating_ip', - False) - net['public_network'] = net.get('public_network', '') - - default_scenario = ['run_jobs', 'scale', 'run_jobs'] - - # set up tests parameters - for testcase in config['clusters']: - testcase['class_name'] = "".join([ - testcase['plugin_name'], - testcase['plugin_version'].replace('.', '_')]) - testcase['retain_resources'] = testcase.get('retain_resources', False) - testcase['scenario'] = testcase.get('scenario', default_scenario) - if isinstance(testcase.get('edp_jobs_flow'), six.string_types): - testcase['edp_jobs_flow'] = [testcase['edp_jobs_flow']] - edp_jobs_flow = [] - for edp_flow in testcase.get('edp_jobs_flow', []): - edp_jobs_flow.extend(config.get('edp_jobs_flow', - {}).get(edp_flow)) - testcase['edp_jobs_flow'] = edp_jobs_flow - - -def recursive_walk(directory): - list_of_files = [] - for file in os.listdir(directory): - path = os.path.join(directory, file) - if os.path.isfile(path): - list_of_files.append(path) - else: - list_of_files += recursive_walk(path) - return list_of_files - - -def valid_count(value): - try: - ivalue = int(value) - if ivalue <= 0: - raise ValueError - except ValueError: - raise argparse.ArgumentTypeError("%s is an invalid value of count. " - "Value must be int and > 0. " % value) - return ivalue - - -def parse_args(array): - args = collections.OrderedDict() - for pair in array: - arg_dict = pair.split(':') - if len(arg_dict) < 2: - return args - args[arg_dict[0]] = "\'%s\'" % arg_dict[1] - return args - - -def get_base_parser(): - parser = argparse.ArgumentParser(description="Scenario tests runner.") - - parser.add_argument('scenario_arguments', help="Path to scenario files", - nargs='*', default=[]) - parser.add_argument('--variable_file', '-V', default='', nargs='?', - help='Path to the file with template variables') - parser.add_argument('--verbose', default=False, action='store_true', - help='Increase output verbosity') - parser.add_argument('--validate', default=False, action='store_true', - help='Validate yaml-files, tests will not be runned') - parser.add_argument('--args', default='', nargs='+', - help='Pairs of arguments key:value') - parser.add_argument('--plugin', '-p', default=None, nargs='?', - help='Specify plugin name') - parser.add_argument('--plugin_version', '-v', default=None, - nargs='?', help='Specify plugin version') - parser.add_argument('--release', '-r', default=None, - nargs='?', help='Specify Sahara release') - parser.add_argument('--report', default=False, action='store_true', - help='Write results of test to file') - parser.add_argument('--feature', '-f', default=[], - action='append', help='Set of features to enable') - parser.add_argument('--count', default=1, nargs='?', type=valid_count, - help='Specify count of runs current cases.') - parser.add_argument('--v2', '-2', default=False, action='store_true', - help='Use APIv2') - return parser - - -def get_scenario_files(scenario_arguments): - files = [] - for scenario_argument in scenario_arguments: - if os.path.isdir(scenario_argument): - files += recursive_walk(scenario_argument) - if os.path.isfile(scenario_argument): - files.append(scenario_argument) - - return files - - -def main(): - # parse args - cloud_config = os_client_config.OpenStackConfig() - parser = get_base_parser() - cloud_config.register_argparse_arguments(parser, sys.argv) - args = parser.parse_args() - - scenario_arguments = args.scenario_arguments - variable_file = args.variable_file - verbose_run = args.verbose - scenario_args = parse_args(args.args) - plugin = args.plugin - version = args.plugin_version - release = args.release - report = args.report - features = args.feature - count = args.count - use_api_v2 = args.v2 - - auth_values = utils.get_auth_values(cloud_config, args) - - scenario_arguments = utils.get_default_templates(plugin, version, release, - scenario_arguments, - features) - - files = get_scenario_files(scenario_arguments) - - template_variables = utils.get_templates_variables(files, variable_file, - verbose_run, - scenario_args, - auth_values) - - params_for_login = {'credentials': auth_values} - config = utils.generate_config(files, template_variables, params_for_login, - verbose_run, features) - - # validate config - validation.validate(config) - - if args.validate: - return - - set_defaults(config) - credentials = config['credentials'] - network = config['network'] - testcases = config['clusters'] - for case in range(count - 1): - testcases.extend(config['clusters']) - - test_dir_path = utils.create_testcase_file(testcases, credentials, network, - report, use_api_v2=use_api_v2) - - # run tests - concurrency = config.get('concurrency') - testr_runner_exit_code = utils.run_tests(concurrency, test_dir_path) - sys.exit(testr_runner_exit_code) - - -if __name__ == '__main__': - main() diff --git a/sahara_tests/scenario/stestr.conf b/sahara_tests/scenario/stestr.conf deleted file mode 100644 index d177f2b2..00000000 --- a/sahara_tests/scenario/stestr.conf +++ /dev/null @@ -1,3 +0,0 @@ -[DEFAULT] -test_path=. -group_regex=([^\.]+\.)+ diff --git a/sahara_tests/scenario/testcase.py.mako b/sahara_tests/scenario/testcase.py.mako deleted file mode 100644 index 48fc74c2..00000000 --- a/sahara_tests/scenario/testcase.py.mako +++ /dev/null @@ -1,26 +0,0 @@ -from sahara_tests.scenario import base - -% for testcase in testcases: - ${make_testcase(testcase)} -% endfor - -<%def name="make_testcase(testcase)"> -class ${testcase['class_name']}TestCase(base.BaseTestCase): - @classmethod - def setUpClass(cls): - super(${testcase['class_name']}TestCase, cls).setUpClass() - cls.credentials = ${credentials} - cls.network = ${network} - cls.testcase = ${testcase} - cls.report = ${report} - cls.results_dir = '${results_dir}' - cls.default_templ_dir = '${default_templ_dir}' - cls.use_api_v2 = ${use_api_v2} - - def test_plugin(self): - self.create_cluster() - % for check in testcase['scenario']: - from sahara_tests.scenario.custom_checks import check_${check} - check_${check}.check(self) - % endfor - diff --git a/sahara_tests/scenario/timeouts.py b/sahara_tests/scenario/timeouts.py deleted file mode 100644 index 04f8f0b3..00000000 --- a/sahara_tests/scenario/timeouts.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -class Defaults(object): - def __init__(self, config): - self.timeout_check_transient = config.get('timeout_check_transient', - 300) - self.timeout_delete_resource = config.get('timeout_delete_resource', - 300) - self.timeout_poll_cluster_status = config.get( - 'timeout_poll_cluster_status', 3600) - self.timeout_poll_jobs_status = config.get('timeout_poll_jobs_status', - 1800) - - @classmethod - def init_defaults(cls, config): - if not hasattr(cls, 'instance'): - cls.instance = Defaults(config) - return cls.instance diff --git a/sahara_tests/scenario/utils.py b/sahara_tests/scenario/utils.py deleted file mode 100644 index 69b0dc64..00000000 --- a/sahara_tests/scenario/utils.py +++ /dev/null @@ -1,325 +0,0 @@ -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import print_function -import argparse -import collections -import os -import shutil -import subprocess -import sys -import tempfile - -from mako import template as mako_template -from oslo_utils import fileutils -from oslo_utils import uuidutils -import os_client_config -import pkg_resources as pkg -import six -import yaml - -from sahara_tests.scenario import validation -from sahara_tests import version - - -SCENARIO_RESOURCES_DIR = pkg.resource_filename(version.version_info.package, - 'scenario') - -TEST_TEMPLATE_DIR = os.path.join(SCENARIO_RESOURCES_DIR, 'defaults/') -DEFAULT_TEMPLATE_VARS = [os.path.join(TEST_TEMPLATE_DIR, - 'credentials.yaml.mako'), - os.path.join(TEST_TEMPLATE_DIR, - 'edp.yaml.mako')] -TEST_TEMPLATE_PATH = os.path.join(SCENARIO_RESOURCES_DIR, - 'testcase.py.mako') -DEFAULT_STESTR_CONF = os.path.join(SCENARIO_RESOURCES_DIR, 'stestr.conf') - - -def rand_name(name=''): - rand_data = uuidutils.generate_uuid()[:8] - if name: - return '%s-%s' % (name, rand_data) - else: - return rand_data - - -def run_tests(concurrency, test_dir_path): - command = ['stestr', 'run'] - if concurrency: - command.extend(['--concurrency=%d' % concurrency]) - new_env = os.environ.copy() - # Use the same python executable which has started sahara-scenario, - # if the PYTHON value has not been set explicitly. - # This is important whenever sahara-scenario is executed in a virtualenv - # or there are multiple Python versions around. - if not new_env.get('PYTHON', ''): - new_env['PYTHON'] = sys.executable - tester_runner = subprocess.Popen(command, env=new_env, cwd=test_dir_path) - tester_runner.communicate() - return tester_runner.returncode - - -def create_testcase_file(testcases, credentials, network, report, - use_api_v2=False): - # current directory, where to write reports, key files, etc, if required - results_dir = os.getcwd() - default_templ_dir = os.path.abspath(TEST_TEMPLATE_DIR) - - # create testcase file - test_template = mako_template.Template(filename=TEST_TEMPLATE_PATH) - testcase_data = test_template.render(testcases=testcases, - credentials=credentials, - network=network, report=report, - results_dir=results_dir, - default_templ_dir=default_templ_dir, - use_api_v2=use_api_v2) - - test_dir_path = tempfile.mkdtemp() - print("The generated test file located at: %s" % test_dir_path) - fileutils.write_to_tempfile(testcase_data.encode("ASCII"), prefix='test_', - suffix='.py', path=test_dir_path) - # Copy both files as long as the old runner is supported - shutil.copyfile(DEFAULT_STESTR_CONF, os.path.join(test_dir_path, - '.stestr.conf')) - - return test_dir_path - - -def get_templates_variables(files, variable_file, verbose_run, scenario_args, - auth_values): - template_variables = {} - if any(is_template_file(config_file) for config_file in files): - template_variables = read_template_variables(variable_file, - verbose_run, - scenario_args) - template_variables.update(read_template_variables( - verbose=verbose_run, scenario_args=scenario_args, - auth_values=auth_values)) - return template_variables - - -def generate_config(files, template_variables, auth_values, verbose_run, - features_list=None): - config = {'credentials': {}, - 'network': {}, - 'clusters': [], - 'edp_jobs_flow': {}} - - for scenario_argument in files: - test_scenario = read_scenario_config(scenario_argument, - template_variables, verbose_run) - config = _merge_dicts_sections(test_scenario, config, 'credentials') - config = _merge_dicts_sections(test_scenario, config, 'network') - - if test_scenario.get('clusters') is not None: - config['clusters'] += test_scenario['clusters'] - - if test_scenario.get('edp_jobs_flow') is not None: - for key in test_scenario['edp_jobs_flow']: - if key not in config['edp_jobs_flow']: - config['edp_jobs_flow'][key] = ( - test_scenario['edp_jobs_flow'][key]) - else: - raise ValueError('Job flow exist') - config['credentials'].update(auth_values['credentials']) - - # filter out the jobs depending on the features, if any - unknown_jobs = [] - if features_list is None: - features_list = [] - for cluster in config['clusters']: - if cluster.get('edp_jobs_flow'): - filtered_jobs = [] - # The jobs associated to a cluster may be a single value (string) - # or a list of values; handle both cases. - cluster_jobs_list = cluster['edp_jobs_flow'] - if isinstance(cluster_jobs_list, six.string_types): - cluster_jobs_list = [cluster_jobs_list] - for job_item in cluster_jobs_list: - if isinstance(job_item, dict): - job = job_item['name'] - else: - job = job_item - - # get the list of features, if defined - job_features = set() - if isinstance(job_item, dict): - job_features = set(job_item.get('features', [])) - # If a job has no features associated, it is always used. - # Otherwise, it should be used only if any of its features - # matches any of the features requested, - if (not job_features or - (features_list is not None and - job_features.intersection(features_list))): - # the job is relevant for the configuration, - # so it must be defined; if it is not, it will break, - # so take note of the name - if job not in config['edp_jobs_flow']: - unknown_jobs.append(job) - continue - # job defined, it can be used - filtered_jobs.append(job) - - cluster['edp_jobs_flow'] = filtered_jobs - if unknown_jobs: - # Some jobs which are listed in some clusters - # are not defined, stop here - raise ValueError('Unknown jobs: %s' % (unknown_jobs)) - - if verbose_run: - six.print_("Generated configuration:\n%s" % ( - yaml.safe_dump(config, - allow_unicode=True, - default_flow_style=False)), - flush=True) - return config - - -def get_default_templates(plugin, version, release, scenario_arguments, - features=None): - all_templates = [] - - templates_location = TEST_TEMPLATE_DIR - if release is not None: - templates_location = os.path.join(TEST_TEMPLATE_DIR, release) - - if plugin: - if plugin in ['transient', 'fake']: - template = "%s.yaml.mako" % plugin - elif plugin and version: - template = "%s-%s.yaml.mako" % (plugin, version) - else: - raise ValueError("Please, specify version for plugin via '-v'") - - # find the templates for each features, if they exist - feature_templates_vars = [] - if features: - default_templates_base = [] - for default_template in DEFAULT_TEMPLATE_VARS: - if default_template.endswith('.yaml.mako'): - default_templates_base.append( - default_template[:-len('.yaml.mako')]) - # for each default template, look for a corresponding - # _.yaml.mako - for feature in features: - for default_template_base in default_templates_base: - template_feature = '%s_%s.yaml.mako' % ( - default_template_base, feature) - if os.path.exists(template_feature): - feature_templates_vars.append(template_feature) - - # return a combination of: default templates, version-specific - # templates, feature-based templates - all_templates = DEFAULT_TEMPLATE_VARS + [os.path.join( - templates_location, template)] + feature_templates_vars - - # it makes sense that all the other templates passed as arguments - # are always added at the end - all_templates += scenario_arguments - return all_templates - - -def get_auth_values(cloud_config, args): - try: - cloud = cloud_config.get_one_cloud(argparse=args) - cloud_credentials = cloud.get_auth_args() - api_version = cloud.config.get('identity_api_version') - except os_client_config.exceptions.OpenStackConfigException: - # cloud not found - api_version = '2.0' - cloud_credentials = {} - auth_values = { - 'os_username': cloud_credentials.get('username', 'admin'), - 'os_password': cloud_credentials.get('password', 'nova'), - 'os_auth_url': cloud_credentials.get('auth_url', - 'http://localhost:5000/v2.0'), - 'os_tenant': cloud_credentials.get('project_name', 'admin') - } - auth_url = auth_values['os_auth_url'] - if not any(v in auth_url for v in ('v2.0', 'v3')): - version = 'v3' if api_version in ('3', '3.0') else 'v2.0' - auth_values['os_auth_url'] = "%s/%s" % (auth_url, version) - return auth_values - - -def _merge_dicts_sections(dict_with_section, dict_for_merge, section): - if dict_with_section.get(section) is not None: - for key in dict_with_section[section]: - if dict_for_merge[section].get(key) is not None: - if dict_for_merge[section][key] != ( - dict_with_section[section][key]): - raise ValueError('Sections %s is different' % section) - else: - dict_for_merge[section][key] = dict_with_section[section][key] - return dict_for_merge - - -def is_template_file(config_file): - return config_file.endswith(('.yaml.mako', '.yml.mako')) - - -def read_template_variables(variable_file=None, verbose=False, - scenario_args=None, auth_values=None): - variables = {} - try: - cp = six.moves.configparser.ConfigParser() - # key-sensitive keys - if variable_file: - cp.optionxform = lambda option: option - cp.read_file(open(variable_file)) - variables = cp.defaults() - if scenario_args: - variables.update(scenario_args) - if auth_values: - variables.update(auth_values) - except IOError as ioe: - print("WARNING: the input contains at least one template, but " - "the variable configuration file '%s' is not valid: %s" % - (variable_file, ioe)) - except six.moves.configparser.Error as cpe: - print("WARNING: the input contains at least one template, but " - "the variable configuration file '%s' can not be parsed: " - "%s" % (variable_file, cpe)) - finally: - if verbose: - six.print_("Template variables:\n%s" % (variables), flush=True) - # continue anyway, as the templates could require no variables - return variables - - -def read_scenario_config(scenario_config, template_vars=None, - verbose=False): - """Parse the YAML or the YAML template file. - - If the file is a YAML template file, expand it first. - """ - - yaml_file = '' - if is_template_file(scenario_config): - scenario_template = mako_template.Template(filename=scenario_config, - strict_undefined=True) - template = scenario_template.render_unicode(**template_vars) - yaml_file = yaml.safe_load(template) - else: - with open(scenario_config, 'r') as yaml_file: - yaml_file = yaml.safe_load(yaml_file) - if verbose: - six.print_("YAML from %s:\n%s" % (scenario_config, - yaml.safe_dump( - yaml_file, - allow_unicode=True, - default_flow_style=False)), - flush=True) - return yaml_file diff --git a/sahara_tests/scenario/validation.py b/sahara_tests/scenario/validation.py deleted file mode 100644 index a7dcb361..00000000 --- a/sahara_tests/scenario/validation.py +++ /dev/null @@ -1,483 +0,0 @@ -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import re - -import jsonschema -import rfc3986 - - -SCHEMA = { - "type": "object", - "properties": { - "concurrency": { - "type": "integer", - "minimum": 1 - }, - "credentials": { - "type": "object", - "properties": { - "os_username": { - "type": "string", - "minLength": 1 - }, - "os_password": { - "type": "string", - "minLength": 1 - }, - "os_tenant": { - "type": "string", - "minLength": 1 - }, - "os_auth_url": { - "type": "string", - "format": "uri" - }, - "sahara_service_type": { - "type": "string", - "minLength": 1 - }, - "sahara_url": { - "type": "string", - "format": "uri" - }, - "ssl_verify": { - "type": "boolean" - }, - "ssl_cert": { - "type": "string", - "minLength": 1 - }, - "s3_accesskey": { - "type": "string", - }, - "s3_secretkey": { - "type": "string", - }, - "s3_endpoint": { - "type": "string", - }, - "s3_endpoint_ssl": { - "type": "boolean", - }, - "s3_bucket_path": { - "type": "boolean", - }, - }, - "additionalProperties": False - }, - "network": { - "type": "object", - "properties": { - "auto_assignment_floating_ip": { - "type": "boolean" - }, - "public_network": { - "type": "string" - }, - "private_network": { - "type": "string", - "minLength": 1 - } - }, - "additionalProperties": False - }, - "clusters": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "existing_cluster": { - "type": "string", - "minLength": 1 - }, - "key_name": { - "type": "string", - "minLength": 1 - }, - "plugin_name": { - "type": "string", - "minLength": 1 - }, - "plugin_version": { - "type": "string", - "minLength": 1 - }, - "image": { - "type": "string", - "minLength": 1 - }, - "image_username": { - "type": "string", - "minLength": 1 - }, - "hdfs_username": { - "type": "string", - "minLength": 1 - }, - "node_group_templates": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "minLength": 1, - "format": "valid_name" - }, - "node_processes": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - }, - "flavor": { - "type": ["object", "string"], - "properties": { - "name": { - "type": "string", - "minLength": 1 - }, - "id": { - "type": "string", - "minLength": 1 - }, - "vcpus": { - "type": "integer", - "minimum": 1 - }, - "ram": { - "type": "integer", - "minimum": 1 - }, - "root_disk": { - "type": "integer", - "minimum": 0 - }, - "ephemeral_disk": { - "type": "integer", - "minimum": 0 - }, - "swap_disk": { - "type": "integer", - "minimum": 0 - }, - }, - "additionalProperties": True - }, - "description": { - "type": "string" - }, - "volumes_per_node": { - "type": "integer", - "minimum": 0 - }, - "volumes_size": { - "type": "integer", - "minimum": 0 - }, - "node_configs": { - "type": "object" - }, - "security_groups": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - }, - "boot_from_volume": { - "type": "boolean" - }, - "auto_security_group": { - "type": "boolean" - }, - "availability_zone": { - "type": "string", - "minLength": 1 - }, - "volumes_availability_zone": { - "type": "string", - "minLength": 1 - }, - "volume_type": { - "type": "string", - "minLength": 1 - }, - "is_proxy_gateway": { - "type": "boolean" - } - }, - "required": ["name", "flavor", "node_processes"], - "additionalProperties": False - } - }, - "cluster_template": { - "type": "object", - "properties": { - "name": { - "type": "string", - "minLength": 1, - "format": "valid_name" - }, - "description": { - "type": "string" - }, - "cluster_configs": { - "type": "object" - }, - "node_group_templates": { - "type": "object", - "patternProperties": { - ".*": { - "type": "integer", - "minimum": 1 - } - } - }, - "anti_affinity": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - } - }, - "required": ["node_group_templates"], - "additionalProperties": False - }, - "cluster": { - "type": "object", - "properties": { - "name": { - "type": "string", - "minLength": 1, - "format": "valid_name" - }, - "description": { - "type": "string" - }, - "is_transient": { - "type": "boolean" - } - }, - "additionalProperties": False, - }, - "timeout_check_transient": { - "type": "integer", - "minimum": 1 - }, - "timeout_delete_resource": { - "type": "integer", - "minimum": 1 - }, - "timeout_poll_cluster_status": { - "type": "integer", - "minimum": 1 - }, - "timeout_poll_jobs_status": { - "type": "integer", - "minimum": 1 - }, - "custom_checks": { - "type": "object", - "properties": { - ".*": { - "type": "object", - } - } - }, - "scaling": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "operation": { - "type": "string", - "enum": ["add", "resize"] - }, - "node_group": { - "type": "string", - "minLength": 1 - }, - "size": { - "type": "integer", - "minimum": 0 - } - }, - "required": ["operation", "node_group", "size"], - "additionalProperties": False - } - }, - "scenario": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - }, - "edp_jobs_flow": { - "type": ["string", "array"], - "items": { - "type": ["string", "object"], - "properties": { - "name": { - "type": "string", - }, - "features": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - } - }, - "required": ["name", "features"], - } - }, - "retain_resources": { - "type": "boolean" - }, - "edp_batching": { - "type": "integer", - "minimum": 1 - } - }, - "required": ["plugin_name", "plugin_version", "image"], - "additionalProperties": False - } - }, - "edp_jobs_flow": { - "type": "object", - "patternProperties": { - ".*": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["Pig", "Java", "MapReduce", - "MapReduce.Streaming", "Hive", - "Spark"] - }, - "input_datasource": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["swift", "hdfs", "maprfs", - "s3"] - }, - "source": { - "type": "string" - } - }, - "required": ["type", "source"], - "additionalProperties": False - }, - "output_datasource": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["swift", "hdfs", "maprfs", - "s3"] - }, - "destination": { - "type": "string" - } - }, - "required": ["type", "destination"], - "additionalProperties": False - }, - "main_lib": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["swift", "s3", "database"] - }, - "source": { - "type": "string" - } - }, - "required": ["type", "source"], - "additionalProperties": False - }, - "additional_libs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["swift", "s3", "database"] - }, - "source": { - "type": "string" - } - }, - "required": ["type", "source"], - "additionalProperties": False - } - }, - "configs": { - "type": "object" - }, - "args": { - "type": "array" - } - }, - "required": ["type"], - "additionalProperties": False - } - } - } - } - }, - "required": ["clusters"], - "additionalProperties": False -} - - -@jsonschema.FormatChecker.cls_checks("uri") -def validate_uri_format(entry): - return rfc3986.is_valid_uri(entry) - - -@jsonschema.FormatChecker.cls_checks('valid_name') -def validate_name_hostname_format(entry): - res = re.match(r"^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]" - r"*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z]" - r"[A-Za-z0-9\-]*[A-Za-z0-9])$", entry) - return res is not None - - -class Validator(jsonschema.Draft4Validator): - def __init__(self, schema): - format_checker = jsonschema.FormatChecker() - super(Validator, self).__init__( - schema, format_checker=format_checker) - - -def validate(config): - return Validator(SCHEMA).validate(config) diff --git a/sahara_tests/unit/__init__.py b/sahara_tests/unit/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tests/unit/scenario/__init__.py b/sahara_tests/unit/scenario/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tests/unit/scenario/clouds.yaml b/sahara_tests/unit/scenario/clouds.yaml deleted file mode 100644 index 33725579..00000000 --- a/sahara_tests/unit/scenario/clouds.yaml +++ /dev/null @@ -1,7 +0,0 @@ -clouds: - scenario_test: - auth: - username: demo_cloud - password: demo_cloud_pwd - project_name: demo_cloud_project - auth_url: http://localhost:5000/v2.0 diff --git a/sahara_tests/unit/scenario/dummy.crt b/sahara_tests/unit/scenario/dummy.crt deleted file mode 100644 index c222944f..00000000 --- a/sahara_tests/unit/scenario/dummy.crt +++ /dev/null @@ -1 +0,0 @@ -# Unit tests require a file for the certificate. The content is not checked but the file must exists. diff --git a/sahara_tests/unit/scenario/templatevars_complete.ini b/sahara_tests/unit/scenario/templatevars_complete.ini deleted file mode 100644 index b669eda2..00000000 --- a/sahara_tests/unit/scenario/templatevars_complete.ini +++ /dev/null @@ -1,11 +0,0 @@ -[DEFAULT] -network_private_name: private -network_public_name: public -vanilla_26_image: centos_sahara_vanilla_hadoop_2_6_latest -vanilla_271_image: vanilla271 -spark_22_image: spark -cdh_550_image: cdh550 -cluster_name: cluster -ci_flavor_id: '2' -medium_flavor_id: '3' -large_flavor_id: '5' diff --git a/sahara_tests/unit/scenario/templatevars_incomplete.ini b/sahara_tests/unit/scenario/templatevars_incomplete.ini deleted file mode 100644 index 2d7ca444..00000000 --- a/sahara_tests/unit/scenario/templatevars_incomplete.ini +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -network_private_name: private -network_public_name: public -ci_flavor_id: '2' diff --git a/sahara_tests/unit/scenario/templatevars_nodefault.ini b/sahara_tests/unit/scenario/templatevars_nodefault.ini deleted file mode 100644 index 8719f032..00000000 --- a/sahara_tests/unit/scenario/templatevars_nodefault.ini +++ /dev/null @@ -1,4 +0,0 @@ -network_private_name: private -network_public_name: public -vanilla_26_image: centos_sahara_vanilla_hadoop_2_6_latest -ci_flavor_id: '2' diff --git a/sahara_tests/unit/scenario/test_base.py b/sahara_tests/unit/scenario/test_base.py deleted file mode 100644 index 8d6f9856..00000000 --- a/sahara_tests/unit/scenario/test_base.py +++ /dev/null @@ -1,677 +0,0 @@ -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from unittest import mock -from saharaclient.api import cluster_templates -from saharaclient.api import clusters -from saharaclient.api import data_sources -from saharaclient.api import images -from saharaclient.api import job_binaries -from saharaclient.api import job_binary_internals -from saharaclient.api import job_executions -from saharaclient.api import jobs -from saharaclient.api import node_group_templates -from saharaclient.api import plugins -from tempest.lib import exceptions as exc -import testtools - -from sahara_tests.scenario import base -from sahara_tests.scenario import timeouts - - -class FakeSaharaClient(object): - def __init__(self): - self.clusters = clusters.ClusterManager(None) - self.cluster_templates = cluster_templates.ClusterTemplateManager(None) - self.node_group_templates = (node_group_templates. - NodeGroupTemplateManager(None)) - self.plugins = plugins.PluginManager(None) - self.images = images.ImageManager(None) - self.data_sources = data_sources.DataSourceManager(None) - self.jobs = jobs.JobsManager(None) - self.job_executions = job_executions.JobExecutionsManager(None) - self.job_binaries = job_binaries.JobBinariesManager(None) - self.job_binary_internals = ( - job_binary_internals.JobBinaryInternalsManager(None)) - - -class FakeCluster(object): - def __init__(self, is_transient=False, provision_progress=[], ng=[]): - self.is_transient = is_transient - self.provision_progress = provision_progress - self.node_groups = ng - - -class FakeResponse(object): - def __init__(self, set_id=None, set_status=None, status_description=None, - node_groups=None, url=None, job_id=None, name=None, - job_type=None, verification=None): - self.id = set_id - self.status = set_status - self.status_description = status_description - self.node_groups = node_groups - self.url = url - self.job_id = job_id - self.name = name - self.type = job_type - self.verification = verification - - -class FakeFlavor(object): - def __init__(self, flavor_id=None, name=None): - self.id = flavor_id - self.name = name - - -class TestBase(testtools.TestCase): - def setUp(self): - super(TestBase, self).setUp() - with mock.patch( - 'sahara_tests.scenario.base.BaseTestCase.__init__' - ) as mock_init: - mock_init.return_value = None - self.base_scenario = base.BaseTestCase() - self.base_scenario.credentials = {'os_username': 'admin', - 'os_password': 'nova', - 'os_tenant': 'admin', - 'os_auth_url': - 'http://localhost:5000/v2.0', - 's3_accesskey': 'very_long_key', - 's3_secretkey': 'very_long_secret', - 's3_endpoint': 'https://localhost', - 'sahara_service_type': - 'data-processing-local', - 'sahara_url': - 'http://sahara_host:8386/v1.1', - 'ssl_cert': 'sahara_tests/unit/' - 'scenario/dummy.crt', - 'ssl_verify': True} - self.base_scenario.plugin_opts = {'plugin_name': 'vanilla', - 'hadoop_version': '2.7.1'} - self.base_scenario.network = {'private_network': 'changed_private', - 'public_network': 'changed_public', - 'auto_assignment_floating_ip': False} - self.base_scenario.testcase = { - 'node_group_templates': [ - { - 'name': 'master', - 'node_processes': ['namenode', 'oozie', 'resourcemanager'], - 'flavor': '2', - 'is_proxy_gateway': True - }, - { - 'name': 'worker', - 'node_processes': ['datanode', 'nodemanager'], - 'flavor': '2' - }], - 'cluster_template': { - 'name': 'test_name_ct', - 'node_group_templates': { - 'master': 1, - 'worker': 3 - } - }, - 'timeout_poll_cluster_status': 300, - 'timeout_delete_resource': 300, - 'timeout_poll_jobs_status': 2, - 'timeout_check_transient': 3, - 'retain_resources': True, - 'image': 'image_name', - 'edp_batching': 1, - "edp_jobs_flow": { - "test_flow": [ - { - "type": "Pig", - "input_datasource": { - "type": "swift", - "source": "sahara_tests/scenario/defaults/" - "edp-examples/edp-pig/" - "top-todoers/data/input" - }, - "output_datasource": { - "type": "hdfs", - "destination": "/user/hadoop/edp-output" - }, - "main_lib": { - "type": "s3", - "source": "sahara_tests/scenario/defaults/" - "edp-examples/edp-pig/" - "top-todoers/example.pig" - } - } - ] - } - } - self.base_scenario.ng_id_map = {'worker': 'set_id', 'master': 'set_id'} - self.base_scenario.ng_name_map = {} - self.base_scenario.key_name = 'test_key' - self.base_scenario.key = 'key_from_yaml' - self.base_scenario.template_path = ('sahara_tests/scenario/templates/' - 'vanilla/2.7.1') - self.job = self.base_scenario.testcase["edp_jobs_flow"].get( - 'test_flow')[0] - self.base_scenario.cluster_id = 'some_id' - self.base_scenario.proxy_ng_name = False - self.base_scenario.proxy = False - self.base_scenario.setUpClass() - timeouts.Defaults.init_defaults(self.base_scenario.testcase) - - @mock.patch('keystoneauth1.identity.v3.Password') - @mock.patch('keystoneauth1.session.Session') - @mock.patch('glanceclient.client.Client', return_value=None) - @mock.patch('saharaclient.client.Client', return_value=None) - @mock.patch('novaclient.client.Client', return_value=None) - @mock.patch('neutronclient.neutron.client.Client', return_value=None) - @mock.patch('swiftclient.client.Connection', return_value=None) - def test__init_clients(self, swift, neutron, nova, sahara, glance, - m_session, m_auth): - fake_session = mock.Mock() - fake_auth = mock.Mock() - m_session.return_value = fake_session - m_auth.return_value = fake_auth - - self.base_scenario._init_clients() - - sahara.assert_called_with('1.1', - session=fake_session, - service_type='data-processing-local', - sahara_url='http://sahara_host:8386/v1.1') - swift.assert_called_with( - auth_version='2.0', user='admin', key='nova', insecure=False, - cacert='sahara_tests/unit/scenario/dummy.crt', - tenant_name='admin', authurl='http://localhost:5000/v2.0') - - nova.assert_called_with('2', session=fake_session) - neutron.assert_called_with('2.0', session=fake_session) - glance.assert_called_with('2', session=fake_session) - - m_auth.assert_called_with(auth_url='http://localhost:5000/v3', - username='admin', - password='nova', - project_name='admin', - user_domain_name='default', - project_domain_name='default') - m_session.assert_called_with( - auth=fake_auth, - cert='sahara_tests/unit/scenario/dummy.crt', verify=True) - - @mock.patch('neutronclient.v2_0.client.Client.list_networks', - return_value={'networks': [{'id': '2314'}]}) - @mock.patch('saharaclient.api.node_group_templates.' - 'NodeGroupTemplateManager.create', - return_value=FakeResponse(set_id='id_ng')) - def test__create_node_group_template(self, mock_del, mock_saharaclient): - self.base_scenario._init_clients() - self.assertEqual({'worker': 'id_ng', 'master': 'id_ng'}, - self.base_scenario._create_node_group_templates()) - - @mock.patch('neutronclient.v2_0.client.Client.list_networks', - return_value={'networks': [{'id': '2314'}]}) - def test__create_node_group_template_bootfromvolume_apiv1(self, mock_del): - self.base_scenario._init_clients() - self.base_scenario.use_api_v2 = False - for ng in self.base_scenario.testcase['node_group_templates']: - ng['boot_from_volume'] = True - with self.assertRaisesRegex(Exception, "^boot_from_volume is.*"): - self.base_scenario._create_node_group_templates() - - @mock.patch('saharaclient.api.node_group_templates.' - 'NodeGroupTemplateManager.create', - return_value=FakeResponse(set_id='id_ng')) - @mock.patch('neutronclient.v2_0.client.Client.list_networks', - return_value={'networks': [ - {'id': '342'} - ]}) - @mock.patch('neutronclient.v2_0.client.Client.create_security_group', - return_value={'security_group': {'id': '213'}}) - @mock.patch('sahara_tests.scenario.clients.NeutronClient' - '.add_security_group_rule_for_neutron', - return_value='sg_name') - @mock.patch('sahara_tests.scenario.clients.NeutronClient' - '.delete_security_group_for_neutron', - return_value=None) - def test__create_security_group_uuid(self, mock_del, mock_add_rule, - mock_sg, mock_neutron, mock_ng): - self.base_scenario.network['public_network'] = ( - '692dcc5b-1205-4645-8a12-2558579ed17e') - self.base_scenario._init_clients() - for ng in self.base_scenario.testcase['node_group_templates']: - ng['auto_security_group'] = False - self.assertEqual({'master': 'id_ng', 'worker': 'id_ng'}, - self.base_scenario._create_node_group_templates()) - - @mock.patch('saharaclient.api.node_group_templates.' - 'NodeGroupTemplateManager.create', - return_value=FakeResponse(set_id='id_ng')) - @mock.patch('neutronclient.v2_0.client.Client.list_networks', - return_value={'networks': [ - {'id': '342'} - ]}) - @mock.patch('neutronclient.v2_0.client.Client.create_security_group', - return_value={'security_group': {'id': '213'}}) - @mock.patch('sahara_tests.scenario.clients.NeutronClient' - '.create_security_group_for_neutron', - return_value='sg_name') - @mock.patch('neutronclient.v2_0.client.Client.create_security_group_rule', - return_value=None) - @mock.patch('neutronclient.v2_0.client.Client.delete_security_group', - return_value=None) - def test__create_security_group(self, mock_del, mock_create, mock_sg, - mock_sgn, mock_list, mock_ng): - self.base_scenario._init_clients() - for ng in self.base_scenario.testcase['node_group_templates']: - ng['auto_security_group'] = False - self.assertEqual({'master': 'id_ng', 'worker': 'id_ng'}, - self.base_scenario._create_node_group_templates()) - - @mock.patch('sahara_tests.scenario.clients.NeutronClient.get_network_id', - return_value='mock_net') - @mock.patch('saharaclient.api.cluster_templates.' - 'ClusterTemplateManager.create', - return_value=FakeResponse(set_id='id_ct')) - def test__create_cluster_template(self, mock_ct, mock_neutron): - self.base_scenario._init_clients() - self.assertEqual('id_ct', - self.base_scenario._create_cluster_template()) - - @mock.patch('saharaclient.api.images.ImageManager.get', - return_value=FakeResponse(set_id='image')) - @mock.patch('sahara_tests.scenario.clients.GlanceClient.get_image_id', - return_value='mock_image') - @mock.patch('saharaclient.api.clusters.ClusterManager.create', - return_value=FakeResponse(set_id='id_cluster')) - def test__create_cluster(self, mock_cluster_manager, mock_glance, - mock_image): - self.base_scenario._init_clients() - self.assertEqual('id_cluster', - self.base_scenario._create_cluster('id_ct')) - - @mock.patch('sahara_tests.scenario.clients.NeutronClient.get_network_id', - return_value='mock_net') - @mock.patch('saharaclient.api.base.ResourceManager._get', - return_value=FakeResponse( - set_status=base.CLUSTER_STATUS_ACTIVE)) - def test__poll_cluster_status(self, mock_status, mock_neutron): - self.base_scenario._init_clients() - self.assertIsNone( - self.base_scenario._poll_cluster_status('id_cluster')) - - @mock.patch('saharaclient.api.base.ResourceManager._get') - def test_check_event_log_feature(self, mock_resp): - self.base_scenario._init_clients() - - self.assertIsNone(self.base_scenario._check_event_logs( - FakeCluster(True, []))) - self.assertIsNone(self.base_scenario._check_event_logs( - FakeCluster(False, [{'successful': True}]))) - - with testtools.ExpectedException(exc.TempestException): - self.base_scenario._check_event_logs( - FakeCluster(False, [{'successful': False}])) - - with testtools.ExpectedException(exc.TempestException): - self.base_scenario._check_event_logs( - FakeCluster(False, [{'successful': None}])) - - @mock.patch('saharaclient.api.base.ResourceManager._update', - return_value=FakeResponse(set_id='id_internal_db_data')) - def test__create_internal_db_data(self, mock_update): - self.base_scenario._init_clients() - self.assertEqual('internal-db://id_internal_db_data', - self.base_scenario._create_internal_db_data( - 'sahara_tests/unit/scenario/vanilla2_7_1.yaml')) - - @mock.patch('swiftclient.client.Connection.put_container', - return_value=None) - def test__create_swift_data(self, mock_swiftclient): - self.base_scenario._init_clients() - self.assertIn('swift://sahara-tests-', - self.base_scenario._create_swift_data()) - - @mock.patch('swiftclient.client.Connection.put_container', - return_value=None) - def test__get_swift_container(self, mock_swiftclient): - self.base_scenario._init_clients() - self.assertIn('sahara-tests-', - self.base_scenario._get_swift_container()) - - @mock.patch('saharaclient.api.base.ResourceManager._create', - return_value=FakeResponse(set_id='id_for_datasource')) - @mock.patch('swiftclient.client.Connection.put_container', - return_value=None) - @mock.patch('swiftclient.client.Connection.put_object', - return_value=None) - def test__create_datasources(self, mock_swiftcontainer, mock_swiftobject, - mock_create): - self.base_scenario._init_clients() - self.assertEqual(('id_for_datasource', 'id_for_datasource'), - self.base_scenario._create_datasources( - self.job)) - - @mock.patch('saharaclient.api.base.ResourceManager._create', - return_value=FakeResponse(set_id='id_for_job_binaries')) - @mock.patch('sahara_tests.scenario.clients.BotoClient.upload_data', - return_value={}) - @mock.patch('sahara_tests.scenario.clients.BotoClient.create_bucket', - return_value={'Location': 'foo'}) - @mock.patch('swiftclient.client.Connection.put_object', - return_value=None) - @mock.patch('swiftclient.client.Connection.put_container', - return_value=None) - def test__create_create_job_binaries(self, mock_swiftcontainer, - mock_swiftobject, - mock_create_bucket, - mock_upload_bucket_data, - mock_sahara_create): - self.base_scenario._init_clients() - self.assertEqual((['id_for_job_binaries'], []), - self.base_scenario._create_job_binaries( - self.job)) - - @mock.patch('saharaclient.api.base.ResourceManager._create', - return_value=FakeResponse(set_id='id_for_job_binary')) - @mock.patch('sahara_tests.scenario.clients.BotoClient.create_bucket', - return_value={'Location': 'foo'}) - @mock.patch('swiftclient.client.Connection.put_object', - return_value=None) - @mock.patch('swiftclient.client.Connection.put_container', - return_value=None) - @mock.patch('saharaclient.client.Client', return_value=FakeSaharaClient()) - def test__create_create_job_binary(self, mock_saharaclient, - mock_swiftcontainer, mock_swiftobject, - mock_create_bucket, mock_sahara_create): - self.base_scenario._init_clients() - self.assertEqual('id_for_job_binary', - self.base_scenario._create_job_binary(self.job.get( - 'input_datasource'))) - - @mock.patch('saharaclient.api.base.ResourceManager._create', - return_value=FakeResponse(set_id='id_for_job')) - def test__create_job(self, mock_client): - self.base_scenario._init_clients() - self.assertEqual('id_for_job', - self.base_scenario._create_job( - 'Pig', - ['id_for_job_binaries'], - [])) - - @mock.patch('sahara_tests.scenario.clients.SaharaClient.get_cluster_id', - return_value='cluster_id') - @mock.patch('sahara_tests.scenario.clients.SaharaClient.get_cluster', - return_value=FakeCluster(ng=[])) - @mock.patch('sahara_tests.scenario.base.BaseTestCase.check_cinder', - return_value=None) - @mock.patch('sahara_tests.scenario.clients.SaharaClient.get_job_status', - return_value='KILLED') - @mock.patch('saharaclient.api.base.ResourceManager._get', - return_value=FakeResponse(set_id='id_for_run_job_get', - job_type='Java', - name='test_job')) - @mock.patch('saharaclient.api.base.ResourceManager._create', - return_value=FakeResponse(set_id='id_for_run_job_create')) - @mock.patch('sahara_tests.scenario.base.BaseTestCase.' - '_poll_cluster_status', - return_value=None) - @mock.patch('sahara_tests.scenario.base.BaseTestCase.' - '_create_node_group_templates', - return_value='id_node_group_template') - @mock.patch('sahara_tests.scenario.base.BaseTestCase.' - '_create_cluster_template', - return_value='id_cluster_template') - @mock.patch('sahara_tests.scenario.base.BaseTestCase._create_cluster', - return_value='id_cluster') - @mock.patch('sahara_tests.scenario.base.BaseTestCase._create_job', - return_value='id_for_job') - @mock.patch('sahara_tests.scenario.base.BaseTestCase._create_job_binaries', - return_value=(['id_for_job_binaries'], [])) - @mock.patch('sahara_tests.scenario.base.BaseTestCase._create_datasources', - return_value=('id_for_datasource', 'id_for_datasource')) - @mock.patch('sahara_tests.scenario.base.BaseTestCase.check_verification') - def test_check_run_jobs(self, mock_verification, mock_datasources, - mock_job_binaries, mock_job, - mock_node_group_template, mock_cluster_template, - mock_cluster, mock_cluster_status, mock_create, - mock_get, mock_client, mock_cinder, mock_get_cl, - mock_get_cluster_id): - self.base_scenario._init_clients() - self.base_scenario.create_cluster() - self.base_scenario.testcase["edp_jobs_flow"] = [ - { - "type": "Pig", - "input_datasource": { - "type": "s3", - "source": "sahara_tests/scenario/defaults/edp-examples/" - "edp-pig/top-todoers/" - "data/input" - }, - "output_datasource": { - "type": "hdfs", - "destination": "/user/hadoop/edp-output" - }, - "main_lib": { - "type": "swift", - "source": "sahara_tests/scenario/defaults/edp-examples/" - "edp-pig/top-todoers/" - "example.pig" - } - } - ] - with mock.patch('time.sleep'): - self.assertIsNone(self.base_scenario.check_run_jobs()) - self.assertIn("Job with id=id_for_run_job_create, name=test_job, " - "type=Java has status KILLED", - self.base_scenario._results[-1]['traceback'][-1]) - - @mock.patch('sahara_tests.scenario.base.BaseTestCase._poll_cluster_status', - return_value=None) - @mock.patch('saharaclient.api.base.ResourceManager._get', - return_value=FakeResponse(set_id='id_scale_get')) - @mock.patch('saharaclient.api.base.ResourceManager._update', - return_value=FakeResponse(set_id='id_scale_update')) - def test_check_scale(self, mock_update, mock_get, mock_poll): - self.base_scenario._init_clients() - self.base_scenario.ng_id_map = {'vanilla-worker': 'set_id-w', - 'vanilla-master': 'set_id-m'} - self.base_scenario.ng_name_map = {'vanilla-worker': 'worker-123', - 'vanilla-master': 'master-321'} - self.base_scenario.cluster_id = 'cluster_id' - self.assertIsNone(self.base_scenario.check_scale()) - - @mock.patch('sahara_tests.scenario.clients.NeutronClient.get_network_id', - return_value='mock_net') - @mock.patch('saharaclient.api.base.ResourceManager._get', - return_value=FakeResponse(set_status='Error', - status_description="")) - def test_errormsg(self, mock_status, mock_neutron): - self.base_scenario._init_clients() - with testtools.ExpectedException(exc.TempestException): - self.base_scenario._poll_cluster_status('id_cluster') - - def test_get_nodes_with_process(self): - self.base_scenario._init_clients() - with mock.patch( - 'sahara_tests.scenario.clients.SaharaClient.get_cluster', - return_value=FakeResponse(node_groups=[ - { - 'node_processes': ['test'], - 'instances': ['test_instance'] - } - ])): - self.assertEqual( - ['test_instance'], - self.base_scenario._get_nodes_with_process('test') - ) - - with mock.patch( - 'sahara_tests.scenario.clients.SaharaClient.get_cluster', - return_value=FakeResponse(node_groups=[ - { - 'node_processes': 'test', - 'instances': [] - } - ])): - self.assertEqual( - [], self.base_scenario._get_nodes_with_process('test')) - - def test_get_node_list_with_volumes(self): - self.base_scenario._init_clients() - with mock.patch( - 'sahara_tests.scenario.clients.SaharaClient.get_cluster', - return_value=FakeResponse(node_groups=[ - { - 'node_processes': 'test', - 'volumes_per_node': 2, - 'volume_mount_prefix': 2, - 'instances': [ - { - 'management_ip': 'test_ip' - } - ] - } - ])): - self.assertEqual( - [{ - 'node_ip': 'test_ip', - 'volume_count': 2, - 'volume_mount_prefix': 2 - }], self.base_scenario._get_node_list_with_volumes()) - - @mock.patch('sahara_tests.scenario.clients.SaharaClient.get_datasource') - def test_put_io_data_to_configs(self, get_datasources): - self.base_scenario._init_clients() - get_datasources.side_effect = [ - mock.Mock(id='1', url="swift://cont/input"), - mock.Mock(id='2', url="hdfs://cont/output") - ] - configs = {'args': ['2', "{input_datasource}", - "{output_datasource}"]} - self.assertEqual({'args': ['2', 'swift://cont/input', - 'hdfs://cont/output']}, - self.base_scenario._put_io_data_to_configs( - configs, '1', '2')) - - @mock.patch('sahara_tests.scenario.base.BaseTestCase.addCleanup') - @mock.patch('novaclient.v2.flavors.FlavorManager.create', - return_value=FakeFlavor(flavor_id='created_flavor_id')) - def test_get_flavor_id_anonymous(self, mock_create_flavor, mock_base): - self.base_scenario._init_clients() - self.assertEqual('created_flavor_id', - self.base_scenario._get_flavor_id({ - "id": 'created_flavor_id', - "vcpus": 1, - "ram": 512, - "root_disk": 1, - "ephemeral_disk": 1, - "swap_disk": 1 - })) - - @mock.patch('sahara_tests.scenario.base.BaseTestCase.addCleanup') - @mock.patch('novaclient.v2.flavors.FlavorManager.create', - return_value=FakeFlavor(flavor_id='created_flavor_id')) - @mock.patch('novaclient.v2.flavors.FlavorManager.list', - return_value=[FakeFlavor(flavor_id='existing_flavor_id', - name='test-flavor')]) - def test_get_flavor_name_found(self, mock_list_flavor, mock_create_flavor, - mock_base): - self.base_scenario._init_clients() - self.assertEqual('existing_flavor_id', - self.base_scenario._get_flavor_id({ - 'name': 'test-flavor', - "id": 'created_flavor_id', - "vcpus": 1, - "ram": 512, - "root_disk": 1, - "ephemeral_disk": 1, - "swap_disk": 1 - })) - - @mock.patch('sahara_tests.scenario.base.BaseTestCase.addCleanup') - @mock.patch('novaclient.v2.flavors.FlavorManager.create', - return_value=FakeFlavor(flavor_id='created_flavor_id')) - @mock.patch('novaclient.v2.flavors.FlavorManager.list', - return_value=[FakeFlavor(flavor_id='another_flavor_id', - name='another-flavor')]) - def test_get_flavor_id_not_found(self, mock_list_flavor, - mock_create_flavor, mock_base): - self.base_scenario._init_clients() - self.assertEqual('created_flavor_id', - self.base_scenario._get_flavor_id({ - 'name': 'test-flavor', - "id": 'created_flavor_id', - "vcpus": 1, - "ram": 512, - "root_disk": 1, - "ephemeral_disk": 1, - "swap_disk": 1 - })) - - @mock.patch('sahara_tests.scenario.base.BaseTestCase._run_command_on_node') - def test_create_hdfs_data(self, mock_ssh): - self.base_scenario._init_clients() - output_path = '/user/test/data/output' - self.assertEqual(output_path, - self.base_scenario._create_dfs_data(None, output_path, - None, 'hdfs')) - input_path = ('sahara_tests/scenario/defaults/edp-examples/edp-pig/' - 'trim-spaces/data/input') - with mock.patch( - 'sahara_tests.scenario.clients.SaharaClient.get_cluster', - return_value=FakeResponse(node_groups=[ - { - 'node_processes': ['master', 'namenode'], - 'instances': [{ - 'management_ip': 'test_ip' - }] - }])): - self.assertIn('/user/test/data-', ( - self.base_scenario._create_dfs_data(input_path, None, - 'test', 'hdfs'))) - - @mock.patch('saharaclient.api.base.ResourceManager._get', - return_value=FakeResponse( - set_status=base.CLUSTER_STATUS_ACTIVE, - verification={'verification': { - 'status': 'GREEN', - 'cluster_id': 'id_cluster' - }})) - @mock.patch('saharaclient.api.clusters.ClusterManager.verification_update') - @mock.patch('sahara_tests.scenario.base.BaseTestCase.' - 'check_feature_available', return_value=True) - def test_check_verification_did_not_start(self, mock_feature, - mock_verification, - mock_get_status): - self.base_scenario._init_clients() - self.assertIsNone(self.base_scenario.check_verification('id_cluster')) - - @mock.patch('saharaclient.api.base.ResourceManager._get', - return_value=FakeResponse( - set_status=base.CLUSTER_STATUS_ACTIVE)) - @mock.patch('saharaclient.api.clusters.ClusterManager.verification_update') - @mock.patch('sahara_tests.scenario.base.BaseTestCase.' - 'check_feature_available', return_value=True) - @mock.patch('sahara_tests.scenario.base.BaseTestCase._get_health_status', - return_value='GREEN') - def test_verification_start(self, mock_status, mock_feature, - mock_verification, mock_get_status): - self.base_scenario._init_clients() - self.assertIsNone(self.base_scenario.check_verification('id_cluster')) - - @mock.patch('saharaclient.api.base.ResourceManager._get', - return_value=FakeResponse( - set_status=base.CLUSTER_STATUS_ACTIVE)) - @mock.patch('saharaclient.api.clusters.ClusterManager.verification_update') - def test_verification_skipped(self, mock_verification, mock_get_status): - self.base_scenario._init_clients() - self.assertIsNone(self.base_scenario.check_verification('id_cluster')) diff --git a/sahara_tests/unit/scenario/test_runner.py b/sahara_tests/unit/scenario/test_runner.py deleted file mode 100644 index 68da08e6..00000000 --- a/sahara_tests/unit/scenario/test_runner.py +++ /dev/null @@ -1,485 +0,0 @@ -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys - -from jsonschema import exceptions -import pkg_resources as pkg -from unittest import mock -import testtools - -from sahara_tests import version -from sahara_tests.scenario import runner - - -def _create_subprocess_communicate_mock(): - communicate_mock = mock.Mock() - communicate_mock.communicate.return_value = ["", ""] - return communicate_mock - - -class RunnerUnitTest(testtools.TestCase): - - def _isDictContainSubset(self, sub_dictionary, dictionary): - for key in sub_dictionary: - if sub_dictionary[key] != dictionary[key]: - return False - return True - - def test_set_defaults(self): - config_without_cred_net = { - "clusters": [ - { - "plugin_name": "vanilla", - "plugin_version": "2.7.1", - "image": "sahara-vanilla-2.7.1-ubuntu-14.04" - } - ] - } - - expected_default_credential = { - "credentials": { - "sahara_url": None, - "sahara_service_type": "data-processing", - "ssl_cert": None, - "ssl_verify": False - } - } - - expected_default_network = { - "network": { - "private_network": "private", - "public_network": "", - "auto_assignment_floating_ip": False - } - } - - expected_default_cluster = { - "clusters": [ - { - "image": "sahara-vanilla-2.7.1-ubuntu-14.04", - "edp_jobs_flow": [], - "class_name": "vanilla2_7_1", - "plugin_name": "vanilla", - "scenario": ['run_jobs', 'scale', 'run_jobs'], - "plugin_version": "2.7.1", - "retain_resources": False, - } - ] - } - - runner.set_defaults(config_without_cred_net) - - self.assertTrue(self._isDictContainSubset( - expected_default_credential, config_without_cred_net)) - self.assertTrue(self._isDictContainSubset( - expected_default_network, config_without_cred_net)) - self.assertTrue(self._isDictContainSubset( - expected_default_cluster, config_without_cred_net)) - - config = { - "credentials": { - "os_username": "changed_admin", - "os_auth_url": "http://127.0.0.1:5000/v2.0", - "sahara_url": "http://127.0.0.1", - "os_password": "changed_nova", - "os_tenant": "changed_admin", - "ssl_cert": "sahara_tests/unit/scenario/dummy.crt" - }, - "network": { - "private_network": "changed_private", - "public_network": "changed_public", - "auto_assignment_floating_ip": True, - }, - "clusters": [ - { - "plugin_name": "vanilla", - "plugin_version": "2.7.1", - "image": "sahara-vanilla-2.7.1-ubuntu-14.04", - "edp_jobs_flow": "test_flow", - "retain_resources": True - } - ], - "edp_jobs_flow": { - "test_flow": [ - { - "type": "Pig", - "input_datasource": { - "type": "swift", - "source": "sahara_tests/scenario/defaults/" - "edp-examples/edp-pig/top-todoers/" - "data/input" - }, - "output_datasource": { - "type": "hdfs", - "destination": "/user/hadoop/edp-output" - }, - "main_lib": { - "type": "swift", - "source": "sahara_tests/scenario/defaults/" - "edp-examples/edp-pig/top-todoers/" - "example.pig" - } - }, - { - "type": "Java", - "additional_libs": [ - { - "type": "database", - "source": 'sahara_tests/scenario/defaults/' - 'edp-examples/hadoop2/edp-java/' - 'hadoop-mapreduce-examples-' - '2.6.0.jars', - }], - "configs": "edp.java.main_class: org.apache.hadoop." - "examples.QuasiMonteCarlo", - "args": [10, 10] - }, - ], - "test_flow2": [ - { - "type": "Java", - "additional_libs": [ - { - "type": "database", - "source": - "sahara_tests/scenario/defaults/" - "edp-examples/hadoop2/edp-java/hadoop-" - "mapreduce-examples-2.6.0.jars" - }], - "configs": "edp.java.main_class: org.apache.hadoop." - "examples.QuasiMonteCarlo", - "args": [20, 20] - } - ] - } - } - - expected_credential = { - "credentials": { - "os_username": "changed_admin", - "os_auth_url": "http://127.0.0.1:5000/v2.0", - "sahara_url": "http://127.0.0.1", - "os_password": "changed_nova", - "os_tenant": "changed_admin", - "sahara_service_type": "data-processing", - "ssl_cert": "sahara_tests/unit/scenario/dummy.crt", - "ssl_verify": False - }, - } - - expected_network = { - "network": { - "private_network": "changed_private", - "public_network": "changed_public", - "auto_assignment_floating_ip": True, - } - } - - expected_cluster = { - "clusters": [ - { - "plugin_name": "vanilla", - "plugin_version": "2.7.1", - "image": "sahara-vanilla-2.7.1-ubuntu-14.04", - "retain_resources": True, - 'edp_jobs_flow': [ - { - 'main_lib': { - 'source': 'sahara_tests/scenario/defaults/' - 'edp-examples/edp-pig/' - 'top-todoers/example.pig', - 'type': 'swift' - }, - 'type': 'Pig', - 'input_datasource': { - 'source': 'sahara_tests/scenario/defaults/' - 'edp-examples/edp-pig/' - 'top-todoers/data/input', - 'type': 'swift' - }, - 'output_datasource': { - 'type': 'hdfs', - 'destination': '/user/hadoop/edp-output' - } - }, - { - 'args': [10, 10], - 'configs': 'edp.java.main_class: org.apache.' - 'hadoop.examples.QuasiMonteCarlo', - 'type': 'Java', - 'additional_libs': [ - { - 'source': 'sahara_tests/scenario/defaults/' - 'edp-examples/hadoop2/edp-java/' - 'hadoop-mapreduce-examples-' - '2.6.0.jars', - 'type': 'database' - }] - } - ], - "scenario": ['run_jobs', 'scale', 'run_jobs'], - "class_name": "vanilla2_7_1" - }], - } - - runner.set_defaults(config) - - self.assertTrue(self._isDictContainSubset( - expected_credential, config)) - self.assertTrue(self._isDictContainSubset( - expected_network, config)) - self.assertTrue(self._isDictContainSubset( - expected_cluster, config)) - - @mock.patch('sys.exit', return_value=None) - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - def test_runner_main(self, mock_sub, mock_sys): - sys.argv = ['sahara_tests/scenario/runner.py', - 'sahara_tests/unit/scenario/vanilla2_7_1.yaml'] - runner.main() - - @mock.patch('sys.exit', return_value=None) - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - def test_runner_template_missing_varfile(self, mock_sub, mock_sys): - sys.argv = ['sahara_tests/scenario/runner.py', - 'sahara_tests/unit/scenario/vanilla2_7_1.yaml.mako'] - self.assertRaises(NameError, runner.main) - - @mock.patch('sys.exit', return_value=None) - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - def test_runner_template_wrong_varfile(self, mock_sub, mock_sys): - sys.argv = ['sahara_tests/scenario/runner.py', - '-V', - 'sahara_tests/unit/scenario/templatevars_nodefault.ini', - 'sahara_tests/unit/scenario/vanilla2_7_1.yaml.mako'] - self.assertRaises(NameError, runner.main) - - @mock.patch('sys.exit', return_value=None) - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - def test_runner_template_incomplete_varfile(self, mock_sub, mock_sys): - sys.argv = ['sahara_tests/scenario/runner.py', - '-V', - 'sahara_tests/unit/scenario/templatevars_incomplete.ini', - 'sahara_tests/unit/scenario/vanilla2_7_1.yaml.mako'] - self.assertRaises(NameError, runner.main) - - @mock.patch('sys.exit', return_value=None) - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - def test_runner_template_working(self, mock_sub, mock_sys): - sys.argv = ['sahara_tests/scenario/runner.py', - '-V', - 'sahara_tests/unit/scenario/templatevars_complete.ini', - 'sahara_tests/unit/scenario/vanilla2_7_1.yaml.mako'] - runner.main() - - @mock.patch('sys.exit', return_value=None) - def test_runner_validate(self, mock_sys): - sys.argv = ['sahara_tests/scenario/runner.py', - '--validate', - '-V', - 'sahara_tests/unit/scenario/templatevars_complete.ini', - 'sahara_tests/unit/scenario/vanilla2_7_1.yaml.mako'] - runner.main() - - @mock.patch('sahara_tests.scenario.validation.validate') - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - @mock.patch('sys.exit', return_value=None) - def test_runner_from_args(self, mock_sys, mock_sub, mock_validate): - sys.argv = ['sahara_tests/scenario/runner.py', - 'sahara_tests/unit/scenario/vanilla2_7_1.yaml.mako', - '--os-username', 'demo', '--os-password', 'demopwd', - '--os-project-name', 'demo', - '--os-auth-url', 'localhost/v2.0', - '--args', - 'network_private_name:private', - 'network_public_name:public', - 'vanilla_26_image:hadoop_2_6_latest', - 'ci_flavor_id:2'] - runner.main() - expected = { - 'os_username': 'demo', - 'os_password': 'demopwd', - 'os_tenant': 'demo', - 'os_auth_url': 'localhost/v2.0' - } - self.assertTrue(self._isDictContainSubset( - expected, mock_validate.call_args_list[0][0][0]['credentials'])) - - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - @mock.patch('sys.exit', return_value=None) - def test_replace_value_args(self, mock_sys, mock_sub): - sys.argv = ['sahara_tests/scenario/runner.py', - '-V', - 'sahara_tests/unit/scenario/templatevars_complete.ini', - 'sahara_tests/unit/scenario/vanilla2_7_1.yaml.mako', - '--args', - 'network_private_name:'] - with testtools.ExpectedException(exceptions.ValidationError): - runner.main() - - @mock.patch('sahara_tests.scenario.validation.validate') - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - @mock.patch('sys.exit', return_value=None) - def test_default_templates_non_plugin(self, mock_sys, mock_sub, - mock_validate): - sys.argv = ['sahara_tests/scenario/runner.py', - '-V', - 'sahara_tests/unit/scenario/templatevars_complete.ini', - '-p', 'transient', '--os-username', 'demo', - '--os-password', 'demopwd', - '--os-project-name', 'demo', - '--os-auth-url', 'http://127.0.0.1:5000/v2'] - runner.main() - - @mock.patch('sahara_tests.scenario.validation.validate') - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - @mock.patch('sys.exit', return_value=None) - def test_default_templates_negative(self, mock_sys, mock_sub, - mock_validate): - sys.argv = ['sahara_tests/scenario/runner.py', - '-V', - 'sahara_tests/unit/scenario/templatevars_complete.ini', - '-p', 'vanilla', '--os-username', 'demo', - '--os-password', 'demopwd', - '--os-project-name', 'demo', - '--os-auth-url', 'http://127.0.0.1:5000/v2'] - with testtools.ExpectedException(ValueError): - runner.main() - - @mock.patch('sahara_tests.scenario.validation.validate') - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - @mock.patch('sys.exit', return_value=None) - def test_default_templates(self, mock_sys, mock_sub, mock_validate): - sys.argv = ['sahara_tests/scenario/runner.py', - '-V', - 'sahara_tests/unit/scenario/templatevars_complete.ini', - '-p', 'spark', '-v', '2.2', '-r', 'queens', - '--os-username', 'demo', '--os-password', 'demopwd', - '--os-project-name', 'demo', - '--os-auth-url', 'http://127.0.0.1:5000/v2'] - runner.main() - self.assertEqual('spark', - mock_validate.call_args[0][0]['clusters'][0][ - 'plugin_name']) - self.assertEqual('2.2', - mock_validate.call_args[0][0]['clusters'][0][ - 'plugin_version']) - - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - @mock.patch('sys.exit', return_value=None) - def test_count(self, mock_sys, mock_sub): - sys.argv = ['sahara_tests/scenario/runner.py', - '-V', - 'sahara_tests/unit/scenario/templatevars_complete.ini', - '-p', 'spark', '-v', '2.2', '--release', 'queens', - '--count', '4', - '--os-username', 'demo', '--os-password', 'demopwd', - '--os-project-name', 'demo', - '--os-auth-url', 'http://127.0.0.1:5000/v2'] - runner.main() - - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - @mock.patch('sys.exit', return_value=None) - def test_run_dir(self, mock_sys, mock_sub): - sys.argv = ['sahara_tests/scenario/runner.py', - '-V', - 'sahara_tests/unit/scenario/templatevars_complete.ini', - 'sahara_tests/scenario/defaults/queens', - 'sahara_tests/scenario/defaults/edp.yaml.mako', - '--os-username', 'demo', '--os-password', 'demopwd', - '--os-project-name', 'demo', - '--os-auth-url', 'http://127.0.0.1:5000/v2', '--args', - 'ambari_24_image:ambari', 'fake_plugin_image:fake', - 'mapr_520mrv2_image:mapr', 'cdh_570_image:cdh', - 'cdh_590_image:cdh', 'cdh_5110_image:cdh', - 'spark_210_image:spark', 'spark_22_image:spark', - 'storm_101_image:storm', 'storm_110_image:storm', - 'vanilla_282_image:vanilla'] - runner.main() - - @mock.patch('sahara_tests.scenario.validation.validate') - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - @mock.patch('sys.exit', return_value=None) - def test_credentials_envvars(self, mock_sys, mock_sub, mock_validate): - username = os.environ.get('OS_USERNAME', '') - password = os.environ.get('OS_PASSWORD', '') - auth_url = os.environ.get('OS_AUTHURL', '') - project_name = os.environ.get('OS_PROJECT_NAME', '') - os.environ['OS_USERNAME'] = 'demo_env' - os.environ['OS_PASSWORD'] = 'demopwd_env' - os.environ['OS_AUTH_URL'] = 'http://localhost:5000/v2.0' - os.environ['OS_PROJECT_NAME'] = 'project_env' - sys.argv = ['sahara_tests/scenario/runner.py', - '-V', - 'sahara_tests/unit/scenario/templatevars_complete.ini', - '-p', 'spark', '-v', '1.3.1', '--release', 'liberty', - '--count', '4'] - runner.main() - expected = { - 'os_username': 'demo_env', - 'os_password': 'demopwd_env', - 'os_tenant': 'project_env', - 'os_auth_url': 'http://localhost:5000/v2.0' - } - self.assertTrue(self._isDictContainSubset( - expected, mock_validate.call_args_list[0][0][0]['credentials'])) - - os.environ['OS_USERNAME'] = username - os.environ['OS_PASSWORD'] = password - os.environ['OS_AUTHURL'] = auth_url - os.environ['OS_PROJECT_NAME'] = project_name - - @mock.patch('sahara_tests.scenario.validation.validate') - @mock.patch('subprocess.Popen', - return_value=_create_subprocess_communicate_mock()) - @mock.patch('sys.exit', return_value=None) - def test_credentials_clouds(self, mock_sys, mock_sub, mock_validate): - unit_dir = pkg.resource_filename(version.version_info.package, - 'unit') - os_client_config_file = os.environ.get('OS_CLIENT_CONFIG_FILE', '') - os.environ['OS_CLIENT_CONFIG_FILE'] = os.path.join(unit_dir, - 'scenario', - 'clouds.yaml') - sys.argv = ['sahara_tests/scenario/runner.py', - '-V', - 'sahara_tests/unit/scenario/templatevars_complete.ini', - '-p', 'spark', '-v', '1.0.0', '--count', '4', - '--os-cloud', 'scenario_test'] - runner.main() - expected = { - 'os_username': 'demo_cloud', - 'os_password': 'demo_cloud_pwd', - 'os_tenant': 'demo_cloud_project', - 'os_auth_url': 'http://localhost:5000/v2.0' - } - self.assertTrue(self._isDictContainSubset( - expected, mock_validate.call_args_list[0][0][0]['credentials'])) - os.environ['OS_CLIENT_CONFIG_FILE'] = os_client_config_file - diff --git a/sahara_tests/unit/scenario/test_utils.py b/sahara_tests/unit/scenario/test_utils.py deleted file mode 100644 index 8368964e..00000000 --- a/sahara_tests/unit/scenario/test_utils.py +++ /dev/null @@ -1,215 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -from unittest import mock -import testtools - -from sahara_tests.scenario import utils - - -class UtilsGenerateConfigTest(testtools.TestCase): - - def setUp(self): - super(UtilsGenerateConfigTest, self).setUp() - self._cluster_config = { - "clusters": [{ - "edp_jobs_flow": [ - "pig_all", - { - "name": "pig_feat2", - "features": ["feat2"] - }, - { - "name": "pig_featmulti", - "features": ["feat1", "feat2"] - }, - ] - }], - "edp_jobs_flow": { - "pig_all": [ - { - "type": "Pig", - "input_datasource": { - "type": "swift", - "source": "sahara_tests/scenario/defaults/" - "edp-examples/edp-pig/" - "top-todoers/data/input" - }, - "output_datasource": { - "type": "hdfs", - "destination": "/user/hadoop/edp-output" - }, - "main_lib": { - "type": "swift", - "source": "sahara_tests/scenario/defaults/" - "edp-examples/edp-pig/" - "top-todoers/example.pig" - } - } - ], - "pig_feat2": [ - { - "type": "Pig", - "input_datasource": { - "type": "swift", - "source": "sahara_tests/scenario/defaults/" - "edp-examples/edp-pig/" - "top-todoers/data/input" - }, - "output_datasource": { - "type": "hdfs", - "destination": "/user/hadoop/edp-output" - }, - "main_lib": { - "type": "swift", - "source": "sahara_tests/scenario/defaults/" - "edp-examples/edp-pig/" - "top-todoers/example.pig" - } - } - ], - "pig_featmulti": [ - { - "type": "Pig", - "input_datasource": { - "type": "swift", - "source": "sahara_tests/scenario/defaults/" - "edp-examples/edp-pig/" - "top-todoers/data/input" - }, - "output_datasource": { - "type": "hdfs", - "destination": "/user/hadoop/edp-output" - }, - "main_lib": { - "type": "swift", - "source": "sahara_tests/scenario/defaults/" - "edp-examples/edp-pig/" - "top-todoers/example.pig" - } - } - ] - } - } - - @mock.patch('sahara_tests.scenario.utils.read_scenario_config') - def test_generate_config_feature(self, m_readscenarioconfig): - """Check the generate_config method when features are specified.""" - m_readscenarioconfig.return_value = self._cluster_config - # "template_variables" can be empty because read_scenario_config, - # which is its only users, is a mock variable. - # "files" is used to loop over the read keys, so at one fake - # file name is needed. - result = utils.generate_config(['dummyfile.yaml'], None, - {'credentials': {}}, False, - features_list=['feat1']) - self.assertIn('clusters', result) - self.assertEqual(set(result['clusters'][0]['edp_jobs_flow']), - set(["pig_all", "pig_featmulti"])) - - @mock.patch('sahara_tests.scenario.utils.read_scenario_config') - def test_generate_config_nofeatures(self, m_readscenarioconfig): - """Check the generate_config method when features are specified.""" - m_readscenarioconfig.return_value = self._cluster_config - # "template_variables" can be empty because read_scenario_config, - # which is its only users, is a mock variable. - # "files" is used to loop over the read keys, so at one fake - # file name is needed. - result = utils.generate_config(['dummyfile.yaml'], None, - {'credentials': {}}, False, - features_list=[]) - self.assertIn('clusters', result) - self.assertEqual(set(result['clusters'][0]['edp_jobs_flow']), - set(["pig_all"])) - - @mock.patch('sahara_tests.scenario.utils.read_scenario_config') - def test_generate_config_singlejob_str(self, m_readscenarioconfig): - """Check the generate_config method when the cluster runs only - a single job defined as string and not as list.""" - cluster_config_singlejob_str = self._cluster_config - cluster_config_singlejob_str['clusters'][0]["edp_jobs_flow"] = \ - 'pig_all' - m_readscenarioconfig.return_value = cluster_config_singlejob_str - # "template_variables" can be empty because read_scenario_config, - # which is its only users, is a mock variable. - # "files" is used to loop over the read keys, so at one fake - # file name is needed. - result = utils.generate_config(['dummyfile.yaml'], None, - {'credentials': {}}, False, - features_list=[]) - self.assertIn('clusters', result) - self.assertEqual(set(result['clusters'][0]['edp_jobs_flow']), - set(["pig_all"])) - - @mock.patch('sahara_tests.scenario.utils.read_scenario_config') - def test_generate_config_unknownjob(self, m_readscenarioconfig): - """Check the generate_config method when an unknown job is specified, - thus leading to an exception.""" - cluster_config_unknownjob = self._cluster_config - cluster_config_unknownjob['clusters'][0]["edp_jobs_flow"].append( - 'unknown_job') - m_readscenarioconfig.return_value = cluster_config_unknownjob - # "template_variables" can be empty because read_scenario_config, - # which is its only users, is a mock variable. - # "files" is used to loop over the read keys, so at one fake - # file name is needed. - self.assertRaises(ValueError, utils.generate_config, - ['dummyfile.yaml'], None, {'credentials': {}}, - False, features_list=[]) - - -class UtilsTemplatesTest(testtools.TestCase): - - def test_get_default_templates_noversion_nofeatures(self): - """Check the list of automatically discovered templates - when the plugin does not require versions and - there are no features specified.""" - found_templates = utils.get_default_templates('fake', None, None, - ['conffile.yaml']) - expected_templates = ( - utils.DEFAULT_TEMPLATE_VARS + - [os.path.join(utils.TEST_TEMPLATE_DIR, 'fake.yaml.mako'), - 'conffile.yaml'] - ) - self.assertListEqual(found_templates, expected_templates) - - def test_get_default_templates_missingversion(self): - """Check the list of automatically discovered templates - when the plugin requires a version but it was not specified.""" - self.assertRaises(ValueError, utils.get_default_templates, - 'vanilla', None, None, ['conffile.yaml']) - - @mock.patch('os.path.exists', side_effect=[True, False, True, True]) - def test_get_default_templates_version_release_features(self, m_exist): - """Check the list of automatically discovered templates - when the plugin requires a version, a release is specified and - there are features specified.""" - found_templates = utils.get_default_templates('vanilla', '2.7.1', - 'rocky', - ['conffile.yaml'], - ['feat1', 'feat2']) - expected_templates = ( - utils.DEFAULT_TEMPLATE_VARS + - [os.path.join(utils.TEST_TEMPLATE_DIR, 'rocky', - 'vanilla-2.7.1.yaml.mako'), - os.path.join(utils.TEST_TEMPLATE_DIR, - 'credentials_feat1.yaml.mako'), - os.path.join(utils.TEST_TEMPLATE_DIR, - 'credentials_feat2.yaml.mako'), - os.path.join(utils.TEST_TEMPLATE_DIR, - 'edp_feat2.yaml.mako'), - 'conffile.yaml'] - ) - self.assertListEqual(found_templates, expected_templates) diff --git a/sahara_tests/unit/scenario/test_validation.py b/sahara_tests/unit/scenario/test_validation.py deleted file mode 100644 index 89afbd9e..00000000 --- a/sahara_tests/unit/scenario/test_validation.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import testtools -import yaml - -from sahara_tests.scenario import validation - - -class TestValidation(testtools.TestCase): - def test_validation(self): - with open("sahara_tests/unit/scenario/vanilla2_7_1.yaml", - "r") as yaml_file: - config = yaml.safe_load(yaml_file) - self.assertIsNone(validation.validate(config)) diff --git a/sahara_tests/unit/scenario/vanilla2_7_1.yaml b/sahara_tests/unit/scenario/vanilla2_7_1.yaml deleted file mode 100644 index 17f683e4..00000000 --- a/sahara_tests/unit/scenario/vanilla2_7_1.yaml +++ /dev/null @@ -1,111 +0,0 @@ -concurrency: 1 - -credentials: - ssl_cert: sahara_tests/unit/scenario/dummy.crt - ssl_verify: True - -network: - private_network: private - public_network: public - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.1 - image: sahara-vanilla-2.7.1-ubuntu-14.04 - image_username: ubuntu - hdfs_username: hadoop - node_group_templates: - - name: master - node_processes: - - namenode - - resourcemanager - - hiveserver - - oozie - - historyserver - - secondarynamenode - flavor: '2' - - name: worker - node_processes: - - datanode - - nodemanager - flavor: - name: test-flavor - id: test-id - vcpus: 1 - ram: 512 - root_disk: 1 - cluster_template: - name: vanilla - node_group_templates: - master: 1 - worker: 3 - anti_affinity: - - namenode - - datanode - scenario: - - run_jobs - - scale - - run_jobs - edp_jobs_flow: - - test_flow - - name: test_flow_hive - features: - - testhive - edp_batching: 1 - retain_resources: true - -edp_jobs_flow: - test_flow: - - type: Pig - input_datasource: - type: swift - source: edp-examples/edp-pig/top-todoers/data/input - output_datasource: - type: hdfs - destination: /user/hadoop/edp-output - main_lib: - type: swift - source: edp-examples/edp-pig/top-todoers/example.pig - - type: Java - additional_libs: - - type: database - source: edp-examples/hadoop2/edp-java/hadoop-mapreduce-examples-2.7.1.jar - configs: - edp.java.main_class: org.apache.hadoop.examples.QuasiMonteCarlo - args: - - 10 - - 10 - - type: MapReduce - configs: - mapred.mapper.class: org.apache.oozie.example.SampleMapper - mapred.reducer.class: org.apache.oozie.example.SampleReducer - additional_libs: - - type: database - source: edp-examples/edp-java/edp-java.jar - input_datasource: - type: swift - source: edp-examples/edp-pig/top-todoers/data/input - output_datasource: - type: hdfs - destination: /user/hadoop/edp-output - - type: MapReduce.Streaming - configs: - edp.streaming.mapper: /bin/cat - edp.streaming.reducer: /usr/bin/wc - input_datasource: - type: swift - source: edp-examples/edp-pig/top-todoers/data/input - output_datasource: - type: hdfs - destination: /user/hadoop/edp-output - test_flow_hive: - - type: Hive - input_datasource: - type: swift - source: edp-examples/edp-hive/input.csv - output_datasource: - type: hdfs - destination: /user/hadoop/edp-hive/ - main_lib: - type: swift - source: edp-examples/edp-hive/script.q diff --git a/sahara_tests/unit/scenario/vanilla2_7_1.yaml.mako b/sahara_tests/unit/scenario/vanilla2_7_1.yaml.mako deleted file mode 100644 index aa99523b..00000000 --- a/sahara_tests/unit/scenario/vanilla2_7_1.yaml.mako +++ /dev/null @@ -1,119 +0,0 @@ -concurrency: 1 - -credentials: - ssl_cert: sahara_tests/unit/scenario/dummy.crt - ssl_verify: True - -network: - private_network: ${network_private_name} - public_network: ${network_public_name} - -clusters: - - plugin_name: vanilla - plugin_version: 2.7.1 - image: ${vanilla_26_image} - image_username: ubuntu - hdfs_username: hadoop - node_group_templates: - - name: master - node_processes: - - namenode - - resourcemanager - - hiveserver - - oozie - - historyserver - - secondarynamenode - flavor: ${ci_flavor_id} - - name: worker - node_processes: - - datanode - - nodemanager - flavor: - name: test-flavor - id: test-id - vcpus: 1 - ram: 512 - root_disk: 1 - cluster_template: - name: vanilla - node_group_templates: - master: 1 - worker: 3 - scenario: - - run_jobs - - scale - - run_jobs - edp_jobs_flow: test_flow - edp_batching: 1 - retain_resources: true - -edp_jobs_flow: - test_flow: - - type: Pig - input_datasource: - type: swift - source: edp-examples/edp-pig/top-todoers/data/input - output_datasource: - type: hdfs - destination: /user/hadoop/edp-output - main_lib: - type: swift - source: edp-examples/edp-pig/top-todoers/example.pig - - type: Java - additional_libs: - - type: database - source: edp-examples/hadoop2/edp-java/hadoop-mapreduce-examples-2.7.1.jar - configs: - edp.java.main_class: org.apache.hadoop.examples.QuasiMonteCarlo - args: - - 10 - - 10 - - type: MapReduce - configs: - mapred.mapper.class: org.apache.oozie.example.SampleMapper - mapred.reducer.class: org.apache.oozie.example.SampleReducer - additional_libs: - - type: database - source: edp-examples/edp-java/edp-java.jar - input_datasource: - type: swift - source: edp-examples/edp-pig/top-todoers/data/input - output_datasource: - type: hdfs - destination: /user/hadoop/edp-output - - type: MapReduce.Streaming - configs: - edp.streaming.mapper: /bin/cat - edp.streaming.reducer: /usr/bin/wc - input_datasource: - type: swift - source: edp-examples/edp-pig/top-todoers/data/input - output_datasource: - type: hdfs - destination: /user/hadoop/edp-output - - type: Hive - input_datasource: - type: swift - source: edp-examples/edp-hive/input.csv - output_datasource: - type: hdfs - destination: /user/hadoop/edp-hive/ - main_lib: - type: swift - source: edp-examples/edp-hive/script.q - - type: MapReduce - configs: - mapred.mapper.class: org.apache.oozie.example.SampleMapper - mapred.reducer.class: org.apache.oozie.example.SampleReducer - additional_libs: - - type: database - source: edp-examples/edp-java/edp-java.jar - input_datasource: - type: swift - source: edp-examples/edp-pig/top-todoers/data/input - output_datasource: - type: hdfs - destination: /user/hadoop/edp-output - args: - - {input_datasource} - - {output_datasource} diff --git a/sahara_tests/unit/utils/__init__.py b/sahara_tests/unit/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tests/unit/utils/test_url.py b/sahara_tests/unit/utils/test_url.py deleted file mode 100644 index 840f7ca2..00000000 --- a/sahara_tests/unit/utils/test_url.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2018 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import testtools - -from sahara_tests.utils import url as utils_url - - -class UrlUnitTest(testtools.TestCase): - - def _check_clean_url(self, url, expected_clean_url): - """Check if the cleaned URL matches the expected one.""" - clean_url = utils_url.url_schema_remover(url) - self.assertEqual(clean_url, expected_clean_url) - - def test_clean_url_http(self): - self._check_clean_url('https://s3.amazonaws.com', - 's3.amazonaws.com') - - def test_clean_url_https_longer(self): - self._check_clean_url('https://s3.amazonaws.com/foo', - 's3.amazonaws.com/foo') - - def test_clean_url_file(self): - self._check_clean_url('file:///s3.amazonaws.com/bar', - '/s3.amazonaws.com/bar') diff --git a/sahara_tests/utils/__init__.py b/sahara_tests/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/sahara_tests/utils/crypto.py b/sahara_tests/utils/crypto.py deleted file mode 100644 index 878e63fd..00000000 --- a/sahara_tests/utils/crypto.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (c) 2013 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -from oslo_concurrency import processutils -import paramiko -import six -from tempest.lib import exceptions as ex - -from sahara_tests.utils import tempfiles - - -def to_paramiko_private_key(pkey): - """Convert private key (str) to paramiko-specific RSAKey object.""" - return paramiko.RSAKey(file_obj=six.StringIO(pkey)) - - -def generate_key_pair(key_length=2048): - """Create RSA key pair with specified number of bits in key. - - Returns tuple of private and public keys. - """ - with tempfiles.tempdir() as tmpdir: - keyfile = os.path.join(tmpdir, 'tempkey') - # The key is generated in the old PEM format, instead of the native - # format of OpenSSH >=6.5, because paramiko does not support it: - # https://github.com/paramiko/paramiko/issues/602 - args = [ - 'ssh-keygen', - '-q', # quiet - '-N', '', # w/o passphrase - '-m', 'PEM', # old PEM format - '-t', 'rsa', # create key of rsa type - '-f', keyfile, # filename of the key file - '-C', 'Generated-by-Sahara' # key comment - ] - if key_length is not None: - args.extend(['-b', key_length]) - processutils.execute(*args) - if not os.path.exists(keyfile): - raise ex.TempestException("Private key file hasn't been created") - with open(keyfile) as keyfile_fd: - private_key = keyfile_fd.read() - public_key_path = keyfile + '.pub' - if not os.path.exists(public_key_path): - raise ex.TempestException("Public key file hasn't been created") - with open(public_key_path) as public_key_path_fd: - public_key = public_key_path_fd.read() - - return private_key, public_key diff --git a/sahara_tests/utils/tempfiles.py b/sahara_tests/utils/tempfiles.py deleted file mode 100644 index 054901d4..00000000 --- a/sahara_tests/utils/tempfiles.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2013 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import contextlib -import shutil -from tempest.lib import exceptions as ex -import tempfile - - -@contextlib.contextmanager -def tempdir(**kwargs): - argdict = kwargs.copy() - if 'dir' not in argdict: - argdict['dir'] = '/tmp/' - tmpdir = tempfile.mkdtemp(**argdict) - try: - yield tmpdir - finally: - try: - shutil.rmtree(tmpdir) - except OSError as e: - raise ex.TempestException( - _("Failed to delete temp dir %(dir)s (reason: %(reason)s)") % - {'dir': tmpdir, 'reason': e}) diff --git a/sahara_tests/utils/url.py b/sahara_tests/utils/url.py deleted file mode 100644 index dbda2533..00000000 --- a/sahara_tests/utils/url.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2018 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from six.moves.urllib import parse as urlparse - - -def url_schema_remover(url): - """ Return the same URL without the schema. - Example: prefix://host/path -> host/path - """ - parsed = urlparse.urlsplit(url) - cleaned = urlparse.urlunsplit((('',) + parsed[1:])) - if cleaned.startswith('//'): - cleaned = cleaned[2:] - return cleaned diff --git a/sahara_tests/version.py b/sahara_tests/version.py deleted file mode 100644 index b9d277f0..00000000 --- a/sahara_tests/version.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2016 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from pbr import version - -version_info = version.VersionInfo('sahara_tests') diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 255a4db5..00000000 --- a/setup.cfg +++ /dev/null @@ -1,44 +0,0 @@ -[metadata] -name = sahara-tests -summary = Sahara tests -description_file = README.rst -license = Apache Software License -python_requires = >=3.6 -classifiers = - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Environment :: OpenStack - Intended Audience :: Information Technology - Intended Audience :: System Administrators - License :: OSI Approved :: Apache Software License - Operating System :: POSIX :: Linux -author = OpenStack -author_email = openstack-discuss@lists.openstack.org -home_page = https://docs.openstack.org/sahara-tests/latest/ - -[global] -setup-hooks = pbr.hooks.setup_hook - -[files] -packages = - sahara_tests - sahara_tempest_plugin - -data_files = - etc/sahara-scenario = etc/* - -[entry_points] -console_scripts = - sahara-scenario = sahara_tests.scenario.runner:main - -tempest.test_plugins = - sahara_tempest_tests = sahara_tempest_plugin.plugin:SaharaTempestPlugin - -[build_sphinx] -all_files = 1 -build-dir = doc/build -source-dir = doc/source -warning-is-error = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index 9a33e945..00000000 --- a/setup.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT -import setuptools - -setuptools.setup( - setup_requires=['pbr>=1.8'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index b590895e..00000000 --- a/test-requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -hacking>=3.0.1,<3.1.0 # Apache-2.0 - -bandit!=1.6.0 -bashate>=0.2 # Apache-2.0 -coverage>=3.6 # Apache-2.0 -doc8 # Apache-2.0 -pylint==1.4.5 # GNU GPL v2 diff --git a/tools/cover.sh b/tools/cover.sh deleted file mode 100755 index c658fc7a..00000000 --- a/tools/cover.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash -# -# Copyright 2015: Mirantis Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -ALLOWED_EXTRA_MISSING=4 - -show_diff () { - head -1 $1 - diff -U 0 $1 $2 | sed 1,2d -} - -package_name=${PACKAGE_NAME:-sahara_tests} -export PYTHON="coverage run --source ${package_name} --parallel-mode" - -run_coverage () { - find . -type f -name "*.pyc" -delete && coverage erase && \ - stestr run "$*" && coverage combine -} - -# Stash uncommitted changes, checkout master and save coverage report -uncommitted=$(git status --porcelain | grep -v "^??") -[[ -n $uncommitted ]] && git stash > /dev/null -git checkout HEAD^ - -baseline_report=$(mktemp -t sahara-scenario_coverageXXXXXXX) -run_coverage "$*" -coverage report > $baseline_report -baseline_missing=$(awk '/^TOTAL/ { print $3 }' $baseline_report) - -# Checkout back and unstash uncommitted changes (if any) -git checkout - -[[ -n $uncommitted ]] && git stash pop > /dev/null - -# Generate and save coverage report -current_report=$(mktemp -t sahara-scenario_coverageXXXXXXX) -run_coverage "$*" -coverage report > $current_report -current_missing=$(awk '/^TOTAL/ { print $3 }' $current_report) - -coverage html -d cover -coverage xml -o cover/coverage.xml - -# Show coverage details -allowed_missing=$((baseline_missing+ALLOWED_EXTRA_MISSING)) - -echo "Allowed to introduce missing lines : ${ALLOWED_EXTRA_MISSING}" -echo "Missing lines in master : ${baseline_missing}" -echo "Missing lines in proposed change : ${current_missing}" - -if [ -z "$current_missing" ]; then - echo "No coverage found!" - exit_code=1 -elif [ $allowed_missing -gt $current_missing ]; -then - if [ $baseline_missing -lt $current_missing ]; - then - show_diff $baseline_report $current_report - echo "I believe you can cover all your code with 100% coverage!" - else - echo "Thank you! You are awesome! Keep writing unit tests! :)" - fi - exit_code=0 -else - show_diff $baseline_report $current_report - echo "Please write more unit tests, we should keep our test coverage :( " - exit_code=1 -fi - -rm $baseline_report $current_report - -exit $exit_code diff --git a/tools/gate/cli_tests/README.rst b/tools/gate/cli_tests/README.rst deleted file mode 100644 index e1842768..00000000 --- a/tools/gate/cli_tests/README.rst +++ /dev/null @@ -1,3 +0,0 @@ -========================================== -Scripts and conf for sahara cli tests gate -========================================== diff --git a/tools/gate/cli_tests/commons b/tools/gate/cli_tests/commons deleted file mode 100644 index cadb52f7..00000000 --- a/tools/gate/cli_tests/commons +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -source settings - -export DEST=${DEST:-$BASE/new} -export DEVSTACK_DIR=${DEVSTACK_DIR:-$DEST/devstack} -export SAHARA_TESTS_DIR=${SAHARA_TESTS_DIR:-$DEST/sahara-tests} - -export LOCALCONF_PATH=$DEVSTACK_DIR/local.conf diff --git a/tools/gate/cli_tests/pre_test_hook.sh b/tools/gate/cli_tests/pre_test_hook.sh deleted file mode 100755 index 8cbd44d5..00000000 --- a/tools/gate/cli_tests/pre_test_hook.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -set -ex - -source commons $@ - -echo "[[local|localrc]]" >> $LOCALCONF_PATH -echo "IMAGE_URLS=$SAHARA_FAKE_PLUGIN_IMAGE" >> $LOCALCONF_PATH diff --git a/tools/gate/cli_tests/settings b/tools/gate/cli_tests/settings deleted file mode 100644 index 73352677..00000000 --- a/tools/gate/cli_tests/settings +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -export SAHARA_FAKE_PLUGIN_IMAGE=https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img -export SAHARA_FAKE_PLUGIN_IMAGE_NAME=$(basename $SAHARA_FAKE_PLUGIN_IMAGE .img) -export SAHARA_FAKE_PLUGIN_IMAGE_USERNAME=ubuntu -export SAHARA_FLAVOR_NAME=sahara-flavor -export SAHARA_FLAVOR_ID=20 -export SAHARA_FLAVOR_RAM=512 -export SAHARA_FLAVOR_DISK=10 -export SAHARA_FLAVOR_VCPUS=1 diff --git a/tools/gate/scenario/README.rst b/tools/gate/scenario/README.rst deleted file mode 100644 index 56746a5a..00000000 --- a/tools/gate/scenario/README.rst +++ /dev/null @@ -1,3 +0,0 @@ -======================================== -Scripts and conf for scenario tests gate -======================================== diff --git a/tools/gate/scenario/commons b/tools/gate/scenario/commons deleted file mode 100644 index 7ef8b3d9..00000000 --- a/tools/gate/scenario/commons +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -export PLUGIN=${2:-fake} -export IS_TRANSIENT=true -export AUTO_SECURITY_GROUP=true - -export DEST=${DEST:-$BASE/new} -export DEVSTACK_DIR=${DEVSTACK_DIR:-$DEST/devstack} -export SAHARA_DIR=${SAHARA_DIR:-$DEST/sahara} -export SAHARA_TESTS_DIR=${SAHARA_TESTS_DIR:-$DEST/sahara-tests} -export SAHARA_IMAGE_ELEMENTS_DIR=${SAHARA_IMAGE_ELEMENTS_DIR:-$DEST/sahara-image-elements} - -source settings - -export LOCALCONF_PATH=$DEVSTACK_DIR/local.conf - -function sahara_build_image { - cd $SAHARA_IMAGE_ELEMENTS_DIR - - sudo -E chown -R $USER:stack $SAHARA_IMAGE_ELEMENTS_DIR - - ./tools/gate/build-images $PLUGIN -} - -function sahara_register_image { - openstack dataprocessing image register --username $SAHARA_IMAGE_USERNAME \ - $SAHARA_IMAGE_NAME - openstack dataprocessing image tags add $SAHARA_IMAGE_NAME --tags \ - $SAHARA_PLUGIN_VERSION $SAHARA_PLUGIN_NAME -} - -function sahara_register_flavor { - nova flavor-create $SAHARA_FLAVOR_NAME $SAHARA_FLAVOR_ID $SAHARA_FLAVOR_RAM \ - $SAHARA_FLAVOR_DISK $SAHARA_FLAVOR_VCPUS -} diff --git a/tools/gate/scenario/dsvm-scenario-rc b/tools/gate/scenario/dsvm-scenario-rc deleted file mode 100755 index feeb5ad2..00000000 --- a/tools/gate/scenario/dsvm-scenario-rc +++ /dev/null @@ -1,12 +0,0 @@ -export DEVSTACK_LOCAL_CONFIG="enable_plugin sahara https://opendev.org/openstack/sahara" -export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin ceilometer https://opendev.org/openstack/ceilometer" -export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin heat https://opendev.org/openstack/heat" -export DEVSTACK_GATE_TEMPEST=0 -export DEVSTACK_GATE_INSTALL_TESTONLY=1 -export KEEP_LOCALRC=1 -export PROJECTS="openstack/sahara-tests $PROJECTS" - -if [ $SAHARA_SCENARIO_GATE_PROJECT == "python-saharaclient" ] ; then - export PROJECTS="openstack/python-saharaclient $PROJECTS" - export DEVSTACK_PROJECT_FROM_GIT=python-saharaclient -fi diff --git a/tools/gate/scenario/post_test_hook.sh b/tools/gate/scenario/post_test_hook.sh deleted file mode 100755 index f2a4d0fb..00000000 --- a/tools/gate/scenario/post_test_hook.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This script is executed inside post_test_hook function in devstack gate. - -set -ex - -source commons $@ - -set +x -source $DEVSTACK_DIR/stackrc -source $DEVSTACK_DIR/openrc admin admin -set -x - -# Make public and register in Sahara as admin -sahara_register_image - -# Register sahara specific flavor for gate -sahara_register_flavor - -sudo -E chown -R $USER:stack $SAHARA_TESTS_DIR -cd $SAHARA_TESTS_DIR - -echo "Generating scenario tests config file" -sudo -E -u $USER tee template_vars.ini <> $LOCALCONF_PATH -echo "IMAGE_URLS=$SAHARA_IMAGE" >> $LOCALCONF_PATH - -# Here we can set some configurations for local.conf -# for example, to pass some config options directly to sahara.conf file -# echo -e '[[post-config|$SAHARA_CONF]]\n[DEFAULT]\n' >> $LOCALCONF_PATH -# echo -e 'infrastructure_engine=true\n' >> $LOCALCONF_PATH - -echo -e '[[post-config|$SAHARA_CONF_FILE]]\n[DEFAULT]\n' >> $LOCALCONF_PATH - -echo -e 'min_transient_cluster_active_time=90\n' >> $LOCALCONF_PATH diff --git a/tools/gate/scenario/settings b/tools/gate/scenario/settings deleted file mode 100644 index 4834af4e..00000000 --- a/tools/gate/scenario/settings +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2015 Mirantis Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -#TODO(slukjanov): replace with special image for fake plugin (cloud ubuntu?) -if [ $PLUGIN == fake ] ; then - export SAHARA_IMAGE=https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img - export SAHARA_IMAGE_NAME=$(basename $SAHARA_IMAGE .img) - export SAHARA_IMAGE_USERNAME=ubuntu - export SAHARA_PLUGIN_NAME=fake - export SAHARA_PLUGIN_VERSION=0.1 - export SAHARA_SCENARIO_TEMPLATE=fake.yaml.mako - export SAHARA_FLAVOR_NAME=sahara-flavor - export SAHARA_FLAVOR_ID=20 - export SAHARA_FLAVOR_RAM=512 - export SAHARA_FLAVOR_DISK=10 - export SAHARA_FLAVOR_VCPUS=1 -else - export SAHARA_IMAGE=file://$SAHARA_IMAGE_ELEMENTS_DIR/ubuntu_sahara_spark_latest.qcow2 - export SAHARA_IMAGE_NAME=$(basename $SAHARA_IMAGE .qcow2) - export SAHARA_IMAGE_USERNAME=ubuntu - export SAHARA_PLUGIN_NAME=spark - export SAHARA_PLUGIN_VERSION=1.6.0 - export SAHARA_SCENARIO_TEMPLATE=spark-1.6.0.yaml.mako - export SAHARA_FLAVOR_NAME=sahara-flavor - export SAHARA_FLAVOR_ID=20 - export SAHARA_FLAVOR_RAM=1536 - export SAHARA_FLAVOR_DISK=20 - export SAHARA_FLAVOR_VCPUS=1 -fi diff --git a/tools/lintstack.py b/tools/lintstack.py deleted file mode 100755 index 0c7f6252..00000000 --- a/tools/lintstack.py +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2012, AT&T Labs, Yun Mao -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -# Stolen from OpenStack Nova - -"""pylint error checking.""" - -import json -import re -import sys - -from pylint import lint -from pylint.reporters import text -from six.moves import cStringIO as StringIO - -# Note(maoy): E1103 is error code related to partial type inference -ignore_codes = ["E1103"] -# Note(maoy): the error message is the pattern of E0202. It should be ignored -# for sahara.tests modules - -KNOWN_PYLINT_EXCEPTIONS_FILE = "tools/pylint_exceptions" - - -class LintOutput(object): - - _cached_filename = None - _cached_content = None - - def __init__(self, filename, lineno, line_content, code, message, - lintoutput): - self.filename = filename - self.lineno = lineno - self.line_content = line_content - self.code = code - self.message = message - self.lintoutput = lintoutput - - @classmethod - def from_line(cls, line): - m = re.search(r"(\S+):(\d+): \[(\S+)(, \S*)?] (.*)", line) - matched = m.groups() - filename, lineno, code, message = (matched[0], int(matched[1]), - matched[2], matched[-1]) - if cls._cached_filename != filename: - with open(filename) as f: - cls._cached_content = list(f.readlines()) - cls._cached_filename = filename - line_content = cls._cached_content[lineno - 1].rstrip() - return cls(filename, lineno, line_content, code, message, - line.rstrip()) - - @classmethod - def from_msg_to_dict(cls, msg): - """From the output of pylint msg, to a dict, where each key - is a unique error identifier, value is a list of LintOutput - """ - result = {} - for line in msg.splitlines(): - if line.startswith('*****'): - continue - obj = cls.from_line(line) - if obj.is_ignored(): - continue - key = obj.key() - if key not in result: - result[key] = [] - result[key].append(obj) - return result - - def is_ignored(self): - if self.code in ignore_codes: - return True - return False - - def key(self): - if self.code in ["E1101", "E1103"]: - # These two types of errors are like Foo class has no member bar. - # We discard the source code so that the error will be ignored - # next time another Foo.bar is encountered. - return self.message, "" - return self.message, self.line_content.strip() - - def json(self): - return json.dumps(self.__dict__) - - def review_str(self): - return ("File %(filename)s\nLine %(lineno)d:%(line_content)s\n" - "%(code)s: %(message)s" % self.__dict__) - - -class ErrorKeys(object): - - @classmethod - def print_json(cls, errors, output=sys.stdout): - print("# automatically generated by tools/lintstack.py", file=output) - for i in sorted(errors.keys()): - print(json.dumps(i), output) - - @classmethod - def from_file(cls, filename): - keys = set() - for line in open(filename): - if line and line[0] != "#": - d = json.loads(line) - keys.add(tuple(d)) - return keys - - -def run_pylint(): - buff = StringIO() - reporter = text.ParseableTextReporter(output=buff) - args = ["--include-ids=y", "-E", "sahara"] - lint.Run(args, reporter=reporter, exit=False) - val = buff.getvalue() - buff.close() - return val - - -def generate_error_keys(msg=None): - print("Generating", KNOWN_PYLINT_EXCEPTIONS_FILE) - if msg is None: - msg = run_pylint() - errors = LintOutput.from_msg_to_dict(msg) - with open(KNOWN_PYLINT_EXCEPTIONS_FILE, "w") as f: - ErrorKeys.print_json(errors, output=f) - - -def validate(newmsg=None): - print("Loading", KNOWN_PYLINT_EXCEPTIONS_FILE) - known = ErrorKeys.from_file(KNOWN_PYLINT_EXCEPTIONS_FILE) - if newmsg is None: - print("Running pylint. Be patient...") - newmsg = run_pylint() - errors = LintOutput.from_msg_to_dict(newmsg) - - print("Unique errors reported by pylint: was %d, now %d." - % (len(known), len(errors))) - passed = True - for err_key, err_list in errors.items(): - for err in err_list: - if err_key not in known: - print(err.lintoutput, '\n') - passed = False - if passed: - print("Congrats! pylint check passed.") - redundant = known - set(errors.keys()) - if redundant: - print("Extra credit: some known pylint exceptions disappeared.") - for i in sorted(redundant): - print(json.dumps(i)) - print("Consider regenerating the exception file if you will.") - else: - print("Please fix the errors above. If you believe they are false" - " positives, run 'tools/lintstack.py generate' to overwrite.") - sys.exit(1) - - -def usage(): - print("""Usage: tools/lintstack.py [generate|validate] - To generate pylint_exceptions file: tools/lintstack.py generate - To validate the current commit: tools/lintstack.py - """) - - -def main(): - option = "validate" - if len(sys.argv) > 1: - option = sys.argv[1] - if option == "generate": - generate_error_keys() - elif option == "validate": - validate() - else: - usage() - - -if __name__ == "__main__": - main() diff --git a/tools/lintstack.sh b/tools/lintstack.sh deleted file mode 100755 index f2464b06..00000000 --- a/tools/lintstack.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2012-2013, AT&T Labs, Yun Mao -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -# Stolen from OpenStack Nova - -# Use lintstack.py to compare pylint errors. -# We run pylint twice, once on HEAD, once on the code before the latest -# commit for review. -set -e -TOOLS_DIR=$(cd $(dirname "$0") && pwd) -# Get the current branch name. -GITHEAD=`git rev-parse --abbrev-ref HEAD` -if [[ "$GITHEAD" == "HEAD" ]]; then - # In detached head mode, get revision number instead - GITHEAD=`git rev-parse HEAD` - echo "Currently we are at commit $GITHEAD" -else - echo "Currently we are at branch $GITHEAD" -fi - -cp -f $TOOLS_DIR/lintstack.py $TOOLS_DIR/lintstack.head.py - -if git rev-parse HEAD^2 2>/dev/null; then - # The HEAD is a Merge commit. Here, the patch to review is - # HEAD^2, the master branch is at HEAD^1, and the patch was - # written based on HEAD^2~1. - PREV_COMMIT=`git rev-parse HEAD^2~1` - git checkout HEAD~1 - # The git merge is necessary for reviews with a series of patches. - # If not, this is a no-op so won't hurt either. - git merge $PREV_COMMIT -else - # The HEAD is not a merge commit. This won't happen on gerrit. - # Most likely you are running against your own patch locally. - # We assume the patch to examine is HEAD, and we compare it against - # HEAD~1 - git checkout HEAD~1 -fi - -# First generate tools/pylint_exceptions from HEAD~1 -$TOOLS_DIR/lintstack.head.py generate -# Then use that as a reference to compare against HEAD -git checkout $GITHEAD -$TOOLS_DIR/lintstack.head.py -echo "Check passed. FYI: the pylint exceptions are:" -cat $TOOLS_DIR/pylint_exceptions - diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 21b94e98..00000000 --- a/tox.ini +++ /dev/null @@ -1,91 +0,0 @@ -[tox] -minversion = 3.18.0 -envlist = docs,py3 -skipsdist = True -# this allows tox to infer the base python from the environment name -# and override any basepython configured in this file -ignore_basepython_conflict = true - -[testenv] -basepython = python3 -usedevelop = True -install_command = pip install -U {opts} {packages} -setenv = - VIRTUAL_ENV={envdir} -deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} - -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - Mako>=0.4.0,<1.3.0 -commands = stestr run {posargs} -passenv = - http_proxy - https_proxy - no_proxy - -[testenv:venv] -usedevelop = False -basepython = python3 -commands = {posargs} -passenv = OS_* - -[testenv:cover] -setenv = - PACKAGE_NAME=sahara_tests -commands = {toxinidir}/tools/cover.sh {posargs} -allowlist_externals = - bash - -[testenv:docs] -deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} - -r{toxinidir}/doc/requirements.txt -commands = - rm -rf doc/build - sphinx-build -W -b html doc/source doc/build/html -allowlist_externals = - rm - -[tox:jenkins] -downloadcache = ~/cache/pip - -[testenv:pylint] -setenv = VIRTUAL_ENV={envdir} -commands = bash tools/lintstack.sh -allowlist_externals = - bash - -[testenv:pep8] -deps = - -r{toxinidir}/requirements.txt - -r{toxinidir}/doc/requirements.txt - -r{toxinidir}/test-requirements.txt -commands = - flake8 {posargs} - doc8 doc/source - # Run bashate checks - # E011 is intentionally ignored because it does not make sense; it is - # perfectly reasonable to put 'then' on a separate line when natural - # line breaks occur in long conditionals. - bash -c "find tools -iname '*.sh' -print0 | xargs -0 bashate -v --ignore E011" -whitelist_externals = bash -allowlist_externals = - bash - -[testenv:releasenotes] -deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} - -r{toxinidir}/doc/requirements.txt -commands = - rm -rf releasenotes/build - sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html -whitelist_externals = - rm -allowlist_externals = - rm - -[flake8] -show-source = true -builtins = _ -exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools -select = E