Add testrail_upload_suite tool
Change-Id: Id3055c83225d0dcd69015658f05bd9ce07e8d88d
Related-prod: PRODX-942
diff --git a/testrail_upload_suites/README.md b/testrail_upload_suites/README.md
new file mode 100644
index 0000000..12b6619
--- /dev/null
+++ b/testrail_upload_suites/README.md
@@ -0,0 +1,78 @@
+# testrail_upload_suites
+## How to Description:
+
+1. Clone the repository `testrail_upload_suites`
+
+2. Install python 3.6 virtualenv:
+
+ ~~~~
+ apt install python3.6-venv
+ ~~~~
+
+3. Navigate to the `testrail_upload_suites/` folder:
+ ~~~
+ cd testrail_upload_suites/
+ ~~~
+
+4. Create virtualenv and activate this:
+
+ ~~~~
+ virtualenv --python=python3.6 .venv
+ . .venv/bin/activate
+ ~~~~
+
+5. In the `testrail_upload_suites` folder install requirements:
+
+ ~~~~
+ pip3 install -r requirements.txt
+ ~~~~
+
+6. In the environment set bash variables
+ ```bash
+ export TESTRAIL_URL='https://mirantis.testrail.com'
+ export TESTRAIL_PROJECT='Mirantis Cloud Platform'
+ export TESTRAIL_USER='you testrail username, for example sturivnyi@mirantis.com'
+ export TESTRAIL_PASSWORD='you super secret password'
+ export TESTRAIL_MILESTONE='MCP1.1 for example'
+ export TESTRAIL_SUITE='A new suite where tests will be uploaded'
+ ```
+
+7. Edit `SECTION_MAP` in the `config.py` file:
+ Comment sections that you do not want to be uploaded to the Testrail Suites.
+ There is `# "Barbican": ["barbican_tempest_plugin."]` section is commented.
+ ```python
+ SECTIONS_MAP = {
+ "Telemetry": ["telemetry_tempest_plugin."],
+ "Glance": ["image."],
+ "Keystone": ["identity."],
+ "Neutron": ["network."],
+ "Nova": ["compute."],
+ "Swift": ["object_storage."],
+ "Scenario": ["tempest.scenario."],
+ "Manila": ["manila_tempest_tests."],
+ "Ironic": ["ironic_tempest_plugin."],
+ "Heat": ["heat_tempest_plugin."],
+ "Designate": ["designate_tempest_plugin."],
+ # "Barbican": ["barbican_tempest_plugin."],
+ "Horizon": ["tempest_horizon."]
+ }
+ ```
+
+8. Prepare file with the tests for upload.
+ For example, `tests_for_upload.txt` the file with the tests that go one by one.
+
+ ```
+ ...
+ tempest.api.compute.servers.test_server_actions.ServerActionsTestJSON.test_reboot_server_hard
+ tempest.api.compute.servers.test_create_server.ServersTestJSON.test_list_servers
+ tempest.api.compute.servers.test_create_server.ServersTestJSON.test_verify_server_details
+ tempest.api.compute.test_versions.TestVersions.test_get_version_details
+ tempest.api.compute.test_versions.TestVersions.test_list_api_versions
+ ...
+ ```
+
+9. Upload thests to the testrail. In this example the command will be:
+
+ ~~~
+ python upload_suite.py tests_for_upload.txt
+ ~~~
diff --git a/testrail_upload_suites/base.py b/testrail_upload_suites/base.py
new file mode 100644
index 0000000..c82c9b4
--- /dev/null
+++ b/testrail_upload_suites/base.py
@@ -0,0 +1,221 @@
+from testrail import *
+import config
+
+
+class Base:
+ def __init__(self):
+ self.client = APIClient(config.URL)
+ self.client.user = config.USER
+ self.client.password = config.PASSWORD
+ self.project = self._get_project(config.PROJECT)
+
+ def _get_project(self, project_name):
+ projects_uri = 'get_projects'
+ projects = self.client.send_get(uri=projects_uri)
+ for project in projects:
+ if project['name'] == project_name:
+ return project
+ return None
+
+ def send_post_add_result(self, some_id, bug, status_id, add_result):
+ add_result['status_id'] = status_id
+ add_result['custom_launchpad_bug'] = bug
+ send_add_result = 'add_result/' + str(some_id)
+ return self.client.send_post(send_add_result, add_result)
+
+ def get_plans(self, project_id): # !
+ return self.client.send_get('get_plans/{0}'.format(project_id))
+
+ def get_plan(self, plan_id): # !
+ return self.client.send_get('get_plan/{0}'.format(plan_id))
+
+ def is_test_plan_exist(self, test_plan_name):
+ runs = self.get_plans(self.project['id'])
+ if True in map(lambda item: item['name'] == test_plan_name, runs):
+ return True
+ return False
+
+ def get_tests(self, plan_id): # !
+ return self.client.send_get('get_tests/{0}'.format(plan_id))
+
+ def get_test_runs(self, plan_id, pattern=None):
+ plans_runs = self.get_plan(plan_id) # !get_plans
+ runs = []
+ for run in plans_runs['entries']:
+ if pattern:
+ if pattern in run['name']:
+ runs.append(run)
+ else:
+ runs.append(run)
+ return runs
+
+ def get_tempest_runs(self, plan_id):
+ runs = self.get_plan(plan_id) # !get_plans
+ tempest_runs = []
+ for run in runs['entries']:
+ if 'Tempest' in run['name']:
+ tempest_runs.append(run)
+ return tempest_runs
+
+ def get_id_of_failed_tests(self, run_id): # !
+ all_tests = self.get_tests(run_id)
+ test_ids = []
+ for test in all_tests:
+ if test['status_id'] == 5:
+ test_ids.append(test['id'])
+ return test_ids
+
+ def get_test_result(self, test_id):
+ return self.client.send_get('get_results/{0}'.format(test_id))
+
+ def get_test_results_for_run(self, run_id):
+ return self.client.send_get('get_results_for_run/{0}'.format(run_id))
+
+ def get_results_for_case(self, run_id, case_id):
+ return self.client.send_get('get_results_for_case/{0}/{1}'.
+ format(run_id, case_id))
+
+ def get_test(self, test_id):
+ return self.client.send_get('get_test/{0}'.format(test_id))
+
+ def get_runs(self, run_id):
+ return self.client.send_get('get_runs/{0}'.format(run_id))
+
+ def get_run(self, run_id):
+ return self.client.send_get('get_run/{0}'.format(run_id))
+
+ def get_milestones(self):
+ milestones_uri = 'get_milestones/{project_id}'.format(
+ project_id=self.project['id'])
+ return self.client.send_get(uri=milestones_uri)
+
+ def get_milestone(self, milestone_id):
+ milestone_uri = 'get_milestone/{milestone_id}'.format(
+ milestone_id=milestone_id)
+ return self.client.send_get(uri=milestone_uri)
+
+ def get_milestone_by_name(self, name):
+ for milestone in self.get_milestones():
+ if milestone['name'] == name:
+ return self.get_milestone(milestone_id=milestone['id'])
+
+ def add_plan(self, name, description, milestone_id, entries):
+ add_plan_uri = 'add_plan/{project_id}'.format(
+ project_id=self.project['id'])
+ new_plan = {
+ 'name': name,
+ 'description': description,
+ 'milestone_id': milestone_id,
+ 'entries': entries # entries=[]
+ }
+ return self.client.send_post(add_plan_uri, new_plan)
+
+ def add_plan_entry(self, project_id, new_run):
+ add_plan_uri = 'add_plan_entry/{project_id}'.format(
+ project_id=project_id)
+ return self.client.send_post(add_plan_uri, new_run)
+
+ def get_suites(self):
+ suites_uri = 'get_suites/{project_id}'.format(
+ project_id=self.project['id'])
+ return self.client.send_get(uri=suites_uri)
+
+ def get_suite(self, suite_id):
+ suite_uri = 'get_suite/{suite_id}'.format(suite_id=suite_id)
+ return self.client.send_get(uri=suite_uri)
+
+ def get_suite_by_name(self, name):
+ for suite in self.get_suites():
+ if suite['name'] == name:
+ return self.get_suite(suite_id=suite['id'])
+
+ def get_plan_by_name(self, name):
+ for plan in self.get_plans(13):
+ if plan['name'] == name:
+ return self.get_plan(plan['id'])
+
+ def add_result(self, test_id, result_to_add):
+ return self.client.send_post('add_result/{0}'.format(test_id['id']),
+ result_to_add)
+
+ def add_suite(self, name, description=None):
+ return self.client.send_post('add_suite/' + str(self.project['id']),
+ dict(name=name, description=description))
+
+ def get_sections(self, suite_id):
+ sections_uri = 'get_sections/{project_id}&suite_id={suite_id}'.format(
+ project_id=self.project['id'],
+ suite_id=suite_id
+ )
+ return self.client.send_get(sections_uri)
+
+ def get_section(self, section_id):
+ section_uri = 'get_section/{section_id}'.format(section_id=section_id)
+ return self.client.send_get(section_uri)
+
+ def get_section_by_name(self, suite_id, section_name):
+ for section in self.get_sections(suite_id=suite_id):
+ if section['name'] == section_name:
+ return self.get_section(section_id=section['id'])
+
+ def add_section(self, suite_id, name, parent_id=None):
+ return self.client.send_post('add_section/' + str(self.project['id']),
+ dict(suite_id=suite_id, name=name,
+ parent_id=parent_id))
+
+ def delete_section(self, section_id):
+ # Not working bug in testrail
+ section = self.get_section(section_id)
+ print('SECTION', section)
+ try:
+ deleted = self.client.send_post('delete_section/{}'.format(section_id), section)
+ print('DELETED', deleted)
+ except Exception:
+ pass
+ return
+
+ def add_case(self, section_id, case):
+ add_case_uri = 'add_case/{section_id}'.format(section_id=section_id)
+ return self.client.send_post(add_case_uri, case)
+
+ @staticmethod
+ def prepare_common_results(tests, status_id):
+ results = {"results": []}
+
+ for test in tests:
+ results["results"].append({
+ "test_id": test['id'],
+ "status_id": status_id,
+ "comment": 'Deploy failed',
+ })
+ return results
+
+ @staticmethod
+ def get_result_by_name():
+ result = config.RESULT
+ if result == 'Blocked':
+ return 2
+ elif result == 'Passed':
+ return 1
+ elif result == 'Failed':
+ return 5
+ elif result == 'ProdFailed':
+ return 8
+ elif result == 'Skipped':
+ return 6
+
+ @staticmethod
+ def get_id_of_tempest_runs(tempest_runs):
+ tempest_runs_ids = {} # []
+ for i in tempest_runs:
+ for item in i['runs']:
+ tempest_runs_ids.update({item['id']: item['name']})
+ return tempest_runs_ids
+
+ @staticmethod
+ def get_last_tempest_run(get_plans):
+ for plans in get_plans:
+ # print dict
+ if (plans.get(u'passed_count') > 1000 or plans.get(
+ u'blocked_count') > 1000)and '9.1' in plans.get(u'name'):
+ return plans.get(u'id')
diff --git a/testrail_upload_suites/config.py b/testrail_upload_suites/config.py
new file mode 100644
index 0000000..be5fdb4
--- /dev/null
+++ b/testrail_upload_suites/config.py
@@ -0,0 +1,44 @@
+import os
+
+URL = os.environ.get('TESTRAIL_URL')
+USER = os.environ.get('TESTRAIL_USER')
+PROJECT = os.environ.get('TESTRAIL_PROJECT')
+PASSWORD = os.environ.get('TESTRAIL_PASSWORD')
+
+MILESTONE = os.environ.get('TESTRAIL_MILESTONE')
+SUITE = os.environ.get('TESTRAIL_SUITE')
+PLAN_NAME = os.environ.get('TESTRAIL_PLAN_NAME')
+RESULT = os.environ.get('TESTRAIL_RESULT')
+
+
+# Use test IDs for titles of TestRail test cases like
+# 'tempest.api.identity.admin.v2.test_rolesRolesTestJSON.test_list_roles[id-
+# 75d9593f-50b7-4fcf-bd64-e3fb4a278e23]' instead of test names.
+USE_TEST_IDs = True
+
+TEST_CASE_TYPE_ID = 1 # Automated
+TEST_CASE_PRIORITY_ID = 4 # P0
+QA_TEAM = 4 # MOS
+DELETE_OLD_SECTIONS = False # User should have proper permissions to do it
+UPLOAD_THREADS_COUNT = 4
+
+SECTIONS_MAP = {
+ "Telemetry": ["telemetry_tempest_plugin."],
+ "Glance": ["image."],
+ "Keystone": ["identity."],
+ "Neutron": ["network."],
+ "Nova": ["compute."],
+ "Swift": ["object_storage."],
+ "Scenario": ["tempest.scenario."],
+ "Manila": ["manila_tempest_tests."],
+ "Ironic": ["ironic_tempest_plugin."],
+ "Heat": ["heat_tempest_plugin."],
+ "Designate": ["designate_tempest_plugin."],
+ "Barbican": ["barbican_tempest_plugin."],
+ "Horizon": ["tempest_horizon."]
+}
+
+# Logging
+LOGGER = 'upload_suite'
+LOG_FOLDER = '/tmp/'
+LOG_FILENAME = 'upload_suite.log'
diff --git a/testrail_upload_suites/requirements.txt b/testrail_upload_suites/requirements.txt
new file mode 100644
index 0000000..e000c3a
--- /dev/null
+++ b/testrail_upload_suites/requirements.txt
@@ -0,0 +1 @@
+requests>=2.22.0
\ No newline at end of file
diff --git a/testrail_upload_suites/testrail.py b/testrail_upload_suites/testrail.py
new file mode 100644
index 0000000..a2c6523
--- /dev/null
+++ b/testrail_upload_suites/testrail.py
@@ -0,0 +1,104 @@
+#
+# TestRail API binding for Python 3.x (API v2, available since
+# TestRail 3.0)
+# Compatible with TestRail 3.0 and later.
+#
+# Learn more:
+#
+# http://docs.gurock.com/testrail-api2/start
+# http://docs.gurock.com/testrail-api2/accessing
+#
+# Copyright Gurock Software GmbH. See license.md for details.
+#
+
+import requests
+import json
+import base64
+
+
+class APIClient:
+ def __init__(self, base_url):
+ self.user = ''
+ self.password = ''
+ if not base_url.endswith('/'):
+ base_url += '/'
+ self.__url = base_url + 'index.php?/api/v2/'
+
+ #
+ # Send Get
+ #
+ # Issues a GET request (read) against the API and returns the result
+ # (as Python dict) or filepath if successful file download
+ #
+ # Arguments:
+ #
+ # uri The API method to call including parameters
+ # (e.g. get_case/1)
+ #
+ # filepath The path and file name for attachment download
+ # Used only for 'get_attachment/:attachment_id'
+ #
+ def send_get(self, uri, filepath=None):
+ return self.__send_request('GET', uri, filepath)
+
+ #
+ # Send POST
+ #
+ # Issues a POST request (write) against the API and returns the result
+ # (as Python dict).
+ #
+ # Arguments:
+ #
+ # uri The API method to call including parameters
+ # (e.g. add_case/1)
+ # data The data to submit as part of the request (as
+ # Python dict, strings must be UTF-8 encoded)
+ # If adding an attachment, must be the path
+ # to the file
+ #
+ def send_post(self, uri, data):
+ return self.__send_request('POST', uri, data)
+
+ def __send_request(self, method, uri, data):
+ url = self.__url + uri
+
+ auth = str(
+ base64.b64encode(
+ bytes('%s:%s' % (self.user, self.password), 'utf-8')
+ ),
+ 'ascii'
+ ).strip()
+ headers = {'Authorization': 'Basic ' + auth}
+
+ if method == 'POST':
+ if uri[:14] == 'add_attachment': # add_attachment API method
+ files = {'attachment': (open(data, 'rb'))}
+ response = requests.post(url, headers=headers, files=files)
+ files['attachment'].close()
+ else:
+ headers['Content-Type'] = 'application/json'
+ payload = bytes(json.dumps(data), 'utf-8')
+ response = requests.post(url, headers=headers, data=payload)
+ else:
+ headers['Content-Type'] = 'application/json'
+ response = requests.get(url, headers=headers)
+
+ if response.status_code > 201:
+ try:
+ error = response.json()
+ except: # response.content not formatted as JSON
+ error = str(response.content)
+ raise APIError('TestRail API returned HTTP %s (%s)' % (response.status_code, error))
+ else:
+ if uri[:15] == 'get_attachment/': # Expecting file, not JSON
+ try:
+ open(data, 'wb').write(response.content)
+ return (data)
+ except:
+ return ("Error saving attachment.")
+ else:
+ return response.json()
+
+
+class APIError(Exception):
+ pass
\ No newline at end of file
diff --git a/testrail_upload_suites/tests_for_upload.txt b/testrail_upload_suites/tests_for_upload.txt
new file mode 100644
index 0000000..73ff016
--- /dev/null
+++ b/testrail_upload_suites/tests_for_upload.txt
@@ -0,0 +1,110 @@
+tempest.api.compute.flavors.test_flavors.FlavorsV2TestJSON.test_get_flavor
+tempest.api.compute.flavors.test_flavors.FlavorsV2TestJSON.test_list_flavors
+tempest.api.compute.security_groups.test_security_group_rules.SecurityGroupRulesTestJSON.test_security_group_rules_create
+tempest.api.compute.security_groups.test_security_group_rules.SecurityGroupRulesTestJSON.test_security_group_rules_list
+tempest.api.compute.security_groups.test_security_groups.SecurityGroupsTestJSON.test_security_groups_create_list_delete
+tempest.api.compute.servers.test_attach_interfaces.AttachInterfacesTestJSON.test_add_remove_fixed_ip
+tempest.api.compute.servers.test_create_server.ServersTestBootFromVolume.test_list_servers
+tempest.api.compute.servers.test_create_server.ServersTestBootFromVolume.test_verify_server_details
+tempest.api.compute.servers.test_server_actions.ServerActionsTestJSON.test_reboot_server_hard
+tempest.api.compute.servers.test_create_server.ServersTestJSON.test_list_servers
+tempest.api.compute.servers.test_create_server.ServersTestJSON.test_verify_server_details
+tempest.api.compute.test_versions.TestVersions.test_get_version_details
+tempest.api.compute.test_versions.TestVersions.test_list_api_versions
+tempest.api.identity.admin.v3.test_credentials.CredentialsTestJSON.test_credentials_create_get_update_delete
+tempest.api.identity.admin.v3.test_domains.DefaultDomainTestJSON.test_default_domain_exists
+tempest.api.identity.admin.v3.test_domains.DomainsTestJSON.test_create_update_delete_domain
+tempest.api.identity.admin.v3.test_endpoints.EndPointsTestJSON.test_update_endpoint
+tempest.api.compute.servers.test_create_server.ServersTestManualDisk.test_list_servers
+tempest.api.compute.servers.test_create_server.ServersTestManualDisk.test_verify_server_details
+tempest.api.identity.admin.v3.test_groups.GroupsV3TestJSON.test_group_users_add_list_delete
+tempest.api.identity.admin.v3.test_regions.RegionsTestJSON.test_create_region_with_specific_id
+tempest.api.identity.admin.v3.test_services.ServicesTestJSON.test_create_update_get_service
+tempest.api.image.v2.test_images.BasicOperationsImagesTest.test_delete_image
+tempest.api.image.v2.test_images.BasicOperationsImagesTest.test_register_upload_get_image_file
+tempest.api.image.v2.test_images.BasicOperationsImagesTest.test_update_image
+tempest.api.image.v2.test_versions.VersionsTest.test_list_versions
+tempest.api.compute.servers.test_server_addresses.ServerAddressesTestJSON.test_list_server_addresses
+tempest.api.compute.servers.test_server_addresses.ServerAddressesTestJSON.test_list_server_addresses_by_network
+tempest.api.network.test_extensions.ExtensionsTestJSON.test_list_show_extensions
+tempest.api.network.test_networks.BulkNetworkOpsTest.test_bulk_create_delete_network
+tempest.api.identity.admin.v3.test_policies.PoliciesTestJSON.test_create_update_delete_policy
+tempest.api.network.test_networks.BulkNetworkOpsTest.test_bulk_create_delete_port
+tempest.api.identity.admin.v3.test_roles.RolesV3TestJSON.test_role_create_update_show_list
+tempest.api.network.test_networks.BulkNetworkOpsTest.test_bulk_create_delete_subnet
+tempest.api.identity.admin.v3.test_trusts.TrustsV3TestJSON.test_get_trusts_all
+tempest.api.identity.v3.test_api_discovery.TestApiDiscovery.test_api_media_types
+tempest.api.identity.v3.test_api_discovery.TestApiDiscovery.test_api_version_resources
+tempest.api.identity.v3.test_api_discovery.TestApiDiscovery.test_api_version_statuses
+tempest.api.identity.v3.test_api_discovery.TestApiDiscovery.test_list_api_versions
+tempest.api.network.test_networks.NetworksIpV6Test.test_create_update_delete_network_subnet
+tempest.api.network.test_networks.NetworksIpV6Test.test_external_network_visibility
+tempest.api.network.test_networks.NetworksIpV6Test.test_list_networks
+tempest.api.network.test_networks.NetworksIpV6Test.test_list_subnets
+tempest.api.network.test_networks.NetworksIpV6Test.test_show_network
+tempest.api.network.test_networks.NetworksIpV6Test.test_show_subnet
+tempest.api.network.test_floating_ips.FloatingIPTestJSON.test_create_floating_ip_specifying_a_fixed_ip_address
+tempest.api.network.test_ports.PortsTestJSON.test_create_port_in_allowed_allocation_pools
+tempest.api.network.test_floating_ips.FloatingIPTestJSON.test_create_list_show_update_delete_floating_ip
+tempest.api.network.test_ports.PortsTestJSON.test_create_port_with_no_securitygroups
+tempest.api.network.test_ports.PortsTestJSON.test_create_update_delete_port
+tempest.api.network.test_ports.PortsTestJSON.test_list_ports
+tempest.api.network.test_ports.PortsTestJSON.test_show_port
+tempest.api.network.test_networks.BulkNetworkOpsIpV6Test.test_bulk_create_delete_network
+tempest.api.network.test_networks.BulkNetworkOpsIpV6Test.test_bulk_create_delete_port
+tempest.api.network.test_routers.RoutersIpV6Test.test_add_multiple_router_interfaces
+tempest.api.network.test_networks.BulkNetworkOpsIpV6Test.test_bulk_create_delete_subnet
+tempest.api.network.test_routers.RoutersIpV6Test.test_add_remove_router_interface_with_port_id
+tempest.api.network.test_networks.NetworksTest.test_create_update_delete_network_subnet
+tempest.api.network.test_routers.RoutersIpV6Test.test_add_remove_router_interface_with_subnet_id
+tempest.api.network.test_networks.NetworksTest.test_external_network_visibility
+tempest.api.network.test_networks.NetworksTest.test_list_networks
+tempest.api.network.test_networks.NetworksTest.test_list_subnets
+tempest.api.network.test_networks.NetworksTest.test_show_network
+tempest.api.network.test_networks.NetworksTest.test_show_subnet
+tempest.api.network.test_routers.RoutersIpV6Test.test_create_show_list_update_delete_router
+tempest.api.network.test_ports.PortsIpV6TestJSON.test_create_port_in_allowed_allocation_pools
+tempest.api.network.test_ports.PortsIpV6TestJSON.test_create_port_with_no_securitygroups
+tempest.api.network.test_ports.PortsIpV6TestJSON.test_create_update_delete_port
+tempest.api.network.test_ports.PortsIpV6TestJSON.test_list_ports
+tempest.api.network.test_ports.PortsIpV6TestJSON.test_show_port
+tempest.api.network.test_security_groups.SecGroupIPv6Test.test_create_list_update_show_delete_security_group
+tempest.api.network.test_security_groups.SecGroupIPv6Test.test_create_show_delete_security_group_rule
+tempest.api.network.test_security_groups.SecGroupIPv6Test.test_list_security_groups
+tempest.api.network.test_security_groups.SecGroupTest.test_create_list_update_show_delete_security_group
+tempest.api.network.test_security_groups.SecGroupTest.test_create_show_delete_security_group_rule
+tempest.api.network.test_security_groups.SecGroupTest.test_list_security_groups
+tempest.api.network.test_routers.RoutersTest.test_add_multiple_router_interfaces
+tempest.api.network.test_subnetpools_extensions.SubnetPoolsTestJSON.test_create_list_show_update_delete_subnetpools
+tempest.api.network.test_versions.NetworksApiDiscovery.test_api_version_resources
+tempest.api.network.test_routers.RoutersTest.test_add_remove_router_interface_with_port_id
+tempest.api.volume.test_volumes_get.VolumesGetTest.test_volume_create_get_update_delete
+tempest.api.network.test_routers.RoutersTest.test_add_remove_router_interface_with_subnet_id
+tempest.api.network.test_routers.RoutersTest.test_create_show_list_update_delete_router
+tempest.api.volume.test_volumes_get.VolumesGetTest.test_volume_create_get_update_delete_from_image
+tempest.api.volume.test_volumes_list.VolumesListTestJSON.test_volume_list
+tempest.api.volume.test_versions.VersionsTest.test_list_versions
+heat_tempest_plugin.tests.api.test_heat_api.stacks_stack_list_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_create_empty_stack_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_delete_empty_stack_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_poll_for_stack_update_complete_smoke.test_request
+tempest.api.volume.test_volumes_actions.VolumesActionsTest.test_attach_detach_volume_to_instance
+heat_tempest_plugin.tests.api.test_heat_api.stacks_poll_for_stack_patch_update_complete_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_get_stack_output_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_delete_stack_smoke.test_request
+designate_tempest_plugin.tests.api.v2.test_zones.ZonesTest.test_delete_zone
+designate_tempest_plugin.tests.api.v2.test_zones_exports.ZonesExportTest.test_show_zone_export
+designate_tempest_plugin.tests.api.v2.test_zones_imports.ZonesImportTest.test_show_zone_import
+tempest.scenario.test_network_basic_ops.TestNetworkBasicOps.test_network_basic_ops
+tempest.scenario.test_server_basic_ops.TestServerBasicOps.test_server_basic_ops
+heat_tempest_plugin.tests.api.test_heat_api.stacks_poll_for_empty_create_complete_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_show_empty_stack_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_create_stack_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_poll_for_stack_create_complete_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_show_stack_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_update_stack_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_patch_update_stack_smoke.test_request
+heat_tempest_plugin.tests.api.test_heat_api.stacks_list_stack_outputs_smoke.test_request
+tempest_horizon.tests.scenario.test_dashboard_basic_ops.TestDashboardBasicOps.test_basic_scenario
+designate_tempest_plugin.tests.api.v2.test_recordset.RecordsetsTest.test_create_recordset
+designate_tempest_plugin.tests.scenario.v2.test_zones.ZonesTest.test_create_and_delete_zone
diff --git a/testrail_upload_suites/upload_suite.py b/testrail_upload_suites/upload_suite.py
new file mode 100644
index 0000000..3cabf05
--- /dev/null
+++ b/testrail_upload_suites/upload_suite.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+#
+# Copyright 2017 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 logging
+import sys
+
+from base import Base
+from testrail import APIError
+
+import config
+
+
+logging.basicConfig(
+ format='[%(asctime)s][%(name)s][%(levelname)s] %(message)s',
+ datefmt='%d-%m-%Y %H:%M:%S',
+ handlers=[logging.FileHandler('{}{}'.format(
+ config.LOG_FOLDER, config.LOG_FILENAME)), logging.StreamHandler()],
+ level=logging.INFO)
+logger = logging.getLogger(config.LOGGER)
+
+
+def choose_section_by_test_name(test_name):
+ for section, key_words in config.SECTIONS_MAP.items():
+ for key_word in key_words:
+ if key_word in test_name:
+ return section
+
+ return "Other"
+
+
+def get_tags_by_test_name(test_name):
+ tags = []
+ if test_name.find("[") > -1:
+ tags = test_name.split("[")[1][:-1].split(",")
+ return tags
+
+
+def create_tr_test_cases(test_cases, milestone_id, type_id=1, priority_id=4,
+ qa_team=4):
+ tr_test_cases = []
+
+ for test_case_name in test_cases:
+ section = choose_section_by_test_name(test_case_name)
+ if section not in config.SECTIONS_MAP:
+ config.SECTIONS_MAP[section] = []
+ test_class, test_name = test_case_name.rsplit(".", 1)
+
+ report_label = test_name
+ for tag in get_tags_by_test_name(test_name):
+ if tag.startswith("id-"):
+ report_label = tag[3:]
+ break
+
+ test_case = {
+ "milestone_id": milestone_id,
+ "section": section,
+ "title": (("%s.%s" % (test_class, test_name)) if config.USE_TEST_IDs
+ else test_name),
+ "type_id": type_id,
+ "priority_id": priority_id,
+ "custom_qa_team": qa_team,
+ "estimate": "1m",
+ "refs": "",
+ "custom_test_group": test_class,
+ "custom_test_case_description": test_name,
+ "custom_test_case_steps": [{"Run test": "passed"}],
+ "custom_report_label": report_label
+ }
+ tr_test_cases.append(test_case)
+
+ return tr_test_cases
+
+
+def add_tr_test_case(tr_client, suite_id, tr_test_case):
+ all_tests = []
+ for i in range(7):
+ try:
+ added_test = tr_client.add_case(suite_id, tr_test_case)
+ all_tests.append(added_test)
+ except APIError:
+ logging.info("APIError")
+ else:
+ break
+ return all_tests
+
+
+def main():
+ call = Base()
+ try:
+ tests_file_path = sys.argv[1]
+ except IndexError:
+ raise Exception("Path to a tests file should be provided!")
+
+ if os.path.exists(tests_file_path):
+ logger.info("Reading tests file '%s'..." % tests_file_path)
+ with open(tests_file_path) as f:
+ test_cases = [test for test in f.read().split("\n") if test]
+ logger.info("Tests file '%s' has been successfully read."
+ % tests_file_path)
+ else:
+ raise Exception("Tests file '%s' doesn't exist!" % tests_file_path)
+
+ logger.info("Initializing TestRail client...")
+ logger.info("TestRail client has been successfully initialized.")
+ logger.info("Getting milestone '%s'..." % config.MILESTONE)
+
+ milestone = call.get_milestone_by_name(config.MILESTONE)
+
+ logger.info(milestone)
+ logger.info("Getting tests suite '%s'..." % config.SUITE)
+
+ suite = call.get_suite_by_name(config.SUITE)
+ if not suite:
+ logger.info("Tests suite '%s' not found. "
+ "Creating tests suite..." % config.SUITE)
+
+ suite = call.add_suite(config.SUITE)
+ logger.info("Tests suite has benn successfully created.")
+ logger.info(suite)
+
+ logger.info("Creating test cases for TestRail...")
+ tr_test_cases = create_tr_test_cases(
+ test_cases, milestone["id"],
+ type_id=config.TEST_CASE_TYPE_ID,
+ priority_id=config.TEST_CASE_PRIORITY_ID,
+ qa_team=config.QA_TEAM)
+ logger.info("Test cases have been successfully created.")
+
+ sections_map = {}
+ for section in sorted(config.SECTIONS_MAP.keys()):
+ logger.info("Creating section '%s'..." % section)
+ s = call.add_section(suite["id"], section)
+ logger.info("Section '%s' has been successfully created." % section)
+ sections_map[section] = s["id"]
+
+ logger.info("Uploading created test cases to TestRail...")
+
+ all_added_test_cases = []
+ for t in tr_test_cases:
+ test_cases = add_tr_test_case(call, sections_map[t["section"]], t)
+ all_added_test_cases.append(test_cases)
+
+ logger.info("Test cases have been successfully uploaded.")
+
+
+if __name__ == "__main__":
+ main()