diff --git a/CODEOWNERS b/CODEOWNERS index 473bdf647..5b4c31b04 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ # Default code owners for master branch - Atlassian Data Center App Performance Toolkit -* @benmagro @ometelytsia @jkim2-atlassian @SergeyMoroz0703 @astashys @mmizin \ No newline at end of file +* @benmagro @ometelytsia @SergeyMoroz0703 @astashys @mmizin \ No newline at end of file diff --git a/README.md b/README.md index ab90f11b3..1d3fc9929 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,16 @@ This repository contains Taurus scripts for performance testing of Atlassian Dat ## Supported versions * Supported Jira versions: - * Jira [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 7.13.6 and 8.5.0 + * Jira [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 7.13.15 and 8.5.6 * Jira Platform release: 8.0.3 * Supported Confluence versions: - * Confluence [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.13.8 - * Confluence Platform release: 7.0.4 + * Confluence [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.13.13 + * Confluence Platform release: 7.0.5 * Supported Bitbucket Server versions: - * Bitbucket Server [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.10.0 - * Bitbucket Server Platform release: 7.0.0 + * Bitbucket Server [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.10.5 + * Bitbucket Server Platform release: 7.0.5 ## Support In case of technical questions, issues or problems with DC Apps Performance Toolkit, contact us for support in the [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. diff --git a/app/bitbucket.yml b/app/bitbucket.yml index 66421a9ed..d258fb766 100644 --- a/app/bitbucket.yml +++ b/app/bitbucket.yml @@ -83,7 +83,7 @@ modules: httpsampler.ignore_failed_embedded_resources: "true" selenium: chromedriver: - version: "83.0.4103.39" # Supports Chrome version 83. You can refer to http://chromedriver.chromium.org/downloads + version: "84.0.4147.30" # Supports Chrome version 84. You can refer to http://chromedriver.chromium.org/downloads reporting: - data-source: sample-labels module: junit-xml diff --git a/app/confluence.yml b/app/confluence.yml index c72f83f08..0e6c6235e 100644 --- a/app/confluence.yml +++ b/app/confluence.yml @@ -4,7 +4,7 @@ settings: aggregator: consolidator verbose: false env: - application_hostname: test_confluence_instance.atlassian.com # Confluence DC hostname without protocol and port e.g. test-jira.atlassian.com or localhost + application_hostname: test_confluence_instance.atlassian.com # Confluence DC hostname without protocol and port e.g. test-confluence.atlassian.com or localhost application_protocol: http # http or https application_port: 80 # 80, 443, 8080, 1990, etc application_postfix: # e.g. /confluence in case of url like http://localhost:1990/confluence @@ -105,7 +105,7 @@ modules: httpsampler.ignore_failed_embedded_resources: "true" selenium: chromedriver: - version: "83.0.4103.39" # Supports Chrome version 83. You can refer to http://chromedriver.chromium.org/downloads + version: "84.0.4147.30" # Supports Chrome version 84. You can refer to http://chromedriver.chromium.org/downloads reporting: - data-source: sample-labels module: junit-xml diff --git a/app/jira.yml b/app/jira.yml index 19fd14b8c..1aac6dd5e 100644 --- a/app/jira.yml +++ b/app/jira.yml @@ -109,7 +109,7 @@ modules: httpsampler.ignore_failed_embedded_resources: "true" selenium: chromedriver: - version: "83.0.4103.39" # Supports Chrome version 83. You can refer to http://chromedriver.chromium.org/downloads + version: "84.0.4147.30" # Supports Chrome version 84. You can refer to http://chromedriver.chromium.org/downloads reporting: - data-source: sample-labels module: junit-xml diff --git a/app/selenium_ui/bitbucket_ui.py b/app/selenium_ui/bitbucket_ui.py index 66f2a7657..796c787fb 100644 --- a/app/selenium_ui/bitbucket_ui.py +++ b/app/selenium_ui/bitbucket_ui.py @@ -3,64 +3,64 @@ # this action should be the first one -def test_0_selenium_a_login(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.login(webdriver, bitbucket_datasets) +def test_0_selenium_a_login(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.login(bitbucket_webdriver, bitbucket_datasets) -def test_1_selenium_view_dashboard(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.view_dashboard(webdriver, bitbucket_datasets) +def test_1_selenium_view_dashboard(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.view_dashboard(bitbucket_webdriver, bitbucket_datasets) -def test_2_selenium_create_pull_request(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.create_pull_request(webdriver, bitbucket_datasets) +def test_2_selenium_create_pull_request(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.create_pull_request(bitbucket_webdriver, bitbucket_datasets) -def test_3_selenium_view_projects(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.view_projects(webdriver, bitbucket_datasets) +def test_3_selenium_view_projects(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.view_projects(bitbucket_webdriver, bitbucket_datasets) -def test_4_selenium_view_project_repositories(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.view_project_repos(webdriver, bitbucket_datasets) +def test_4_selenium_view_project_repositories(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.view_project_repos(bitbucket_webdriver, bitbucket_datasets) -def test_5_selenium_view_repo(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.view_repo(webdriver, bitbucket_datasets) +def test_5_selenium_view_repo(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.view_repo(bitbucket_webdriver, bitbucket_datasets) -def test_6_selenium_view_list_pull_requests(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.view_list_pull_requests(webdriver, bitbucket_datasets) +def test_6_selenium_view_list_pull_requests(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.view_list_pull_requests(bitbucket_webdriver, bitbucket_datasets) -def test_7_selenium_view_pull_request_overview(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.view_pull_request_overview_tab(webdriver, bitbucket_datasets) +def test_7_selenium_view_pull_request_overview(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.view_pull_request_overview_tab(bitbucket_webdriver, bitbucket_datasets) -def test_8_selenium_view_pull_request_diff(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.view_pull_request_diff_tab(webdriver, bitbucket_datasets) +def test_8_selenium_view_pull_request_diff(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.view_pull_request_diff_tab(bitbucket_webdriver, bitbucket_datasets) -def test_9_selenium_view_pull_request_commits(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.view_pull_request_commits_tab(webdriver, bitbucket_datasets) +def test_9_selenium_view_pull_request_commits(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.view_pull_request_commits_tab(bitbucket_webdriver, bitbucket_datasets) -def test_10_selenium_comment_pull_request_diff(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.comment_pull_request_diff(webdriver, bitbucket_datasets) +def test_10_selenium_comment_pull_request_diff(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.comment_pull_request_diff(bitbucket_webdriver, bitbucket_datasets) -def test_11_selenium_comment_pull_request_overview(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.comment_pull_request_overview(webdriver, bitbucket_datasets) +def test_11_selenium_comment_pull_request_overview(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.comment_pull_request_overview(bitbucket_webdriver, bitbucket_datasets) -def test_12_selenium_view_branches(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.view_branches(webdriver, bitbucket_datasets) +def test_12_selenium_view_branches(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.view_branches(bitbucket_webdriver, bitbucket_datasets) -def test_13_selenium_view_commits(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.view_commits(webdriver, bitbucket_datasets) +def test_13_selenium_view_commits(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.view_commits(bitbucket_webdriver, bitbucket_datasets) -# def test_01_selenium_create_pull_request(webdriver, bitbucket_datasets, bitbucket_screen_shots): -# modules.create_pull_request(webdriver, bitbucket_datasets) +# def test_01_selenium_create_pull_request(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): +# modules.create_pull_request(bitbucket_webdriver, bitbucket_datasets) """ @@ -68,9 +68,9 @@ def test_13_selenium_view_commits(webdriver, bitbucket_datasets, bitbucket_scree Write your custom selenium scripts in `app/extension/jira/extension_ui.py`. Refer to `app/selenium_ui/jira/modules.py` for examples. """ -# def test_1_selenium_custom_action(webdriver, bitbucket_datasets, bitbucket_screen_shots): -# extension_ui.app_specific_action(webdriver, bitbucket_datasets) +# def test_1_selenium_custom_action(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): +# extension_ui.app_specific_action(bitbucket_webdriver, bitbucket_datasets) -def test_14_selenium_logout(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.logout(webdriver, bitbucket_datasets) +def test_14_selenium_logout(bitbucket_webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.logout(bitbucket_webdriver, bitbucket_datasets) diff --git a/app/selenium_ui/confluence_ui.py b/app/selenium_ui/confluence_ui.py index a6fab4647..d41138e35 100644 --- a/app/selenium_ui/confluence_ui.py +++ b/app/selenium_ui/confluence_ui.py @@ -3,32 +3,32 @@ # this action should be the first one -def test_0_selenium_a_login(webdriver, confluence_datasets, confluence_screen_shots): - modules.login(webdriver, confluence_datasets) +def test_0_selenium_a_login(confluence_webdriver, confluence_datasets, confluence_screen_shots): + modules.login(confluence_webdriver, confluence_datasets) -def test_1_selenium_view_page(webdriver, confluence_datasets, confluence_screen_shots): - modules.view_page(webdriver, confluence_datasets) +def test_1_selenium_view_page(confluence_webdriver, confluence_datasets, confluence_screen_shots): + modules.view_page(confluence_webdriver, confluence_datasets) -def test_1_selenium_create_page(webdriver, confluence_datasets, confluence_screen_shots): - modules.create_confluence_page(webdriver, confluence_datasets) +def test_1_selenium_create_page(confluence_webdriver, confluence_datasets, confluence_screen_shots): + modules.create_confluence_page(confluence_webdriver, confluence_datasets) -def test_1_selenium_edit_page(webdriver, confluence_datasets, confluence_screen_shots): - modules.edit_confluence_page(webdriver, confluence_datasets) +def test_1_selenium_edit_page(confluence_webdriver, confluence_datasets, confluence_screen_shots): + modules.edit_confluence_page(confluence_webdriver, confluence_datasets) -def test_1_selenium_create_comment(webdriver, confluence_datasets, confluence_screen_shots): - modules.create_comment(webdriver, confluence_datasets) +def test_1_selenium_create_comment(confluence_webdriver, confluence_datasets, confluence_screen_shots): + modules.create_comment(confluence_webdriver, confluence_datasets) -def test_1_selenium_view_blog(webdriver, confluence_datasets, confluence_screen_shots): - modules.view_blog(webdriver, confluence_datasets) +def test_1_selenium_view_blog(confluence_webdriver, confluence_datasets, confluence_screen_shots): + modules.view_blog(confluence_webdriver, confluence_datasets) -def test_1_selenium_view_dashboard(webdriver, confluence_datasets, confluence_screen_shots): - modules.view_dashboard(webdriver, confluence_datasets) +def test_1_selenium_view_dashboard(confluence_webdriver, confluence_datasets, confluence_screen_shots): + modules.view_dashboard(confluence_webdriver, confluence_datasets) """ @@ -36,10 +36,10 @@ def test_1_selenium_view_dashboard(webdriver, confluence_datasets, confluence_sc Write your custom selenium scripts in `app/extension/confluence/extension_ui.py`. Refer to `app/selenium_ui/confluence/modules.py` for examples. """ -# def test_1_selenium_custom_action(webdriver, confluence_datasets, confluence_screen_shots): -# extension_ui.app_specific_action(webdriver, confluence_datasets) +# def test_1_selenium_custom_action(confluence_webdriver, confluence_datasets, confluence_screen_shots): +# extension_ui.app_specific_action(confluence_webdriver, confluence_datasets) # this action should be the last one -def test_2_selenium_z_log_out(webdriver, confluence_datasets, confluence_screen_shots): - modules.log_out(webdriver, confluence_datasets) +def test_2_selenium_z_log_out(confluence_webdriver, confluence_datasets, confluence_screen_shots): + modules.log_out(confluence_webdriver, confluence_datasets) diff --git a/app/selenium_ui/conftest.py b/app/selenium_ui/conftest.py index e8e4f2716..a3f79a7b3 100644 --- a/app/selenium_ui/conftest.py +++ b/app/selenium_ui/conftest.py @@ -1,7 +1,6 @@ import atexit import csv import datetime -import os import sys import time import functools @@ -121,16 +120,16 @@ def wrapper(): return deco_wrapper -@pytest.fixture(scope="module") -def webdriver(): +def webdriver(app_settings): def driver_init(): chrome_options = Options() - if os.getenv('WEBDRIVER_VISIBLE', 'False').lower() != 'true': + if not app_settings.webdriver_visible: chrome_options.add_argument("--headless") chrome_options.add_argument("--window-size={},{}".format(SCREEN_WIDTH, SCREEN_HEIGHT)) chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-infobars") driver = Chrome(options=chrome_options) + driver.app_settings = app_settings return driver # First time driver init if not globals.driver: @@ -155,6 +154,21 @@ def driver_quit(): return globals.driver +@pytest.fixture(scope="module") +def jira_webdriver(): + return webdriver(app_settings=JIRA_SETTINGS) + + +@pytest.fixture(scope="module") +def confluence_webdriver(): + return webdriver(app_settings=CONFLUENCE_SETTINGS) + + +@pytest.fixture(scope="module") +def bitbucket_webdriver(): + return webdriver(app_settings=BITBUCKET_SETTINGS) + + @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item): # Making test result information available in fixtures @@ -165,24 +179,24 @@ def pytest_runtest_makereport(item): @pytest.fixture -def jira_screen_shots(request, webdriver): +def jira_screen_shots(request, jira_webdriver): yield - get_screen_shots(request, webdriver, app_settings=JIRA_SETTINGS) + get_screen_shots(request, jira_webdriver) @pytest.fixture -def confluence_screen_shots(request, webdriver): +def confluence_screen_shots(request, confluence_webdriver): yield - get_screen_shots(request, webdriver, app_settings=CONFLUENCE_SETTINGS) + get_screen_shots(request, confluence_webdriver) @pytest.fixture -def bitbucket_screen_shots(request, webdriver): +def bitbucket_screen_shots(request, bitbucket_webdriver): yield - get_screen_shots(request, webdriver, app_settings=BITBUCKET_SETTINGS) + get_screen_shots(request, bitbucket_webdriver) -def get_screen_shots(request, webdriver, app_settings): +def get_screen_shots(request, webdriver): # request.node is an "item" because we use the default # "function" scope if request.node.rep_call.failed: @@ -199,7 +213,7 @@ def get_screen_shots(request, webdriver, app_settings): with open(f'{error_artifact_name}.html', 'wb') as html_file: html_file.write(webdriver.page_source.encode('utf-8')) webdriver.execute_script("window.onbeforeunload = function() {};") # to prevent alert window (force get link) - webdriver.get(app_settings.server_url) + webdriver.get(webdriver.app_settings.server_url) application_dataset = Dataset() diff --git a/app/selenium_ui/jira/modules.py b/app/selenium_ui/jira/modules.py index 8e15b64cd..8172a6e1e 100644 --- a/app/selenium_ui/jira/modules.py +++ b/app/selenium_ui/jira/modules.py @@ -5,6 +5,12 @@ from selenium_ui.jira.pages.pages import Login, PopupManager, Issue, Project, Search, ProjectsList, \ BoardsList, Board, Dashboard, Logout +from util.api.jira_clients import JiraRestClient +from util.conf import JIRA_SETTINGS + +client = JiraRestClient(JIRA_SETTINGS.server_url, JIRA_SETTINGS.admin_login, JIRA_SETTINGS.admin_password) +rte_status = client.check_rte_status() + KANBAN_BOARDS = "kanban_boards" SCRUM_BOARDS = "scrum_boards" USERS = "users" @@ -90,7 +96,7 @@ def sub_measure(): @print_timing("selenium_create_issue:fill_and_submit_issue_form") def sub_measure(): issue_modal.fill_summary_create() # Fill summary field - issue_modal.fill_description_create() # Fill description field + issue_modal.fill_description_create(rte_status) # Fill description field issue_modal.assign_to_me() # Click assign to me issue_modal.set_resolution() # Set resolution if there is such field issue_modal.set_issue_type() # Set issue type, use non epic type @@ -126,7 +132,7 @@ def sub_measure(): sub_measure() issue_page.fill_summary_edit() # edit summary - issue_page.fill_description_edit() # edit description + issue_page.fill_description_edit(rte_status) # edit description @print_timing("selenium_edit_issue:save_edit_issue_form") def sub_measure(): @@ -147,7 +153,7 @@ def sub_measure(): issue_page.go_to_edit_comment() # Open edit comment page sub_measure() - issue_page.fill_comment_edit() # Fill comment text field + issue_page.fill_comment_edit(rte_status) # Fill comment text field @print_timing("selenium_save_comment:submit_form") def sub_measure(): diff --git a/app/selenium_ui/jira/pages/pages.py b/app/selenium_ui/jira/pages/pages.py index d001860c6..b98b13e23 100644 --- a/app/selenium_ui/jira/pages/pages.py +++ b/app/selenium_ui/jira/pages/pages.py @@ -1,6 +1,7 @@ from selenium.webdriver.common.keys import Keys import time import random +import json from selenium_ui.base_page import BasePage from selenium_ui.jira.pages.selectors import UrlManager, LoginPageLocators, DashboardLocators, PopupLocators, \ @@ -82,20 +83,29 @@ def __fill_rich_editor_textfield(self, text, selector): self.get_element(IssueLocators.tinymce_description_field).send_keys(text) self.return_to_parent_frame() + def __fill_textfield(self, text, selector): + self.get_element(selector).send_keys(text) + def edit_issue_submit(self): self.get_element(IssueLocators.edit_issue_submit).click() - def fill_description_edit(self): + def fill_description_edit(self, rte): text_description = f"Edit description form selenium - {self.generate_random_string(30)}" - self.__fill_rich_editor_textfield(text_description, selector=IssueLocators.issue_description_field) + if rte: + self.__fill_rich_editor_textfield(text_description, selector=IssueLocators.issue_description_field_RTE) + else: + self.__fill_textfield(text_description, selector=IssueLocators.issue_description_field) def open_create_issue_modal(self): self.wait_until_clickable(IssueLocators.create_issue_button).click() self.wait_until_visible(IssueLocators.issue_modal) - def fill_description_create(self): + def fill_description_create(self, rte): text_description = f'Description: {self.generate_random_string(100)}' - self.__fill_rich_editor_textfield(text_description, selector=IssueLocators.issue_description_field) + if rte: + self.__fill_rich_editor_textfield(text_description, selector=IssueLocators.issue_description_field_RTE) + else: + self.__fill_textfield(text_description, selector=IssueLocators.issue_description_field) def fill_summary_create(self): summary = f"Issue created date {time.time()}" @@ -116,22 +126,38 @@ def set_resolution(self): def set_issue_type(self): def __filer_epic(element): return "epic" not in element.get_attribute("class").lower() - - self.get_element(IssueLocators.issue_type_field).click() - issue_dropdown_elements = self.get_elements(IssueLocators.issue_type_dropdown_elements) - if issue_dropdown_elements: - filtered_issue_elements = list(filter(__filer_epic, issue_dropdown_elements)) - rnd_issue_type_el = random.choice(filtered_issue_elements) - self.action_chains().move_to_element(rnd_issue_type_el).click(rnd_issue_type_el).perform() - self.wait_until_invisible(IssueLocators.issue_ready_to_save_spinner) + issue_types = {} + data_suggestions = json.loads(self.get_element(IssueLocators.issue_types_options) + .get_attribute('data-suggestions')) + for data in data_suggestions: + # 'Please select' is label in items list where all issue types are presented (not for current project) + if 'Please select' not in str(data): + items = data['items'] + for label in items: + if label['label'] not in issue_types: + issue_types[label['label']] = label['selected'] + if 'Epic' in issue_types: + if issue_types['Epic']: + # Do in case of 'Epic' issue type is selected + self.action_chains().move_to_element(self.get_element(IssueLocators.issue_type_field)) + self.get_element(IssueLocators.issue_type_field).click() + issue_dropdown_elements = self.get_elements(IssueLocators.issue_type_dropdown_elements) + if issue_dropdown_elements: + filtered_issue_elements = list(filter(__filer_epic, issue_dropdown_elements)) + rnd_issue_type_el = random.choice(filtered_issue_elements) + self.action_chains().move_to_element(rnd_issue_type_el).click(rnd_issue_type_el).perform() + self.wait_until_invisible(IssueLocators.issue_ready_to_save_spinner) def submit_issue(self): self.wait_until_clickable(IssueLocators.issue_submit_button).click() self.wait_until_invisible(IssueLocators.issue_modal) - def fill_comment_edit(self): + def fill_comment_edit(self, rte): text = 'Comment from selenium' - self.__fill_rich_editor_textfield(text, selector=IssueLocators.edit_comment_text_field) + if rte: + self.__fill_rich_editor_textfield(text, selector=IssueLocators.edit_comment_text_field_RTE) + else: + self.__fill_textfield(text, selector=IssueLocators.edit_comment_text_field) def edit_comment_submit(self): self.get_element(IssueLocators.edit_comment_add_comment_button).click() diff --git a/app/selenium_ui/jira/pages/selectors.py b/app/selenium_ui/jira/pages/selectors.py index 58ccea0b2..a153ba689 100644 --- a/app/selenium_ui/jira/pages/selectors.py +++ b/app/selenium_ui/jira/pages/selectors.py @@ -101,11 +101,13 @@ class IssueLocators: # Issue create modal form issue_modal = (By.ID, "create-issue-dialog") issue_summary_field = (By.ID, "summary") - issue_description_field = (By.XPATH, f"//div[textarea[@id='description']]//iframe") + issue_description_field_RTE = (By.XPATH, "//div[textarea[@id='description']]//iframe") + issue_description_field = (By.XPATH, "//textarea[@id='description']") tinymce_description_field = (By.ID, "tinymce") issue_assign_to_me_link = (By.ID, 'assign-to-me-trigger') issue_resolution_field = (By.ID, 'resolution') issue_type_field = (By.ID, 'issuetype-field') + issue_types_options = (By.ID, "issuetype-options") issue_type_dropdown_elements = (By.CLASS_NAME, "aui-list-item") issue_ready_to_save_spinner = (By.CSS_SELECTOR, ".buttons>.throbber") issue_submit_button = (By.ID, "create-issue-submit") @@ -117,7 +119,8 @@ class IssueLocators: # Edit Comments page edit_comment_add_comment_button = (By.ID, "comment-add-submit") - edit_comment_text_field = (By.XPATH, f"//div[textarea[@id='comment']]//iframe") + edit_comment_text_field_RTE = (By.XPATH, "//div[textarea[@id='comment']]//iframe") + edit_comment_text_field = (By.XPATH, "//textarea[@id='comment']") class ProjectLocators: diff --git a/app/selenium_ui/jira_ui.py b/app/selenium_ui/jira_ui.py index 7deb4c199..9bf2949df 100644 --- a/app/selenium_ui/jira_ui.py +++ b/app/selenium_ui/jira_ui.py @@ -3,56 +3,56 @@ # this action should be the first one -def test_0_selenium_a_login(webdriver, jira_datasets, jira_screen_shots): - modules.login(webdriver, jira_datasets) +def test_0_selenium_a_login(jira_webdriver, jira_datasets, jira_screen_shots): + modules.login(jira_webdriver, jira_datasets) -def test_1_selenium_browse_projects_list(webdriver, jira_datasets, jira_screen_shots): - modules.browse_projects_list(webdriver, jira_datasets) +def test_1_selenium_browse_projects_list(jira_webdriver, jira_datasets, jira_screen_shots): + modules.browse_projects_list(jira_webdriver, jira_datasets) -def test_1_selenium_browse_boards_list(webdriver, jira_datasets, jira_screen_shots): - modules.browse_boards_list(webdriver, jira_datasets) +def test_1_selenium_browse_boards_list(jira_webdriver, jira_datasets, jira_screen_shots): + modules.browse_boards_list(jira_webdriver, jira_datasets) -def test_1_selenium_create_issue(webdriver, jira_datasets, jira_screen_shots): - modules.create_issue(webdriver, jira_datasets) +def test_1_selenium_create_issue(jira_webdriver, jira_datasets, jira_screen_shots): + modules.create_issue(jira_webdriver, jira_datasets) -def test_1_selenium_edit_issue(webdriver, jira_datasets, jira_screen_shots): - modules.edit_issue(webdriver, jira_datasets) +def test_1_selenium_edit_issue(jira_webdriver, jira_datasets, jira_screen_shots): + modules.edit_issue(jira_webdriver, jira_datasets) -def test_1_selenium_save_comment(webdriver, jira_datasets, jira_screen_shots): - modules.save_comment(webdriver, jira_datasets) +def test_1_selenium_save_comment(jira_webdriver, jira_datasets, jira_screen_shots): + modules.save_comment(jira_webdriver, jira_datasets) -def test_1_selenium_search_jql(webdriver, jira_datasets, jira_screen_shots): - modules.search_jql(webdriver, jira_datasets) +def test_1_selenium_search_jql(jira_webdriver, jira_datasets, jira_screen_shots): + modules.search_jql(jira_webdriver, jira_datasets) -def test_1_selenium_view_backlog_for_scrum_board(webdriver, jira_datasets, jira_screen_shots): - modules.view_backlog_for_scrum_board(webdriver, jira_datasets) +def test_1_selenium_view_backlog_for_scrum_board(jira_webdriver, jira_datasets, jira_screen_shots): + modules.view_backlog_for_scrum_board(jira_webdriver, jira_datasets) -def test_1_selenium_view_scrum_board(webdriver, jira_datasets, jira_screen_shots): - modules.view_scrum_board(webdriver, jira_datasets) +def test_1_selenium_view_scrum_board(jira_webdriver, jira_datasets, jira_screen_shots): + modules.view_scrum_board(jira_webdriver, jira_datasets) -def test_1_selenium_view_kanban_board(webdriver, jira_datasets, jira_screen_shots): - modules.view_kanban_board(webdriver, jira_datasets) +def test_1_selenium_view_kanban_board(jira_webdriver, jira_datasets, jira_screen_shots): + modules.view_kanban_board(jira_webdriver, jira_datasets) -def test_1_selenium_view_dashboard(webdriver, jira_datasets, jira_screen_shots): - modules.view_dashboard(webdriver, jira_datasets) +def test_1_selenium_view_dashboard(jira_webdriver, jira_datasets, jira_screen_shots): + modules.view_dashboard(jira_webdriver, jira_datasets) -def test_1_selenium_view_issue(webdriver, jira_datasets, jira_screen_shots): - modules.view_issue(webdriver, jira_datasets) +def test_1_selenium_view_issue(jira_webdriver, jira_datasets, jira_screen_shots): + modules.view_issue(jira_webdriver, jira_datasets) -def test_1_selenium_view_project_summary(webdriver, jira_datasets, jira_screen_shots): - modules.view_project_summary(webdriver, jira_datasets) +def test_1_selenium_view_project_summary(jira_webdriver, jira_datasets, jira_screen_shots): + modules.view_project_summary(jira_webdriver, jira_datasets) """ @@ -60,10 +60,10 @@ def test_1_selenium_view_project_summary(webdriver, jira_datasets, jira_screen_s Write your custom selenium scripts in `app/extension/jira/extension_ui.py`. Refer to `app/selenium_ui/jira/modules.py` for examples. """ -# def test_1_selenium_custom_action(webdriver, jira_datasets, jira_screen_shots): -# extension_ui.app_specific_action(webdriver, jira_datasets) +# def test_1_selenium_custom_action(jira_webdriver, jira_datasets, jira_screen_shots): +# extension_ui.app_specific_action(jira_webdriver, jira_datasets) # this action should be the last one -def test_2_selenium_z_log_out(webdriver, jira_datasets, jira_screen_shots): - modules.log_out(webdriver, jira_datasets) +def test_2_selenium_z_log_out(jira_webdriver, jira_datasets, jira_screen_shots): + modules.log_out(jira_webdriver, jira_datasets) diff --git a/app/util/api/abstract_clients.py b/app/util/api/abstract_clients.py index e855ae577..489731494 100644 --- a/app/util/api/abstract_clients.py +++ b/app/util/api/abstract_clients.py @@ -57,27 +57,34 @@ def session(self): def base_auth(self): return self.user, self.password - def get(self, url: str, error_msg: str, expected_status_codes: list = None): - response = self.session.get(url, auth=self.base_auth, verify=False, timeout=self.requests_timeout) + def get(self, url: str, error_msg: str, expected_status_codes: list = None, allow_redirect=False): + response = self.session.get(url, auth=self.base_auth, verify=False, timeout=self.requests_timeout, + allow_redirects=allow_redirect) self.__verify_response(response, error_msg, expected_status_codes) return response - def post(self, url: str, error_msg: str, body: dict = None, params=None): + def post(self, url: str, error_msg: str, body: dict = None, params=None, allow_redirect=False): body_data = self.to_json(body) if body else None - response = self.session.post(url, body_data, params=params, auth=self.base_auth, headers=self.JSON_HEADERS) + response = self.session.post(url, body_data, params=params, auth=self.base_auth, headers=self.JSON_HEADERS, + allow_redirects=allow_redirect) self.__verify_response(response, error_msg) return response - def put(self, url: str, error_msg: str, body: dict = None, params=None): + def put(self, url: str, error_msg: str, body: dict = None, params=None, allow_redirect=False): body_data = self.to_json(body) if body else None - response = self.session.put(url, body_data, params=params, auth=self.base_auth, headers=self.JSON_HEADERS) + response = self.session.put(url, body_data, params=params, auth=self.base_auth, headers=self.JSON_HEADERS, + allow_redirects=allow_redirect) self.__verify_response(response, error_msg) return response def __verify_response(self, response: Response, error_msg: str, expected_status_codes: list = None): - if response.ok or response.status_code in expected_status_codes: + if response.is_redirect: + raise Exception(f"Redirect detected.\n " + f"Please check config.yml file (application_hostname, application_port, " + f"application_protocol, application_postfix).") + if response.ok or (expected_status_codes and response.status_code in expected_status_codes): return status_code = response.status_code @@ -89,5 +96,6 @@ def __verify_response(self, response: Response, error_msg: str, expected_status_ elif status_code == 404: raise Exception(f"The URL or content are not found for {response.url}. " f"Please check environment variables in " - f"config.yml file (hostname, port, protocol, postfix).") + f"config.yml file (application_hostname, application_port, application_protocol, " + f"application_postfix).") raise Exception(f"{error_msg}. Response code:[{response.status_code}], response text:[{response.text}]") diff --git a/app/util/api/bitbucket_clients.py b/app/util/api/bitbucket_clients.py index 59f483773..13ac1916a 100644 --- a/app/util/api/bitbucket_clients.py +++ b/app/util/api/bitbucket_clients.py @@ -152,10 +152,15 @@ def get_bitbucket_system_page(self): def get_locale(self): language = None - page = self.get(self.host, "Could not get page content.").content + page = self.get(f'{self.host}/dashboard', "Could not get page content.").content tree = html.fromstring(page) try: language = tree.xpath('//html/@lang')[0] except Exception as error: print(f"Warning: Could not get user locale: {error}") return language + + def get_user_global_permissions(self, user=''): + api_url = f'{self.host}/rest/api/1.0/admin/permissions/users?filter={user}' + response = self.get(api_url, "Could not get user global permissions") + return response.json() diff --git a/app/util/api/confluence_clients.py b/app/util/api/confluence_clients.py index f1cd1ded9..1e358732e 100644 --- a/app/util/api/confluence_clients.py +++ b/app/util/api/confluence_clients.py @@ -148,7 +148,7 @@ def get_collaborative_editing_status(self): def get_locale(self): language = None - page = self.get(self.host, "Could not get page content.").content + page = self.get(f"{self.host}/index.action#all-updates", "Could not get page content.").content tree = html.fromstring(page) try: language = tree.xpath('.//meta[@name="ajs-user-locale"]/@content')[0] @@ -156,6 +156,12 @@ def get_locale(self): print(f"Warning: Could not get user locale: {error}") return language + def get_groups_membership(self, username): + api_url = f'{self.host}/rest/api/user/memberof?username={username}' + response = self.get(api_url, error_msg='Could not get group members') + groups = [group['name'] for group in response.json()['results']] + return groups + class ConfluenceRpcClient(Client): diff --git a/app/util/api/jira_clients.py b/app/util/api/jira_clients.py index 64430309a..c00eaa19b 100644 --- a/app/util/api/jira_clients.py +++ b/app/util/api/jira_clients.py @@ -1,4 +1,5 @@ from util.api.abstract_clients import RestClient +from selenium.common.exceptions import WebDriverException BATCH_SIZE_BOARDS = 1000 BATCH_SIZE_USERS = 1000 @@ -99,10 +100,10 @@ def issues_search(self, jql='order by key', start_at=0, max_results=1000, fields while loop_count > 0: body = { - "jql": jql, - "startAt": start_at, - "maxResults": max_results, - "fields": ['id'] if fields is None else fields + "jql": jql, + "startAt": start_at, + "maxResults": max_results, + "fields": ['id'] if fields is None else fields } response = self.post(api_url, "Could not retrieve issues", body=body) @@ -164,7 +165,8 @@ def get_nodes_count_via_rest(self): response = self.get(api_url, 'Could not get Jira nodes count', expected_status_codes=[200, 405]) if response.status_code == 405 and 'This Jira instance is not clustered' in response.text: return 'Server' - return len(response.json()) + nodes = [1 if node['state'] == "ACTIVE" else 0 for node in response.json()] + return nodes.count(1) def get_system_info_page(self): session = self._session @@ -205,3 +207,26 @@ def get_locale(self): api_url = f'{self.host}/rest/api/2/myself' user_properties = self.get(api_url, "Could not retrieve user") return user_properties.json()['locale'] + + def get_applications_properties(self): + api_url = f'{self.host}/rest/api/2/application-properties' + app_properties = self.get(api_url, "Could not retrieve application properties") + return app_properties.json() + + def check_rte_status(self): + # Safe check for RTE status. Return RTE status or return default value (True). + try: + app_prop = self.get_applications_properties() + rte = [i['value'] for i in app_prop if i['id'] == 'jira.rte.enabled'] + if rte: + return rte[0] == 'true' + else: + raise Exception("RTE status was nof found in application properties.") + except (Exception, WebDriverException) as e: + print(f"Warning: failed to get RTE status. Returned default value: True. Error: {e}") + return True + + def get_user_permissions(self): + api_url = f'{self.host}/rest/api/2/mypermissions' + app_properties = self.get(api_url, "Could not retrieve user permissions") + return app_properties.json() diff --git a/app/util/bitbucket/populate_db.sh b/app/util/bitbucket/populate_db.sh index 03d3ffcd5..f016122fc 100644 --- a/app/util/bitbucket/populate_db.sh +++ b/app/util/bitbucket/populate_db.sh @@ -20,7 +20,7 @@ BITBUCKET_DB_USER="postgres" BITBUCKET_DB_PASS="Password1!" # BITBUCKET version variables -SUPPORTED_BITBUCKET_VERSIONS=(6.10.0 7.0.0) +SUPPORTED_BITBUCKET_VERSIONS=(6.10.5 7.0.5) BITBUCKET_VERSION=$(sudo su bitbucket -c "cat ${BITBUCKET_VERSION_FILE}") if [[ -z "$BITBUCKET_VERSION" ]]; then echo The $BITBUCKET_VERSION_FILE file does not exists or emtpy. Please check if BITBUCKET_VERSION_FILE variable \ @@ -47,13 +47,14 @@ if [[ ! "${SUPPORTED_BITBUCKET_VERSIONS[@]}" =~ "${BITBUCKET_VERSION}" ]]; then # Check if --force flag is passed into command if [[ "$1" == "--force" ]]; then # Check if passed Bitbucket version is in list of supported - if [[ "${SUPPORTED_BITBUCKET_VERSIONS[@]}" =~ "$2" ]]; then + if [[ " ${SUPPORTED_BITBUCKET_VERSIONS[@]} " =~ " ${2} " ]]; then DB_DUMP_URL="${DATASETS_AWS_BUCKET}/$2/${DATASETS_SIZE}/${DB_DUMP_NAME}" echo "Force mode. Dataset URL: ${DB_DUMP_URL}" else - echo "Correct dataset version was not specified after --force flag." - echo "Available datasets: ${SUPPORTED_BITBUCKET_VERSIONS[@]}" - exit 1 + LAST_DATASET_VERSION=${SUPPORTED_BITBUCKET_VERSIONS[${#SUPPORTED_BITBUCKET_VERSIONS[@]}-1]} + DB_DUMP_URL="${DATASETS_AWS_BUCKET}/$LAST_DATASET_VERSION/${DATASETS_SIZE}/${DB_DUMP_NAME}" + echo "Specific dataset version was not specified after --force flag, using the last available: ${LAST_DATASET_VERSION}" + echo "Dataset URL: ${DB_DUMP_URL}" fi else # No force flag diff --git a/app/util/bitbucket/upload_attachments.sh b/app/util/bitbucket/upload_attachments.sh index 1d761bf3a..ecc97e4ad 100644 --- a/app/util/bitbucket/upload_attachments.sh +++ b/app/util/bitbucket/upload_attachments.sh @@ -6,7 +6,7 @@ pgrep nfsd > /dev/null && echo "NFS found" || { echo NFS process was not found. ################### Variables section ################### # Bitbucket version variables BITBUCKET_VERSION_FILE="/media/atl/bitbucket/shared/bitbucket.version" -SUPPORTED_BITBUCKET_VERSIONS=(6.10.0 7.0.0) +SUPPORTED_BITBUCKET_VERSIONS=(6.10.5 7.0.5) BITBUCKET_VERSION=$(sudo su bitbucket -c "cat ${BITBUCKET_VERSION_FILE}") if [[ -z "$BITBUCKET_VERSION" ]]; then echo The $BITBUCKET_VERSION_FILE file does not exists or emtpy. Please check if BITBUCKET_VERSION_FILE variable \ @@ -33,13 +33,14 @@ if [[ ! "${SUPPORTED_BITBUCKET_VERSIONS[@]}" =~ "${BITBUCKET_VERSION}" ]]; then # Check if --force flag is passed into command if [[ "$1" == "--force" ]]; then # Check if passed Bitbucket version is in list of supported - if [[ "${SUPPORTED_BITBUCKET_VERSIONS[@]}" =~ "$2" ]]; then + if [[ " ${SUPPORTED_BITBUCKET_VERSIONS[@]} " =~ " ${2} " ]]; then ATTACHMENTS_TAR_URL="${DATASETS_AWS_BUCKET}/$2/${DATASETS_SIZE}/${ATTACHMENTS_TAR}" echo "Force mode. Dataset URL: ${ATTACHMENTS_TAR_URL}" else - echo "Correct dataset version was not specified after --force flag." - echo "Available datasets: ${SUPPORTED_BITBUCKET_VERSIONS[@]}" - exit 1 + LAST_DATASET_VERSION=${SUPPORTED_BITBUCKET_VERSIONS[${#SUPPORTED_BITBUCKET_VERSIONS[@]}-1]} + ATTACHMENTS_TAR_URL="${DATASETS_AWS_BUCKET}/$LAST_DATASET_VERSION/${DATASETS_SIZE}/${ATTACHMENTS_TAR}" + echo "Specific dataset version was not specified after --force flag, using the last available: ${LAST_DATASET_VERSION}" + echo "Dataset URL: ${ATTACHMENTS_TAR_URL}" fi else # No force flag diff --git a/app/util/conf.py b/app/util/conf.py index e1666b2fa..b979feef2 100644 --- a/app/util/conf.py +++ b/app/util/conf.py @@ -2,7 +2,7 @@ from util.project_paths import JIRA_YML, CONFLUENCE_YML, BITBUCKET_YML -TOOLKIT_VERSION = '3.0.1' +TOOLKIT_VERSION = '3.1.0' def read_yml_file(file): @@ -25,6 +25,7 @@ def __init__(self, config_yml): self.duration = env_settings['test_duration'] self.analytics_collector = env_settings['allow_analytics'] self.load_executor = env_settings['load_executor'] + self.webdriver_visible = env_settings['WEBDRIVER_VISIBLE'] @property def server_url(self): diff --git a/app/util/confluence/index-snapshot.sh b/app/util/confluence/index-snapshot.sh index 31b5f0985..a3d23037f 100644 --- a/app/util/confluence/index-snapshot.sh +++ b/app/util/confluence/index-snapshot.sh @@ -2,7 +2,8 @@ # Wait until index snapshot for Confluence DC is generated -SNAPSHOT="/media/atl/confluence/shared-home/index-snapshots/IndexSnapshot_main_index_*zip" +# Get the latest snapshot from the index-snapshots folder +SNAPSHOT=$(sudo su -c "ls -tr /media/atl/confluence/shared-home/index-snapshots/IndexSnapshot_main_index_*zip" | tail -1) TEMP_DIR="/var/atlassian/application-data/confluence/temp" TEMP_ZIP="/var/atlassian/application-data/confluence/index/*main_index*zip" diff --git a/app/util/confluence/populate_db.sh b/app/util/confluence/populate_db.sh index 0688dca1c..9a9eb97b3 100644 --- a/app/util/confluence/populate_db.sh +++ b/app/util/confluence/populate_db.sh @@ -20,7 +20,7 @@ CONFLUENCE_DB_PASS="Password1!" SELECT_CONFLUENCE_SETTING_SQL="select BANDANAVALUE from BANDANA where BANDANACONTEXT = '_GLOBAL' and BANDANAKEY = 'atlassian.confluence.settings';" # Confluence version variables -SUPPORTED_CONFLUENCE_VERSIONS=(6.13.8 7.0.4) +SUPPORTED_CONFLUENCE_VERSIONS=(6.13.13 7.0.5) if [[ ! $(systemctl status confluence) ]]; then echo "The Confluence service was not found on this host." \ @@ -54,13 +54,14 @@ if [[ ! "${SUPPORTED_CONFLUENCE_VERSIONS[@]}" =~ "${CONFLUENCE_VERSION}" ]]; the # Check if --force flag is passed into command if [[ "$1" == "--force" ]]; then # Check if passed Confluence version is in list of supported - if [[ "${SUPPORTED_CONFLUENCE_VERSIONS[@]}" =~ "$2" ]]; then + if [[ " ${SUPPORTED_CONFLUENCE_VERSIONS[@]} " =~ " ${2} " ]]; then DB_DUMP_URL="${DATASETS_AWS_BUCKET}/$2/${DATASETS_SIZE}/${DB_DUMP_NAME}" echo "Force mode. Dataset URL: ${DB_DUMP_URL}" else - echo "Correct dataset version was not specified after --force flag." - echo "Available datasets: ${SUPPORTED_CONFLUENCE_VERSIONS[@]}" - exit 1 + LAST_DATASET_VERSION=${SUPPORTED_CONFLUENCE_VERSIONS[${#SUPPORTED_CONFLUENCE_VERSIONS[@]}-1]} + DB_DUMP_URL="${DATASETS_AWS_BUCKET}/$LAST_DATASET_VERSION/${DATASETS_SIZE}/${DB_DUMP_NAME}" + echo "Specific dataset version was not specified after --force flag, using the last available: ${LAST_DATASET_VERSION}" + echo "Dataset URL: ${DB_DUMP_URL}" fi else # No force flag diff --git a/app/util/confluence/upload_attachments.sh b/app/util/confluence/upload_attachments.sh index 9a321f840..59d6c1045 100644 --- a/app/util/confluence/upload_attachments.sh +++ b/app/util/confluence/upload_attachments.sh @@ -4,7 +4,7 @@ ################### Variables section ################### # Confluence version variables CONFLUENCE_VERSION_FILE="/media/atl/confluence/shared-home/confluence.version" -SUPPORTED_CONFLUENCE_VERSIONS=(6.13.8 7.0.4) +SUPPORTED_CONFLUENCE_VERSIONS=(6.13.13 7.0.5) CONFLUENCE_VERSION=$(sudo su confluence -c "cat ${CONFLUENCE_VERSION_FILE}") if [[ -z "$CONFLUENCE_VERSION" ]]; then echo The $CONFLUENCE_VERSION_FILE file does not exists or emtpy. Please check if CONFLUENCE_VERSION_FILE variable \ @@ -38,13 +38,14 @@ if [[ ! "${SUPPORTED_CONFLUENCE_VERSIONS[@]}" =~ "${CONFLUENCE_VERSION}" ]]; the # Check if --force flag is passed into command if [[ "$1" == "--force" ]]; then # Check if passed Confluence version is in list of supported - if [[ "${SUPPORTED_CONFLUENCE_VERSIONS[@]}" =~ "$2" ]]; then + if [[ " ${SUPPORTED_CONFLUENCE_VERSIONS[@]} " =~ " ${2} " ]]; then ATTACHMENTS_TAR_URL="${DATASETS_AWS_BUCKET}/$2/${DATASETS_SIZE}/${ATTACHMENTS_TAR}" echo "Force mode. Dataset URL: ${ATTACHMENTS_TAR_URL}" else - echo "Correct dataset version was not specified after --force flag." - echo "Available datasets: ${SUPPORTED_CONFLUENCE_VERSIONS[@]}" - exit 1 + LAST_ATTACHMENTS_VERSION=${SUPPORTED_CONFLUENCE_VERSIONS[${#SUPPORTED_CONFLUENCE_VERSIONS[@]}-1]} + ATTACHMENTS_TAR_URL="${DATASETS_AWS_BUCKET}/$LAST_ATTACHMENTS_VERSION/${DATASETS_SIZE}/${ATTACHMENTS_TAR}" + echo "Specific dataset version was not specified after --force flag, using the last available: ${LAST_ATTACHMENTS_VERSION}" + echo "Dataset URL: ${ATTACHMENTS_TAR_URL}" fi else # No force flag diff --git a/app/util/data_preparation/bitbucket_prepare_data.py b/app/util/data_preparation/bitbucket_prepare_data.py index c69a51bf8..edaaa4c08 100644 --- a/app/util/data_preparation/bitbucket_prepare_data.py +++ b/app/util/data_preparation/bitbucket_prepare_data.py @@ -128,6 +128,10 @@ def __check_current_language(bitbucket_api): f'Please change your account language to "English (United States)"') +def __check_for_admin_permissions(bitbucket_api): + bitbucket_api.get_user_global_permissions() + + def main(): print("Started preparing data") @@ -137,6 +141,7 @@ def main(): client = BitbucketRestClient(url, BITBUCKET_SETTINGS.admin_login, BITBUCKET_SETTINGS.admin_password) __check_current_language(client) + __check_for_admin_permissions(client) dataset = __create_data_set(client) write_test_data_to_files(dataset) diff --git a/app/util/data_preparation/confluence_prepare_data.py b/app/util/data_preparation/confluence_prepare_data.py index 348643790..f8b1d847f 100644 --- a/app/util/data_preparation/confluence_prepare_data.py +++ b/app/util/data_preparation/confluence_prepare_data.py @@ -27,8 +27,10 @@ def generate_random_string(length=20): def __create_data_set(rest_client, rpc_client): dataset = dict() dataset[USERS] = __get_users(rest_client, rpc_client, CONFLUENCE_SETTINGS.concurrency) - dataset[PAGES] = __get_pages(rest_client, 5000) - dataset[BLOGS] = __get_blogs(rest_client, 5000) + perf_user = random.choice(dataset[USERS])['user'] + perf_user_api = ConfluenceRestClient(CONFLUENCE_SETTINGS.server_url, perf_user['username'], DEFAULT_USER_PASSWORD) + dataset[PAGES] = __get_pages(perf_user_api, 5000) + dataset[BLOGS] = __get_blogs(perf_user_api, 5000) print(f'Users count: {len(dataset[USERS])}') print(f'Pages count: {len(dataset[PAGES])}') print(f'Blogs count: {len(dataset[BLOGS])}') @@ -67,7 +69,8 @@ def __get_pages(confluence_api, count): ' and title !~ locust' # filter out pages created by locust ' and title !~ Home') # filter out space Home pages if not pages: - raise SystemExit("There are no Pages in Confluence") + raise SystemExit(f"There are no Pages in Confluence accessible by a random performance user: " + f"{confluence_api.user}") return pages @@ -77,7 +80,8 @@ def __get_blogs(confluence_api, count): 0, count, cql='type=blogpost' ' and title !~ Performance') if not blogs: - raise SystemExit(f"There are no Blog posts in Confluence") + raise SystemExit(f"There are no Blog posts in Confluence accessible by a random performance user: " + f"{confluence_api.user}") return blogs @@ -117,6 +121,12 @@ def __check_current_language(confluence_api): f'Please change your profile language to "English (US)"') +def __check_for_admin_permissions(confluence_api): + groups = confluence_api.get_groups_membership(CONFLUENCE_SETTINGS.admin_login) + if 'confluence-administrators' not in groups: + raise SystemExit(f"The '{confluence_api.user}' user does not have admin permissions.") + + def main(): print("Started preparing data") @@ -126,10 +136,9 @@ def main(): rest_client = ConfluenceRestClient(url, CONFLUENCE_SETTINGS.admin_login, CONFLUENCE_SETTINGS.admin_password) rpc_client = ConfluenceRpcClient(url, CONFLUENCE_SETTINGS.admin_login, CONFLUENCE_SETTINGS.admin_password) + __check_for_admin_permissions(rest_client) __is_remote_api_enabled(rest_client) - __is_collaborative_editing_enabled(rest_client) - __check_current_language(rest_client) dataset = __create_data_set(rest_client, rpc_client) diff --git a/app/util/data_preparation/jira_prepare_data.py b/app/util/data_preparation/jira_prepare_data.py index a9949770f..bce6e28c3 100644 --- a/app/util/data_preparation/jira_prepare_data.py +++ b/app/util/data_preparation/jira_prepare_data.py @@ -91,11 +91,13 @@ def __write_to_file(file_path, items): def __create_data_set(jira_api): dataset = dict() dataset[USERS] = __get_users(jira_api) - software_projects = __get_software_projects(jira_api) + perf_user = random.choice(dataset[USERS]) + perf_user_api = JiraRestClient(JIRA_SETTINGS.server_url, perf_user['name'], DEFAULT_USER_PASSWORD) + software_projects = __get_software_projects(perf_user_api) dataset[PROJECTS] = software_projects - dataset[ISSUES] = __get_issues(jira_api, software_projects) - dataset[SCRUM_BOARDS] = __get_boards(jira_api, 'scrum') - dataset[KANBAN_BOARDS] = __get_boards(jira_api, 'kanban') + dataset[ISSUES] = __get_issues(perf_user_api, software_projects) + dataset[SCRUM_BOARDS] = __get_boards(perf_user_api, 'scrum') + dataset[KANBAN_BOARDS] = __get_boards(perf_user_api, 'kanban') dataset[JQLS] = __generate_jqls(count=150) print(f'Users count: {len(dataset[USERS])}') print(f'Projects: {len(dataset[PROJECTS])}') @@ -114,7 +116,7 @@ def __get_issues(jira_api, software_projects): jql=f"project in ({jql_projects_str}) AND status != Closed order by key", max_results=8000 ) if not issues: - raise SystemExit("There are no issues in Jira") + raise SystemExit("There are no issues in Jira accessible by a random performance user: {jira_api.user}") return issues @@ -122,7 +124,8 @@ def __get_issues(jira_api, software_projects): def __get_boards(jira_api, board_type): boards = jira_api.get_boards(board_type=board_type, max_results=250) if not boards: - raise SystemExit(f"There are no {board_type} boards in Jira") + raise SystemExit( + f"There are no {board_type} boards in Jira accessible by a random performance user: {jira_api.user}") return boards @@ -131,7 +134,7 @@ def __get_users(jira_api): perf_users = jira_api.get_users(username=DEFAULT_USER_PREFIX, max_results=performance_users_count) users = generate_perf_users(api=jira_api, cur_perf_user=perf_users) if not users: - raise SystemExit("There are no users in Jira") + raise SystemExit("There are no users in Jira accessible by a random performance user: {jira_api.user}") return users @@ -141,8 +144,8 @@ def __get_software_projects(jira_api): software_projects = \ [f"{project['key']},{project['id']}" for project in all_projects if 'software' == project.get('projectTypeKey')] if not software_projects: - raise SystemExit("There are no software projects in Jira") - # Limit number of projects to avoid "Request header is too large" for further requests. + raise SystemExit( + f"There are no software projects in Jira accessible by a random performance user: {jira_api.user}") return software_projects @@ -153,6 +156,13 @@ def __check_current_language(jira_api): f'Please change your profile language to "English (United States) [Default]"') +def __check_for_admin_permissions(jira_api): + user_permissions = jira_api.get_user_permissions() + if not (user_permissions['permissions']['ADMINISTER']['havePermission'] or + user_permissions['permissions']['SYSTEM_ADMIN']['havePermission']): + raise SystemExit(f"The '{jira_api.user}' user does not have admin permissions.") + + def main(): print("Started preparing data") @@ -161,8 +171,8 @@ def main(): client = JiraRestClient(url, JIRA_SETTINGS.admin_login, JIRA_SETTINGS.admin_password) + __check_for_admin_permissions(client) __check_current_language(client) - dataset = __create_data_set(client) write_test_data_to_files(dataset) diff --git a/app/util/jira/populate_db.sh b/app/util/jira/populate_db.sh index 91e17c3fc..f09958b75 100644 --- a/app/util/jira/populate_db.sh +++ b/app/util/jira/populate_db.sh @@ -23,7 +23,7 @@ JIRA_DB_USER="postgres" JIRA_DB_PASS="Password1!" # Jira version variables -SUPPORTED_JIRA_VERSIONS=(8.0.3 7.13.6 8.5.0) +SUPPORTED_JIRA_VERSIONS=(8.0.3 7.13.15 8.5.6) JIRA_VERSION=$(sudo su jira -c "cat ${JIRA_VERSION_FILE}") if [[ -z "$JIRA_VERSION" ]]; then echo The $JIRA_VERSION_FILE file does not exists or emtpy. Please check if JIRA_VERSION_FILE variable \ @@ -50,7 +50,7 @@ if [[ ! "${SUPPORTED_JIRA_VERSIONS[@]}" =~ "${JIRA_VERSION}" ]]; then # Check if --force flag is passed into command if [[ "$1" == "--force" ]]; then # Check if passed Jira version is in list of supported - if [[ "${SUPPORTED_JIRA_VERSIONS[@]}" =~ "$2" ]]; then + if [[ " ${SUPPORTED_JIRA_VERSIONS[@]} " =~ " ${2} " ]]; then DB_DUMP_URL="${DATASETS_AWS_BUCKET}/$2/${DATASETS_SIZE}/${DB_DUMP_NAME}" echo "Force mode. Dataset URL: ${DB_DUMP_URL}" # If there is no DOWNGRADE_OPT - set it @@ -60,9 +60,10 @@ if [[ ! "${SUPPORTED_JIRA_VERSIONS[@]}" =~ "${JIRA_VERSION}" ]]; then echo "Flag -${DOWNGRADE_OPT} was set in ${JIRA_SETENV_FILE}" fi else - echo "Correct dataset version was not specified after --force flag." - echo "Available datasets: ${SUPPORTED_JIRA_VERSIONS[@]}" - exit 1 + LAST_DATASET_VERSION=${SUPPORTED_JIRA_VERSIONS[${#SUPPORTED_JIRA_VERSIONS[@]}-1]} + DB_DUMP_URL="${DATASETS_AWS_BUCKET}/$LAST_DATASET_VERSION/${DATASETS_SIZE}/${DB_DUMP_NAME}" + echo "Specific dataset version was not specified after --force flag, using the last available: ${LAST_DATASET_VERSION}" + echo "Dataset URL: ${DB_DUMP_URL}" fi else # No force flag diff --git a/app/util/jira/upload_attachments.sh b/app/util/jira/upload_attachments.sh index 56d7977b0..d667d1d28 100644 --- a/app/util/jira/upload_attachments.sh +++ b/app/util/jira/upload_attachments.sh @@ -4,7 +4,7 @@ ################### Variables section ################### # Jira version variables JIRA_VERSION_FILE="/media/atl/jira/shared/jira-software.version" -SUPPORTED_JIRA_VERSIONS=(8.0.3 7.13.6 8.5.0) +SUPPORTED_JIRA_VERSIONS=(8.0.3 7.13.15 8.5.6) JIRA_VERSION=$(sudo su jira -c "cat ${JIRA_VERSION_FILE}") if [[ -z "$JIRA_VERSION" ]]; then echo The $JIRA_VERSION_FILE file does not exists or emtpy. Please check if JIRA_VERSION_FILE variable \ @@ -38,13 +38,14 @@ if [[ ! "${SUPPORTED_JIRA_VERSIONS[@]}" =~ "${JIRA_VERSION}" ]]; then # Check if --force flag is passed into command if [[ "$1" == "--force" ]]; then # Check if passed Jira version is in list of supported - if [[ "${SUPPORTED_JIRA_VERSIONS[@]}" =~ "$2" ]]; then + if [[ " ${SUPPORTED_JIRA_VERSIONS[@]} " =~ " ${2} " ]]; then ATTACHMENTS_TAR_URL="${DATASETS_AWS_BUCKET}/$2/${DATASETS_SIZE}/${ATTACHMENTS_TAR}" echo "Force mode. Dataset URL: ${ATTACHMENTS_TAR_URL}" else - echo "Correct dataset version was not specified after --force flag." - echo "Available datasets: ${SUPPORTED_JIRA_VERSIONS[@]}" - exit 1 + LAST_DATASET_VERSION=${SUPPORTED_JIRA_VERSIONS[${#SUPPORTED_JIRA_VERSIONS[@]}-1]} + ATTACHMENTS_TAR_URL="${DATASETS_AWS_BUCKET}/$LAST_DATASET_VERSION/${DATASETS_SIZE}/${ATTACHMENTS_TAR}" + echo "Specific dataset version was not specified after --force flag, using the last available: ${LAST_DATASET_VERSION}" + echo "Dataset URL: ${ATTACHMENTS_TAR_URL}" fi else # No force flag diff --git a/docs/bitbucket/README.md b/docs/bitbucket/README.md index e05cc0ad3..2704dac36 100644 --- a/docs/bitbucket/README.md +++ b/docs/bitbucket/README.md @@ -68,15 +68,13 @@ Detailed log and stacktrace of Selenium PyTest fails are located in the `results Also, screenshots and HTMLs of Selenium fails are stared in the `results/bitbucket/YY-MM-DD-hh-mm-ss/error_artifacts` folder. ### Running Selenium tests with Browser GUI -There are two options of running Selenium tests with browser GUI: -1. In [bitbucket.yml](../../app/bitbucket.yml) file, set the `WEBDRIVER_VISIBLE: True`. -1. Set environment variable with the `export WEBDRIVER_VISIBLE=True` command. +In [bitbucket.yml](../../app/bitbucket.yml) file, set the `WEBDRIVER_VISIBLE: True`. ### Running Selenium tests locally without the Performance Toolkit 1. Activate virualenv for the Performance Toolkit. 1. Navigate to the selenium folder using the `cd app/selenium_ui` command. -1. Set browser visibility using the `export WEBDRIVER_VISIBLE=True` command. +1. In [bitbucket.yml](../../app/bitbucket.yml) file, set the `WEBDRIVER_VISIBLE: True`. 1. Run all Selenium PyTest tests with the `pytest bitbucket-ui.py` command. 1. To run one Selenium PyTest test (e.g., `test_1_selenium_view_dashboard`), execute the first login test and the required one with this command: diff --git a/docs/confluence/README.md b/docs/confluence/README.md index b374c5682..f4641a750 100644 --- a/docs/confluence/README.md +++ b/docs/confluence/README.md @@ -114,15 +114,13 @@ Detailed log and stacktrace of Selenium PyTest fails are located in the `results Also, screenshots and HTMLs of Selenium fails are stared in the `results/confluence/YY-MM-DD-hh-mm-ss/error_artifacts` folder. ### Running Selenium tests with Browser GUI -There are two options of running Selenium tests with browser GUI: -1. In [confluence.yml](../../app/confluence.yml) file, set the `WEBDRIVER_VISIBLE: True`. -1. Set environment variable with the `export WEBDRIVER_VISIBLE=True` command. +In [confluence.yml](../../app/confluence.yml) file, set the `WEBDRIVER_VISIBLE: True`. ### Running Selenium tests locally without the Performance Toolkit 1. Activate virualenv for the Performance Toolkit. 1. Navigate to the selenium folder using the `cd app/selenium_ui` command. -1. Set browser visibility using the `export WEBDRIVER_VISIBLE=True` command. +1. In [confluence.yml](../../app/confluence.yml) file, set the `WEBDRIVER_VISIBLE: True`. 1. Run all Selenium PyTest tests with the `pytest confluence_ui.py` command. 1. To run one Selenium PyTest test (e.g., `test_1_selenium_view_page`), execute the first login test and the required one with this command: diff --git a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md index 71d1244e7..c63d719bd 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md +++ b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md @@ -40,16 +40,30 @@ You are responsible for the cost of the AWS services used while running this Qui To reduce costs, we recommend you to keep your deployment up and running only during the performance runs. ### AWS cost estimation ### -[SIMPLE MONTHLY CALCULATOR](https://calculator.s3.amazonaws.com/index.html) provides an estimate of usage charges for AWS services based on certain information you provide. +[AWS Pricing Calculator](https://calculator.aws/) provides an estimate of usage charges for AWS services based on certain information you provide. Monthly charges will be based on your actual usage of AWS services, and may vary from the estimates the Calculator has provided. *The prices below are approximate and may vary depending on factors such as (region, instance type, deployment type of DB, etc.) | Stack | Estimated hourly cost ($) | | ----- | ------------------------- | -| One Node Bitbucket DC | 1 - 1.3 | -| Two Nodes Bitbucket DC | 1.5 - 1.8 | -| Four Nodes Bitbucket DC | 2.1 - 2.5 | +| One Node Bitbucket DC | 1.4 - 2.0 | +| Two Nodes Bitbucket DC | 1.7 - 2.5 | +| Four Nodes Bitbucket DC | 2.4 - 3.6 | + +#### Stop Bitbucket cluster nodes +To reduce AWS infrastructure costs you could stop Bitbucket nodes when the cluster is standing idle. +Bitbucket node might be stopped by using [Suspending and Resuming Scaling Processes](https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-suspend-resume-processes.html). + +To stop one node within the Bitbucket cluster follow the instructions: +1. Go to EC2 `Auto Scaling Groups` and open the necessary group to which belongs the node you want to stop. +1. Press `Edit` (in case you have New EC2 experience UI mode enabled, press `Edit` on `Advanced configuration`) and add `HealthCheck` to the `Suspended Processes`. Amazon EC2 Auto Scaling stops marking instances unhealthy as a result of EC2 and Elastic Load Balancing health checks. +1. Go to `Instances` and stop Bitbucket node. + +To return Bitbucket node into a working state follow the instructions: +1. Go to `Instances` and start Bitbucket node, wait a few minutes for Bitbucket node to become responsible. +1. Go to EC2 `Auto Scaling Groups` and open the necessary group to which belongs the node you want to start. +1. Press `Edit` (in case you have New EC2 experience UI mode enabled, press `Edit` on `Advanced configuration`) and remove `HealthCheck` from `Suspended Processes` of Auto Scaling Group. #### Quick Start parameters @@ -59,12 +73,12 @@ All important parameters are listed and described in this section. For all other | Parameter | Recommended Value | | --------- | ----------------- | -| Version | 6.10.0 or 7.0.0 | +| Version | 6.10.5 or 7.0.5 | The Data Center App Performance Toolkit officially supports: -- Bitbucket Platform release version: 7.0.0 -- Bitbucket [Long Term Support releases](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.10.0 +- Bitbucket Platform release version: 7.0.5 +- Bitbucket [Long Term Support releases](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.10.5 **Cluster nodes** @@ -73,6 +87,7 @@ The Data Center App Performance Toolkit officially supports: | Bitbucket cluster node instance type | [c5.2xlarge](https://aws.amazon.com/ec2/instance-types/c5/) | | Maximum number of cluster nodes | 1 | | Minimum number of cluster nodes | 1 | +| Cluster node instance volume size | 50 | We recommend [c5.2xlarge](https://aws.amazon.com/ec2/instance-types/c5/) to strike the balance between cost and hardware we see in the field for our enterprise customers. More info could be found in public [recommendations](https://confluence.atlassian.com/enterprise/infrastructure-recommendations-for-enterprise-bitbucket-instances-on-aws-970602035.html). @@ -93,7 +108,7 @@ The Data Center App Performance Toolkit framework is also set up for concurrency | Database instance class | [db.m4.large](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.DBInstanceClass.html#Concepts.DBInstanceClass.Summary) | | RDS Provisioned IOPS | 1000 | | Master password | Password1! | -| Enable RDS Multi-AZ deployment | true | +| Enable RDS Multi-AZ deployment | false | | Bitbucket database password | Password1! | | Database storage | 100 | diff --git a/docs/dc-apps-performance-toolkit-user-guide-confluence.md b/docs/dc-apps-performance-toolkit-user-guide-confluence.md index cdc9a42c4..41f2e7c11 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-confluence.md +++ b/docs/dc-apps-performance-toolkit-user-guide-confluence.md @@ -40,16 +40,30 @@ You are responsible for the cost of the AWS services used while running this Qui To reduce costs, we recommend you to keep your deployment up and running only during the performance runs. ### AWS cost estimation ### -[SIMPLE MONTHLY CALCULATOR](https://calculator.s3.amazonaws.com/index.html) provides an estimate of usage charges for AWS services based on certain information you provide. +[AWS Pricing Calculator](https://calculator.aws/) provides an estimate of usage charges for AWS services based on certain information you provide. Monthly charges will be based on your actual usage of AWS services, and may vary from the estimates the Calculator has provided. *The prices below are approximate and may vary depending on factors such as (region, instance type, deployment type of DB, etc.) | Stack | Estimated hourly cost ($) | | ----- | ------------------------- | -| One Node Confluence DC | 1.5 - 1.6 | -| Two Nodes Confluence DC | 1.7 - 2.5 | -| Four Nodes Confluence DC | 3.5 - 4.2 | +| One Node Confluence DC | 0.9 - 1.1 | +| Two Nodes Confluence DC | 1.3 - 1.8 | +| Four Nodes Confluence DC | 2.1 - 3.1 | + +#### Stop Confluence cluster nodes +To reduce AWS infrastructure costs you could stop Confluence nodes when the cluster is standing idle. +Confluence node might be stopped by using [Suspending and Resuming Scaling Processes](https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-suspend-resume-processes.html). + +To stop one node within the Confluence cluster follow the instructions: +1. Go to EC2 `Auto Scaling Groups` and open the necessary group to which belongs the node you want to stop. +1. Press `Edit` (in case you have New EC2 experience UI mode enabled, press `Edit` on `Advanced configuration`) and add `HealthCheck` to the `Suspended Processes`. Amazon EC2 Auto Scaling stops marking instances unhealthy as a result of EC2 and Elastic Load Balancing health checks. +1. Go to `Instances` and stop Confluence node. + +To return Confluence node into a working state follow the instructions: +1. Go to `Instances` and start Confluence node, wait a few minutes for Confluence node to become responsible. +1. Go to EC2 `Auto Scaling Groups` and open the necessary group to which belongs the node you want to start. +1. Press `Edit` (in case you have New EC2 experience UI mode enabled, press `Edit` on `Advanced configuration`) and remove `HealthCheck` from `Suspended Processes` of Auto Scaling Group. #### Quick Start parameters @@ -60,23 +74,23 @@ All important parameters are listed and described in this section. For all other | Parameter | Recommended Value | | --------- | ----------------- | | Collaborative editing mode | synchrony-local | -| Confluence Version | 6.13.8 or 7.0.4 | +| Confluence Version | 6.13.13 or 7.0.5 | The Data Center App Performance Toolkit officially supports: -- Confluence Platform release version: 7.0.4 -- Confluence [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.13.8 +- Confluence Platform release version: 7.0.5 +- Confluence [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.13.13 **Cluster nodes** | Parameter | Recommended Value | | ----------| ----------------- | -| Cluster node instance type | [c5.4xlarge](https://aws.amazon.com/ec2/instance-types/c5/) | +| Cluster node instance type | [m5.2xlarge](https://aws.amazon.com/ec2/instance-types/m5/) | | Maximum number of cluster nodes | 1 | | Minimum number of cluster nodes | 1 | | Cluster node instance volume size | 200 | -We recommend [c5.4xlarge](https://aws.amazon.com/ec2/instance-types/c5/) to strike the balance between cost and hardware we see in the field for our enterprise customers. More info could be found in public [recommendations](https://confluence.atlassian.com/enterprise/infrastructure-recommendations-for-enterprise-confluence-instances-on-aws-965544795.html). +We recommend [m5.2xlarge](https://aws.amazon.com/ec2/instance-types/m5/) to strike the balance between cost and hardware we see in the field for our enterprise customers. More info could be found in public [recommendations](https://confluence.atlassian.com/enterprise/infrastructure-recommendations-for-enterprise-confluence-instances-on-aws-965544795.html). The Data Center App Performance Toolkit framework is also set up for concurrency we expect on this instance size. As such, underprovisioning will likely show a larger performance impact than expected. @@ -87,7 +101,7 @@ The Data Center App Performance Toolkit framework is also set up for concurrency | Database instance class | [db.m5.xlarge](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.DBInstanceClass.html#Concepts.DBInstanceClass.Summary) | | RDS Provisioned IOPS | 1000 | | Master (admin) password | Password1! | -| Enable RDS Multi-AZ deployment | true | +| Enable RDS Multi-AZ deployment | false | | Application user database password | Password1! | | Database storage | 200 | @@ -100,7 +114,7 @@ The **Master (admin) password** will be used later when restoring the SQL databa | Parameter | Recommended Value | | --------- | ----------------- | | Trusted IP range | 0.0.0.0/0 _(for public access) or your own trusted IP range_ | -| Availability Zones | _Select two availability zones in your region. Both zones must support EFS (see [Supported AWS regions](https://confluence.atlassian.com/enterprise/getting-started-with-jira-data-center-on-aws-969535550.html#GettingstartedwithJiraDataCenteronAWS-SupportedAWSregions) for details)._ | +| Availability Zones | _Select two availability zones in your region_ | | Permitted IP range | 0.0.0.0/0 _(for public access) or your own trusted IP range_ | | Make instance internet facing | true | | Key Name | _The EC2 Key Pair to allow SSH access. See [Amazon EC2 Key Pairs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html) for more info._ | diff --git a/docs/dc-apps-performance-toolkit-user-guide-jira.md b/docs/dc-apps-performance-toolkit-user-guide-jira.md index 32c3b91b3..41c50b1c0 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-jira.md +++ b/docs/dc-apps-performance-toolkit-user-guide-jira.md @@ -40,16 +40,31 @@ You are responsible for the cost of the AWS services used while running this Qui To reduce costs, we recommend you to keep your deployment up and running only during the performance runs. ### AWS cost estimation ### -[SIMPLE MONTHLY CALCULATOR](https://calculator.s3.amazonaws.com/index.html) provides an estimate of usage charges for AWS services based on certain information you provide. +[AWS Pricing Calculator](https://calculator.aws/) provides an estimate of usage charges for AWS services based on certain information you provide. Monthly charges will be based on your actual usage of AWS services, and may vary from the estimates the Calculator has provided. *The prices below are approximate and may vary depending on factors such as (region, instance type, deployment type of DB, etc.) | Stack | Estimated hourly cost ($) | | ----- | ------------------------- | -| One Node Jira DC | 1 - 1.3 | -| Two Nodes Jira DC | 1.7 - 2.1 | -| Four Nodes Jira DC | 3.1 - 3.8 | +| One Node Jira DC | 0.8 - 1.1 | +| Two Nodes Jira DC | 1.2 - 1.7 | +| Four Nodes Jira DC | 2.0 - 3.0 | + +#### Stop Jira cluster nodes +To reduce AWS infrastructure costs you could stop Jira nodes when the cluster is standing idle. +Jira node might be stopped by using [Suspending and Resuming Scaling Processes](https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-suspend-resume-processes.html). + +To stop one node within the Jira cluster follow the instructions: +1. Go to EC2 `Auto Scaling Groups` and open the necessary group to which belongs the node you want to stop. +1. Press `Edit` (in case you have New EC2 experience UI mode enabled, press `Edit` on `Advanced configuration`) and add `HealthCheck` to the `Suspended Processes`. Amazon EC2 Auto Scaling stops marking instances unhealthy as a result of EC2 and Elastic Load Balancing health checks. +1. Go to `Instances` and stop Jira node. + +To return Jira node into a working state follow the instructions: +1. Go to `Instances` and start Jira node, wait a few minutes for Jira node to become responsible. +1. Go to EC2 `Auto Scaling Groups` and open the necessary group to which belongs the node you want to start. +1. Press `Edit` (in case you have New EC2 experience UI mode enabled, press `Edit` on `Advanced configuration`) and remove `HealthCheck` from `Suspended Processes` of Auto Scaling Group. + #### Quick Start parameters @@ -60,23 +75,23 @@ All important parameters are listed and described in this section. For all other | Parameter | Recommended Value | | --------- | ----------------- | | Jira Product | Software | -| Jira Version | 8.0.3 or 7.13.6 or 8.5.0 | +| Jira Version | 8.0.3 or 7.13.15 or 8.5.6 | The Data Center App Performance Toolkit officially supports: - Jira Platform release version: 8.0.3 -- Jira [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 7.13.6 and 8.5.0 +- Jira [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 7.13.15 and 8.5.6 **Cluster nodes** | Parameter | Recommended Value | | --------- | ----------------- | -| Cluster node instance type | [c5.4xlarge](https://aws.amazon.com/ec2/instance-types/c5/) | +| Cluster node instance type | [m5.2xlarge](https://aws.amazon.com/ec2/instance-types/m5/) | | Maximum number of cluster nodes | 1 | | Minimum number of cluster nodes | 1 | | Cluster node instance volume size | 100 | -We recommend [c5.4xlarge](https://aws.amazon.com/ec2/instance-types/c5/) to strike the balance between cost and hardware we see in the field for our enterprise customers. This differs from our [public recommendation on c4.8xlarge](https://confluence.atlassian.com/enterprise/infrastructure-recommendations-for-enterprise-jira-instances-on-aws-969532459.html) for production instances but is representative for a lot of our Jira Data Center customers. +We recommend [m5.2xlarge](https://aws.amazon.com/ec2/instance-types/m5/) to strike the balance between cost and hardware we see in the field for our enterprise customers. This differs from our [public recommendation on c4.8xlarge](https://confluence.atlassian.com/enterprise/infrastructure-recommendations-for-enterprise-jira-instances-on-aws-969532459.html) for production instances but is representative for a lot of our Jira Data Center customers. The Data Center App Performance Toolkit framework is also set up for concurrency we expect on this instance size. As such, underprovisioning will likely show a larger performance impact than expected. @@ -87,8 +102,9 @@ The Data Center App Performance Toolkit framework is also set up for concurrency | Database instance class | [db.m5.xlarge](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.DBInstanceClass.html#Concepts.DBInstanceClass.Summary) | | RDS Provisioned IOPS | 1000 | | Master (admin) password | Password1! | -| Enable RDS Multi-AZ deployment | true | +| Enable RDS Multi-AZ deployment | false | | Application user database password | Password1! | +| Database storage | 200 | {{% note %}} The **Master (admin) password** will be used later when restoring the SQL database dataset. If password value is not set to default, you'll need to change `DB_PASS` value manually in the restore database dump script (later in [Preloading your Jira deployment with an enterprise-scale dataset](#preloading)). @@ -99,7 +115,7 @@ The **Master (admin) password** will be used later when restoring the SQL databa | Parameter | Recommended Value | | --------- | ----------------- | | Trusted IP range | 0.0.0.0/0 _(for public access) or your own trusted IP range_ | -| Availability Zones | _Select two availability zones in your region. Both zones must support EFS (see [Supported AWS regions](https://confluence.atlassian.com/enterprise/getting-started-with-jira-data-center-on-aws-969535550.html#GettingstartedwithJiraDataCenteronAWS-SupportedAWSregions) for details)._ | +| Availability Zones | _Select two availability zones in your region_ | | Permitted IP range | 0.0.0.0/0 _(for public access) or your own trusted IP range_ | | Make instance internet facing | true | | Key Name | _The EC2 Key Pair to allow SSH access. See [Amazon EC2 Key Pairs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html) for more info._ | @@ -414,7 +430,7 @@ Jira 7 index time for 1M issues on a User Guide [recommended configuration](#qui {{% /note %}} {{% note %}} -If your Amazon RDS DB instance class is lower then db.m5.xlarge it is required to wait ~2 hours after previous reindex finish before starting a new one. +If your Amazon RDS DB instance class is lower than db.m5.xlarge it is required to wait ~2 hours after previous reindex finish before starting a new one. {{% /note %}} Benchmark your re-index time with your app installed: @@ -426,7 +442,7 @@ Benchmark your re-index time with your app installed: 1. **Take a screenshot of the acknowledgment screen** displaying the re-index time and Lucene index timing. 1. Attach the screenshot to your DCHELP ticket. -After attaching both screenshots to your DC HELP ticket, move on to performance results generation with an app installed: +After attaching both screenshots to your DCHELP ticket, move on to performance results generation with an app installed: ``` bash bzt jira.yml diff --git a/docs/jira/README.md b/docs/jira/README.md index d34651bea..b4f7604f7 100644 --- a/docs/jira/README.md +++ b/docs/jira/README.md @@ -114,15 +114,13 @@ Detailed log and stacktrace of Selenium PyTest fails are located in the `results Also, screenshots and HTMLs of Selenium fails are stared in the `results/jira/YY-MM-DD-hh-mm-ss/error_artifacts` folder. ### Running Selenium tests with Browser GUI -There are two options of running Selenium tests with browser GUI: -1. In [jira.yml](../../app/jira.yml) file, set the `WEBDRIVER_VISIBLE: True`. -1. Set environment variable with the `export WEBDRIVER_VISIBLE=True` command. +In [jira.yml](../../app/jira.yml) file, set the `WEBDRIVER_VISIBLE: True`. ### Running Selenium tests locally without the Performance Toolkit 1. Activate virualenv for the Performance Toolkit. 1. Navigate to the selenium folder using the `cd app/selenium_ui` command. -1. Set browser visibility using the `export WEBDRIVER_VISIBLE=True` command. +1. In [jira.yml](../../app/jira.yml) file, set the `WEBDRIVER_VISIBLE: True`. 1. Run all Selenium PyTest tests with the `pytest jira-ui.py` command. 1. To run one Selenium PyTest test (e.g., `test_1_selenium_view_issue`), execute the first login test and the required one with this command: diff --git a/requirements.txt b/requirements.txt index 22549867a..e50d94438 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -matplotlib==3.2.1 -pandas==1.0.4 -importlib-metadata==1.6.1 +matplotlib==3.3.0 +pandas==1.1.0 +importlib-metadata==1.7.0 zipp==2.2.0 # hard requirement of bzt 1.14.2 bzt==1.14.2 locustio==0.13.5 \ No newline at end of file