diff --git a/.dockerignore b/.dockerignore index 57e3f8b6f..f2da8af0e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ * !requirements.txt !Dockerfile +!bzt.tar.gz \ No newline at end of file diff --git a/CODEOWNERS b/CODEOWNERS index c4a79c422..5166b983c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ # Default code owners - Atlassian Data Center App Performance Toolkit -* @ometelytsia @SergeyMoroz0703 @OlehStefanyshyn \ No newline at end of file +* @vkavalenka80 @bgospodarek diff --git a/Dockerfile b/Dockerfile index 9ee9b0816..8d3053ca9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,11 +4,12 @@ # bzt run: docker run --shm-size=4g -v "$PWD:/dc-app-performance-toolkit" atlassian/dcapt jira.yml # interactive run: docker run -it --entrypoint="/bin/bash" -v "$PWD:/dc-app-performance-toolkit" atlassian/dcapt -FROM python:3.12-slim-bookworm +FROM python:3.13-slim-bookworm ENV APT_INSTALL="apt-get -y install --no-install-recommends" ARG CHROME_VERSION="latest" +ARG INCLUDE_BZT_TOOLS="false" ENV CHROME_LATEST_URL="https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb" ENV CHROME_VERSION_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb" @@ -29,9 +30,12 @@ RUN if [ "$CHROME_VERSION" = "latest" ]; then wget -O google-chrome.deb $CHROME_ COPY requirements.txt /tmp/requirements.txt RUN pip install --no-cache-dir -r /tmp/requirements.txt -RUN wget https://blazemeter-tools.s3.us-east-2.amazonaws.com/bzt.tar.gz -O /tmp/bzt.tar.gz && \ - tar -xzf /tmp/bzt.tar.gz -C /root && \ - rm /tmp/bzt.tar.gz +ADD . /tmp/ + +RUN if [ "$INCLUDE_BZT_TOOLS" = "true" ]; then \ + tar -xzf /tmp/bzt.tar.gz -C /root && \ + rm /tmp/bzt.tar.gz; \ + fi WORKDIR /dc-app-performance-toolkit/app diff --git a/README.md b/README.md index 3a3667d44..8ccba8118 100644 --- a/README.md +++ b/README.md @@ -5,30 +5,30 @@ 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): `9.12.12`, `9.4.25` and `10.0.1` Platform release + * Jira [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): `10.3.15`, `11.3.1` * Supported Jira Service Management versions: - * Jira Service Management [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): `5.12.12`, `5.4.25` and `10.0.1` Platform release + * Jira Service Management [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): `10.3.15`, `11.3.1` * Supported Confluence versions: - * Confluence [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): `8.5.14`, `7.19.26` and `9.0.2` Platform release + * Confluence [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): `9.2.13`, `10.2.2` * Supported Bitbucket Server versions: - * Bitbucket Server [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): `8.19.7`, `8.9.18` and `9.0.0` Platform release + * Bitbucket Server [Long Term Support release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): `8.19.24`, `9.4.12` and `10.0.1` Platform release * Supported Crowd versions: - * Crowd [release notes](https://confluence.atlassian.com/crowd/crowd-release-notes-199094.html): `6.0.0` + * Crowd [release notes](https://confluence.atlassian.com/crowd/crowd-release-notes-199094.html): `7.1.3` * Supported Bamboo versions: - * Bamboo [Long Term Support release](https://confluence.atlassian.com/bamboo/bamboo-release-notes-671089224.html): `9.6.6` and `10.0.1` Platform release + * Bamboo [Long Term Support release](https://confluence.atlassian.com/bamboo/bamboo-release-notes-671089224.html): `10.2.14` ## 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. +In case of technical questions, issues or problems with DC Apps Performance Toolkit, contact us for support in the [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. ## Installation and set up #### Dependencies -* Python 3.9 - 3.12 and pip +* Python 3.10 - 3.13 and pip * JDK 17 or JDK 21 * Google Chrome web browser * Git client (only for Bitbucket DC) @@ -76,6 +76,10 @@ source venv/bin/activate ``` pip install -r requirements.txt ``` +5. Optional: to use locust as main load-executor while running tests uninstall 'trio' and 'trio-websocket because they conflict with gevent monkey-patching used by bzt's Locust wrapper: +``` +pip uninstall -y trio trio-websocket +``` ### Linux setup Make sure that you have: diff --git a/app/bamboo.yml b/app/bamboo.yml index 595e1c577..0d968fd30 100644 --- a/app/bamboo.yml +++ b/app/bamboo.yml @@ -37,6 +37,11 @@ settings: parallel_plans_count: 40 # number of parallel plans execution start_plan_timeout: 60 # maximum timeout of plan to start default_dataset_plan_duration: 60 # expected plan execution duration + local_chrome_binary_path: # e.g. /usr/bin/google-chrome-stable. Leave it blank to use default Chrome binary path. + chrome_options: # Optional Chrome browser options for Selenium. Avoids manual edits to Python files in CI/CD. + arguments: # List of Chrome command-line arguments, e.g. ["--disable-gpu", "--remote-debugging-port=9222"] + experimental_options: # Chrome experimental options. 'prefs' are deep-merged with defaults, so you can override specific keys only. + services: - module: shellexec prepare: @@ -51,7 +56,7 @@ services: - python util/post_run/cleanup_results_dir.py - module: pip-install packages: - - selenium==4.25.0 + - selenium==4.39.0 execution: - scenario: jmeter executor: jmeter @@ -121,7 +126,7 @@ modules: httpsampler.ignore_failed_embedded_resources: "true" selenium: chromedriver: - version: "130.0.6723.91" # Supports Chrome version 130. You can refer to https://googlechromelabs.github.io/chrome-for-testing + version: "147.0.7727.57" # Supports Chrome version 147. You can refer to https://googlechromelabs.github.io/chrome-for-testing reporting: - data-source: sample-labels module: junit-xml diff --git a/app/bitbucket.yml b/app/bitbucket.yml index acffec757..bfc6ea54c 100644 --- a/app/bitbucket.yml +++ b/app/bitbucket.yml @@ -22,6 +22,11 @@ settings: LANGUAGE: en_US.utf8 allow_analytics: Yes # Allow sending basic run analytics to Atlassian. These analytics help us to understand how the tool is being used and help us to continue to invest in this tooling. For more details please see our README. environment_compliance_check: True # Pre-test environment compliance validation. Set to "False" to skip it. + local_chrome_binary_path: # e.g. /usr/bin/google-chrome-stable. Leave it blank to use default Chrome binary path. + chrome_options: # Optional Chrome browser options for Selenium. Avoids manual edits to Python files in CI/CD. + arguments: # List of Chrome command-line arguments, e.g. ["--disable-gpu", "--remote-debugging-port=9222"] + experimental_options: # Chrome experimental options. 'prefs' are deep-merged with defaults, so you can override specific keys only. + services: - module: shellexec prepare: @@ -37,7 +42,7 @@ services: - python util/post_run/cleanup_results_dir.py - module: pip-install packages: - - selenium==4.25.0 + - selenium==4.39.0 execution: - scenario: ${load_executor} concurrency: ${concurrency} @@ -87,7 +92,7 @@ modules: httpsampler.ignore_failed_embedded_resources: "true" selenium: chromedriver: - version: "130.0.6723.91" # Supports Chrome version 130. You can refer to https://googlechromelabs.github.io/chrome-for-testing + version: "147.0.7727.57" # Supports Chrome version 147. You can refer to https://googlechromelabs.github.io/chrome-for-testing reporting: - data-source: sample-labels module: junit-xml diff --git a/app/confluence.yml b/app/confluence.yml index 14b073efa..3a1fa3a51 100644 --- a/app/confluence.yml +++ b/app/confluence.yml @@ -38,6 +38,11 @@ settings: standalone_extension: 0 # By default disabled # Custom dataset section. custom_dataset_query: # Write CQL query to add CQL output to the app/datasets/confluence/custom_pages.csv, e.g. "title ~ 'AppPage*'" + local_chrome_binary_path: # e.g. /usr/bin/google-chrome-stable. Leave it blank to use default Chrome binary path. + chrome_options: # Optional Chrome browser options for Selenium. Avoids manual edits to Python files in CI/CD. + arguments: # List of Chrome command-line arguments, e.g. ["--disable-gpu", "--remote-debugging-port=9222"] + experimental_options: # Chrome experimental options. 'prefs' are deep-merged with defaults, so you can override specific keys only. + services: - module: shellexec prepare: @@ -52,7 +57,7 @@ services: - python util/post_run/cleanup_results_dir.py - module: pip-install packages: - - selenium==4.25.0 + - selenium==4.39.0 execution: - scenario: ${load_executor} executor: ${load_executor} @@ -114,7 +119,7 @@ modules: httpsampler.ignore_failed_embedded_resources: "true" selenium: chromedriver: - version: "130.0.6723.91" # Supports Chrome version 130. You can refer to https://googlechromelabs.github.io/chrome-for-testing + version: "147.0.7727.57" # Supports Chrome version 147. You can refer to https://googlechromelabs.github.io/chrome-for-testing reporting: - data-source: sample-labels module: junit-xml diff --git a/app/extension/bitbucket/extension_ui.py b/app/extension/bitbucket/extension_ui.py index 4c2ac35a0..72c4a75be 100644 --- a/app/extension/bitbucket/extension_ui.py +++ b/app/extension/bitbucket/extension_ui.py @@ -4,7 +4,7 @@ from selenium_ui.base_page import BasePage from selenium_ui.conftest import print_timing -from selenium_ui.bitbucket.pages.pages import LoginPage, GetStarted +from selenium_ui.bitbucket.pages.pages import LoginPage, GetStarted, AdminPage, PopupManager from util.conf import BITBUCKET_SETTINGS @@ -25,10 +25,16 @@ def app_specific_action(webdriver, datasets): # login_page = LoginPage(webdriver) # login_page.delete_all_cookies() # login_page.go_to() + # login_page.wait_for_page_loaded() # login_page.set_credentials(username=username, password=password) # login_page.submit_login() # get_started_page = GetStarted(webdriver) # get_started_page.wait_for_page_loaded() + # PopupManager(webdriver).dismiss_default_popup() + # get_started_page.close_whats_new_window() + # + # # uncomment below line to do web_sudo and authorise access to admin pages + # # AdminPage(webdriver).go_to(password=password) # app_specific_user_login(username='admin', password='admin') # measure() diff --git a/app/extension/confluence/extension_locust.py b/app/extension/confluence/extension_locust.py index 5d2053e4d..932368762 100644 --- a/app/extension/confluence/extension_locust.py +++ b/app/extension/confluence/extension_locust.py @@ -5,7 +5,10 @@ @confluence_measure("locust_app_specific_action") -# @run_as_specific_user(username='admin', password='admin') # run as specific user +# WebSudo is a feature that enhances security by requiring administrators to re-authenticate before +# accessing administrative functions within Atlassian applications. +# do_websudo=True requires user administrative rights, otherwise requests fail. +#@run_as_specific_user(username='admin', password='admin', do_websudo=False) # run as specific user def app_specific_action(locust): r = locust.get('/app/get_endpoint', catch_response=True) # call app-specific GET endpoint content = r.content.decode('utf-8') # decode response content diff --git a/app/extension/confluence/extension_ui.py b/app/extension/confluence/extension_ui.py index ea4aa40e2..18a3dfb02 100644 --- a/app/extension/confluence/extension_ui.py +++ b/app/extension/confluence/extension_ui.py @@ -4,7 +4,7 @@ from selenium_ui.base_page import BasePage from selenium_ui.conftest import print_timing -from selenium_ui.confluence.pages.pages import Login, AllUpdates +from selenium_ui.confluence.pages.pages import Login, AllUpdates, AdminPage from util.conf import CONFLUENCE_SETTINGS @@ -29,6 +29,9 @@ def app_specific_action(webdriver, datasets): # login_page.first_user_setup() # all_updates_page = AllUpdates(webdriver) # all_updates_page.wait_for_page_loaded() + # # uncomment below line to do web_sudo and authorise access to admin pages + # # AdminPage(webdriver).go_to(password=password) + # # app_specific_user_login(username='admin', password='admin') # measure() diff --git a/app/extension/jira/extension_locust.py b/app/extension/jira/extension_locust.py index 27ee33260..3ab85efcd 100644 --- a/app/extension/jira/extension_locust.py +++ b/app/extension/jira/extension_locust.py @@ -5,7 +5,10 @@ @jira_measure("locust_app_specific_action") -# @run_as_specific_user(username='admin', password='admin') # run as specific user +# WebSudo is a feature that enhances security by requiring administrators to re-authenticate before +# accessing administrative functions within Atlassian applications. +# do_websudo=True requires user administrative rights, otherwise requests fail. +#@run_as_specific_user(username='admin', password='admin', do_websudo=False) # run as specific user def app_specific_action(locust): r = locust.get('/app/get_endpoint', catch_response=True) # call app-specific GET endpoint content = r.content.decode('utf-8') # decode response content diff --git a/app/extension/jira/extension_ui.py b/app/extension/jira/extension_ui.py index 01aff462d..3ad126b68 100644 --- a/app/extension/jira/extension_ui.py +++ b/app/extension/jira/extension_ui.py @@ -4,7 +4,7 @@ from selenium_ui.base_page import BasePage from selenium_ui.conftest import print_timing -from selenium_ui.jira.pages.pages import Login +from selenium_ui.jira.pages.pages import Login, AdminPage from util.conf import JIRA_SETTINGS @@ -23,12 +23,17 @@ def app_specific_action(webdriver, datasets): # login_page = Login(webdriver) # login_page.delete_all_cookies() # login_page.go_to() + # login_page.wait_for_login_page_loaded() # login_page.set_credentials(username=username, password=password) + # login_page.wait_for_dashboard_or_first_login_loaded() # if login_page.is_first_login(): # login_page.first_login_setup() # if login_page.is_first_login_second_page(): # login_page.first_login_second_page_setup() # login_page.wait_for_page_loaded() + # # uncomment below line to do web_sudo and authorise access to admin pages + # # AdminPage(webdriver).go_to(password=password) + # # app_specific_user_login(username='admin', password='admin') # measure() diff --git a/app/extension/jsm/extension_locust_agents.py b/app/extension/jsm/extension_locust_agents.py index bdd05c9c9..9655b8b6b 100644 --- a/app/extension/jsm/extension_locust_agents.py +++ b/app/extension/jsm/extension_locust_agents.py @@ -5,7 +5,10 @@ @jsm_agent_measure('locust_agent_app_specific_action') -# @run_as_specific_user(username='admin', password='admin') # run as specific user +# WebSudo is a feature that enhances security by requiring administrators to re-authenticate before +# accessing administrative functions within Atlassian applications. +# do_websudo=True requires user administrative rights, otherwise requests fail. +# @run_as_specific_user(username='admin', password='admin', do_websudo=False) # run as specific user def app_specific_action(locust): r = locust.get('/app/get_endpoint', catch_response=True) # call app-specific GET endpoint content = r.content.decode('utf-8') # decode response content diff --git a/app/extension/jsm/extension_locust_customers.py b/app/extension/jsm/extension_locust_customers.py index 4102a82cf..38bd2e72b 100644 --- a/app/extension/jsm/extension_locust_customers.py +++ b/app/extension/jsm/extension_locust_customers.py @@ -5,7 +5,10 @@ @jsm_customer_measure('locust_customer_app_specific_action') -# @run_as_specific_user(username='admin', password='admin') # run as specific user +# WebSudo is a feature that enhances security by requiring administrators to re-authenticate before +# accessing administrative functions within Atlassian applications. +# do_websudo=True requires user administrative rights, otherwise requests fail. +# @run_as_specific_user(username='admin', password='admin', do_websudo=False) # run as specific user def app_specific_action(locust): r = locust.get('/app/get_endpoint', catch_response=True) # call app-specific GET endpoint content = r.content.decode('utf-8') # decode response content diff --git a/app/jira.yml b/app/jira.yml index c5127e650..17f14018f 100644 --- a/app/jira.yml +++ b/app/jira.yml @@ -38,6 +38,11 @@ settings: standalone_extension: 0 # By default disabled # Custom dataset section. custom_dataset_query: # Write JQL query to add JQL output to the app/datasets/jira/custom-issues.csv, e.g. "summary ~ 'AppIssue*'" + local_chrome_binary_path: # e.g. /usr/bin/google-chrome-stable. Leave it blank to use default Chrome binary path. + chrome_options: # Optional Chrome browser options for Selenium. Avoids manual edits to Python files in CI/CD. + arguments: # List of Chrome command-line arguments, e.g. ["--disable-gpu", "--remote-debugging-port=9222"] + experimental_options: # Chrome experimental options. 'prefs' are deep-merged with defaults, so you can override specific keys only. + services: - module: shellexec prepare: @@ -52,7 +57,7 @@ services: - python util/post_run/cleanup_results_dir.py - module: pip-install packages: - - selenium==4.25.0 + - selenium==4.39.0 execution: - scenario: ${load_executor} executor: ${load_executor} @@ -115,7 +120,7 @@ modules: httpsampler.ignore_failed_embedded_resources: "true" selenium: chromedriver: - version: "130.0.6723.91" # Supports Chrome version 130. You can refer to https://googlechromelabs.github.io/chrome-for-testing + version: "147.0.7727.57" # Supports Chrome version 147. You can refer to https://googlechromelabs.github.io/chrome-for-testing reporting: - data-source: sample-labels module: junit-xml diff --git a/app/jmeter/bitbucket.jmx b/app/jmeter/bitbucket.jmx index abab3ad3a..cc142974f 100644 --- a/app/jmeter/bitbucket.jmx +++ b/app/jmeter/bitbucket.jmx @@ -305,32 +305,42 @@ else{ - + + Detected the start of a redirect chain ${application.postfix}/login - true - POST + GET true false - - - false - ${admin_login} - = - true - j_username - - - false - ${admin_password} - = - true - j_password - - + + + + + Accept-Language + en-US,en;q=0.5 + + + Upgrade-Insecure-Requests + 1 + + + Content-Type + application/x-www-form-urlencoded + + + Accept-Encoding + gzip, deflate + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 + + + + 200 @@ -338,9 +348,139 @@ else{ Assertion.response_code false - 1 + 2 + + false + j_username + j_username + $1$ + NOT_FOUND + 1 + all + + false + + + + groovy + + + true + String loginform = vars.get("j_username"); + +if ("NOT_FOUND".equals(loginform)) { + vars.put("legacy_login_form", "false"); + log.info("legacy login flow detected"); +} else { + vars.put("legacy_login_form", "true"); + log.info("2SVlogin flow detected"); +} + + + + + groovy + + + true + log.info("Legacy login flow: ${legacy_login_form}") + + + + + + ${__groovy(vars.get("legacy_login_form") == 'false')} + false + true + + + + 2sv login flow + ${application.postfix}/rest/tsv/1.0/authenticate + POST + true + true + + + + false + {"username": "${admin_login}", + "password": "${admin_password}", + "rememberMe": "True", + "targetUrl": "" +} + = + + + + + + + + + Content-Type + application/json + + + + + + + 200 + + + Assertion.response_code + false + 2 + + + + + + ${__groovy(vars.get("legacy_login_form") == 'true')} + false + true + + + + ${application.postfix}/login + true + POST + true + false + + + + false + ${admin_login} + = + true + j_username + + + false + ${admin_password} + = + true + j_password + + + + + + + + 200 + + + Assertion.response_code + false + 1 + + + ${application.postfix}/rest/ssh/1.0/keys diff --git a/app/jmeter/confluence.jmx b/app/jmeter/confluence.jmx index 3b22e2aca..9df5f79a8 100644 --- a/app/jmeter/confluence.jmx +++ b/app/jmeter/confluence.jmx @@ -253,7 +253,7 @@ if ("NOT_FOUND".equals(loginform)) { - + ${__groovy(vars.get("legacy_login_form") == 'false')} false true @@ -301,7 +301,7 @@ if ("NOT_FOUND".equals(loginform)) { - + ${__groovy(vars.get("legacy_login_form") == 'true')} false true @@ -498,7 +498,7 @@ vars.put("confluence-version-number", "" + (versionNumbers[0 - + true -1 @@ -527,17 +527,6 @@ vars.put("confluence-version-number", "" + (versionNumbers[0 page_id,space_key - - datasets/confluence/cqls.csv - UTF-8 - , - cql_var - true - false - true - false - - , @@ -1486,7 +1475,7 @@ if ( sleep_time > 0 ) { - + 1 false 1 @@ -1556,7 +1545,136 @@ if ( sleep_time > 0 ) { - + + true + false + + + + ${application.postfix}/rest/api/search + true + GET + true + false + + + + true + cql + siteSearch~"confluence agreement" + = + true + + + false + 0 + = + true + start + + + false + 20 + = + true + limit + + + + + + + + + Accept-Language + en-US,en;q=0.5 + + + X-Requested-With + XMLHttpRequest + + + Accept-Encoding + gzip, deflate + + + Accept + application/json, text/javascript, */*; q=0.01 + + + + + + + {"results":[ + + + Assertion.response_data + false + 16 + + + + + + 1 + 0 + 0 + + + + true + + + // concurrency +def concurrency = ctx.getThreadGroup().getNumThreads() + +// Delays between each action in milli-seconds +def delay_between_transactions = (1000 * 3600) / (vars.get("total_actions_per_hr").toDouble() / concurrency.toDouble()) + +//Sets the pacing length based on the last requests response time. x is the time in ms +def start_time = Long.valueOf(vars.get("loop_start_time")) +def sleep_time = (delay_between_transactions - (System.currentTimeMillis() - start_time)) as int +def action_time = delay_between_transactions - sleep_time as int +def thread_number = ${__threadNum} as int + +log.info("concurrency: " + concurrency + ", delay_between_transactions: " + delay_between_transactions + ", thread_sleep: " + sleep_time + ", thread_number: " + thread_number + ", jmeter_search_cql action_time: " + action_time) + +if ( sleep_time > 0 ) { + return sleep_time +} else { + return 0 +} + + groovy + + + + + + 1 + false + 1 + ${perc_search_cql} + + + + 1 + 0 + 0 + + + + true + + + vars.put("loop_start_time",String.valueOf(System.currentTimeMillis())); + + groovy + + + + true false @@ -1572,7 +1690,7 @@ if ( sleep_time > 0 ) { true cql - siteSearch~'${cql_var}' + siteSearch~"shoulder trip discussion" = true @@ -5367,7 +5485,7 @@ if ( sleep_time > 0 ) { - + app_specific_username @@ -5382,13 +5500,22 @@ if ( sleep_time > 0 ) { - + + groovy + + + true + vars.remove("x_atl_token_view_issue") +vars.remove("x_editable_g2") + + + ${__groovy(vars.get("legacy_login_form") == 'false')} false true - + 2sv login flow ${application.postfix}/rest/tsv/1.0/authenticate POST @@ -5409,7 +5536,7 @@ if ( sleep_time > 0 ) { - + Content-Type @@ -5418,7 +5545,7 @@ if ( sleep_time > 0 ) { - + 200 @@ -5428,7 +5555,7 @@ if ( sleep_time > 0 ) { 2 - + groovy @@ -5436,7 +5563,7 @@ if ( sleep_time > 0 ) { vars.put("run_as_specific_user", "true") - + groovy @@ -5446,13 +5573,13 @@ if ( sleep_time > 0 ) { - + ${__groovy(vars.get("legacy_login_form") == 'true')} false true - + Detected the start of a redirect chain ${application.postfix}/dologin.action POST @@ -5499,7 +5626,7 @@ if ( sleep_time > 0 ) { - + Accept-Language @@ -5534,7 +5661,7 @@ if ( sleep_time > 0 ) { 2 - + groovy @@ -5542,7 +5669,7 @@ if ( sleep_time > 0 ) { vars.put("run_as_specific_user", "true") - + groovy @@ -5552,7 +5679,7 @@ if ( sleep_time > 0 ) { - + ${application.postfix}/rest/api/user/current true GET @@ -5563,7 +5690,7 @@ if ( sleep_time > 0 ) { - + groovy @@ -5586,13 +5713,151 @@ if (response_data.username != vars.get("app_specific_username")) { + + ${application.postfix}/ + true + GET + true + false + + + + + + + + + Accept-Language + en-US,en;q=0.5 + + + Upgrade-Insecure-Requests + 1 + + + Accept-Encoding + gzip, deflate + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 + + + + + + false + ajs-build-number + meta name=\"ajs-build-number\" content=\"(.*?)\"> + $1$ + NOT FOUND + 1 + all + + false + + + + false + ajs-remote-user-key + meta name=\"ajs-remote-user-key\" content=\"(.*?)\"> + $1$ + + 1 + all + false + + + + false + atlassian-token + meta name=\"ajs-atl-token\" content=\"(.*?)\"> + $1$ + + 1 + all + true + + + + false + confluence-version + meta name=\"ajs-version-number\" content=\"(.*?)\"> + $1$ + + 1 + all + true + + + + groovy + + + true + prev.setIgnore() + + + + + Works only with admin permissions + ${application.postfix}/doauthenticate.action + true + POST + true + false + + + + false + Confirm + = + true + authenticate + + + false + /admin/systeminfo.action + = + true + destination + + + false + ${app_specific_password} + = + true + password + + + + + + + + + X-Atlassian-Token + no-check + + + + + + groovy + + + true + prev.setIgnore() + + + - + true false - + ${application.postfix}/app/get_endpoint true GET @@ -5635,7 +5900,7 @@ if (response_data.username != vars.get("app_specific_username")) { - + ${application.postfix}/app/post_endpoint true GET @@ -5673,19 +5938,19 @@ if (response_data.username != vars.get("app_specific_username")) { - + ${run_as_specific_user} false true - + ${__groovy(vars.get("legacy_login_form") == 'false')} false true - + 2sv login flow ${application.postfix}/rest/tsv/1.0/authenticate POST @@ -5749,7 +6014,7 @@ if (response_data.username != vars.get("app_specific_username")) { true - + Detected the start of a redirect chain ${application.postfix}/dologin.action POST @@ -5849,7 +6114,7 @@ if (response_data.username != vars.get("app_specific_username")) { - + ${application.postfix}/rest/api/user/current true GET @@ -5860,7 +6125,7 @@ if (response_data.username != vars.get("app_specific_username")) { - + groovy @@ -5877,12 +6142,99 @@ if (response_data.username != vars.get("username")) { } else { prev.setIgnore() } + + + + + + ${application.postfix}/ + true + GET + true + false + + + + + + + + + Accept-Language + en-US,en;q=0.5 + + + Upgrade-Insecure-Requests + 1 + + + Accept-Encoding + gzip, deflate + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 + + + + + + false + ajs-build-number + meta name=\"ajs-build-number\" content=\"(.*?)\"> + $1$ + NOT FOUND + 1 + all + + false + + + + false + ajs-remote-user-key + meta name=\"ajs-remote-user-key\" content=\"(.*?)\"> + $1$ + + 1 + all + false + + + + false + atlassian-token + meta name=\"ajs-atl-token\" content=\"(.*?)\"> + $1$ + + 1 + all + true + + + + false + confluence-version + meta name=\"ajs-version-number\" content=\"(.*?)\"> + $1$ + + 1 + all + true + + + + groovy + + + true + prev.setIgnore() - + 1 0 0 diff --git a/app/jmeter/jira.jmx b/app/jmeter/jira.jmx index 8a783bfb6..a9d5c85ad 100644 --- a/app/jmeter/jira.jmx +++ b/app/jmeter/jira.jmx @@ -245,54 +245,11 @@ props.put("project_pages", String.valueOf(pages)); UTF-8 ${application.postfix}/login.jsp true - POST + GET true false - - - false - os_username - ${username} - = - true - - - false - os_password - ${password} - = - true - - - false - os_destination - - = - true - - - false - user_role - - = - true - - - false - atl_token - - = - true - - - true - login - Log In - = - true - - + @@ -329,6 +286,199 @@ props.put("project_pages", String.valueOf(pages)); + + + 200 + + + Assertion.response_code + false + 2 + + + + false + legacy_form + login-form-remember-me + $1$ + NOT_FOUND + 1 + all + + false + + + + groovy + + + true + String loginform = vars.get("legacy_form"); + +if ("NOT_FOUND".equals(loginform)) { + vars.put("legacy_login_form", "false"); + log.info("2sv flow detected"); +} else { + vars.put("legacy_login_form", "true"); + log.info("Legacy login flow detected"); +} + + + + + groovy + + + true + log.info("Legacy login flow: ${legacy_login_form}") + + + + + + ${__groovy(vars.get("legacy_login_form") == 'true')} + false + true + + + + Detected the start of a redirect chain + UTF-8 + ${application.postfix}/login.jsp + true + POST + true + false + + + + false + os_username + ${username} + = + true + + + false + os_password + ${password} + = + true + + + false + os_destination + + = + true + + + false + user_role + + = + true + + + false + atl_token + + = + true + + + true + login + Log In + = + true + + + + + + + + + Accept-Language + en-US,en;q=0.5 + + + Pragma + no-cache + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 + + + Upgrade-Insecure-Requests + 1 + + + Content-Type + application/x-www-form-urlencoded + + + Cache-Control + no-cache + + + Accept-Encoding + gzip, deflate + + + + + + + + ${__groovy(vars.get("legacy_login_form") == 'false')} + false + true + + + + 2sv login flow + ${application.postfix}/rest/tsv/1.0/authenticate + POST + true + true + + + + false + {"username": "${username}", + "password": "${password}", + "rememberMe": "True", + "targetUrl": "" +} + = + + + + + + + + + Content-Type + application/json + + + + + + + 200 + + + Assertion.response_code + false + 2 + + + ${application.postfix}/ @@ -2005,7 +2155,7 @@ if ( sleep_time > 0 ) { false - + ${application.postfix}/browse/${issue_key} true GET @@ -2045,7 +2195,7 @@ if ( sleep_time > 0 ) { - + false x_issue_id id="key-val" rel="(\d+)" @@ -7075,7 +7225,7 @@ if ( sleep_time > 0 ) { - + 1 false 1 @@ -7100,7 +7250,7 @@ if ( sleep_time > 0 ) { - + app_specific_username @@ -7115,144 +7265,321 @@ if ( sleep_time > 0 ) { - - Detected the start of a redirect chain - UTF-8 - ${application.postfix}/login.jsp + + ${__groovy(vars.get("legacy_login_form") == 'true')} + false + true + + + + Detected the start of a redirect chain + UTF-8 + ${application.postfix}/login.jsp + true + POST + true + false + + + + false + os_username + ${app_specific_username} + = + true + + + false + os_password + ${app_specific_password} + = + true + + + false + os_destination + + = + true + + + false + user_role + + = + true + + + false + atl_token + + = + true + + + true + login + Log In + = + true + + + + + + + + + Accept-Language + en-US,en;q=0.5 + + + Pragma + no-cache + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 + + + Upgrade-Insecure-Requests + 1 + + + Content-Type + application/x-www-form-urlencoded + + + Cache-Control + no-cache + + + Accept-Encoding + gzip, deflate + + + + + + groovy + + + true + vars.put("run_as_specific_user", "true") +prev.setIgnore() + + + + + + ${__groovy(vars.get("legacy_login_form") == 'false')} + false + true + + + + 2sv login flow + ${application.postfix}/rest/tsv/1.0/authenticate + POST + true + true + + + + false + {"username": "${app_specific_username}", + "password": "${app_specific_password}", + "rememberMe": "True", + "targetUrl": "" +} + = + + + + + + + + + Content-Type + application/json + + + + + + + 200 + + + Assertion.response_code + false + 2 + + + + groovy + + + true + vars.put("run_as_specific_user", "true") +prev.setIgnore() + + + + + + ${application.postfix}/rest/api/2/myself true - POST + GET true false - - - false - os_username - ${app_specific_username} - = - true - - - false - os_password - ${app_specific_password} - = - true - - - false - os_destination - - = - true - - - false - user_role - - = - true - - - false - atl_token - - = - true - - - true - login - Log In - = - true - - + - - - - Accept-Language + + groovy + + + true + import groovy.json.JsonSlurper; + +def slurper = new JsonSlurper() +response_data = slurper.parseText(prev.getResponseDataAsString()) + +if (response_data.name != vars.get("app_specific_username")) { + prev.setSuccessful(false) + prev.setResponseMessage("Login Failed: Your app_specific_username: '" + vars.get("app_specific_username") + "' or password is incorrect. Also, check the CAPTCHA for '" + vars.get("app_specific_username") + "' user.") + prev.setTestLogicalAction(org.apache.jmeter.threads.JMeterContext.TestLogicalAction.BREAK_CURRENT_LOOP) +} else { + log.info("Logged in as ${response_data.name}") + prev.setIgnore() +} + + + + + + ${application.postfix}/ + true + GET + true + false + + + + + + + + + Accept-Language en-US,en;q=0.5 - - Pragma - no-cache - - - Accept - text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 - Upgrade-Insecure-Requests 1 - - Content-Type - application/x-www-form-urlencoded - - - Cache-Control - no-cache - Accept-Encoding gzip, deflate + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 + - + + false + x-atl-token + name="atlassian-token" content="(.+?)" + $1$ + + 1 + all + true + + + groovy true - vars.put("run_as_specific_user", "true") + log.info("atl token ${x-atl-token}") prev.setIgnore() - - ${application.postfix}/rest/api/2/myself + + Works only with admin permissions + ${application.postfix}/secure/admin/WebSudoAuthenticate.jspa true - GET + POST true false - + + + false + /secure/admin/ViewSystemInfo.jspa + = + true + webSudoDestination + + + false + false + = + true + webSudoIsPost + + + false + ${app_specific_password} + = + true + webSudoPassword + + + false + ${x-atl-token} + = + true + atl_token + + - + + + + X-Atlassian-Token + no-check + + + + + groovy true - import groovy.json.JsonSlurper; - -def slurper = new JsonSlurper() -response_data = slurper.parseText(prev.getResponseDataAsString()) - -if (response_data.name != vars.get("app_specific_username")) { - prev.setSuccessful(false) - prev.setResponseMessage("Login Failed: Your app_specific_username: '" + vars.get("app_specific_username") + "' or password is incorrect. Also, check the CAPTCHA for '" + vars.get("app_specific_username") + "' user.") - prev.setTestLogicalAction(org.apache.jmeter.threads.JMeterContext.TestLogicalAction.BREAK_CURRENT_LOOP) -} else { - prev.setIgnore() -} - + prev.setIgnore() - + true false - + ${application.postfix}/app/get_endpoint true GET @@ -7263,7 +7590,7 @@ if (response_data.name != vars.get("app_specific_username")) { - + false app_id "id":"(.+?)" @@ -7273,7 +7600,7 @@ if (response_data.name != vars.get("app_specific_username")) { false - + false app_token "token":"(.+?)" @@ -7294,7 +7621,7 @@ if (response_data.name != vars.get("app_specific_username")) { - + ${application.postfix}/app/post_endpoint true POST @@ -7332,111 +7659,174 @@ if (response_data.name != vars.get("app_specific_username")) { - + ${run_as_specific_user} false true - - Detected the start of a redirect chain - UTF-8 - ${application.postfix}/login.jsp - true - POST - true - false - - - - false - os_username - ${username} - = - true - - - false - os_password - ${password} - = - true - - - false - os_destination - - = - true - - - false - user_role - - = - true - - - false - atl_token - - = - true - - - true - login - Log In - = - true - - - - + + ${__groovy(vars.get("legacy_login_form") == 'true')} + false + true + - - - - Accept-Language - en-US,en;q=0.5 - - - Pragma - no-cache - - - Accept - text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 - - - Upgrade-Insecure-Requests - 1 - - - Content-Type - application/x-www-form-urlencoded - - - Cache-Control - no-cache - - - Accept-Encoding - gzip, deflate - - - - - - groovy - - - true - prev.setIgnore() - - + + Detected the start of a redirect chain + UTF-8 + ${application.postfix}/login.jsp + true + POST + true + false + + + + false + os_username + ${username} + = + true + + + false + os_password + ${password} + = + true + + + false + os_destination + + = + true + + + false + user_role + + = + true + + + false + atl_token + + = + true + + + true + login + Log In + = + true + + + + + + + + + Accept-Language + en-US,en;q=0.5 + + + Pragma + no-cache + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 + + + Upgrade-Insecure-Requests + 1 + + + Content-Type + application/x-www-form-urlencoded + + + Cache-Control + no-cache + + + Accept-Encoding + gzip, deflate + + + + + + groovy + + + true + prev.setIgnore() + + + - + + ${__groovy(vars.get("legacy_login_form") == 'false')} + false + true + + + + 2sv login flow + ${application.postfix}/rest/tsv/1.0/authenticate + POST + true + true + + + + false + {"username": "${username}", + "password": "${password}", + "rememberMe": "True", + "targetUrl": "" +} + = + + + + + + + + + Content-Type + application/json + + + + + + + 200 + + + Assertion.response_code + false + 2 + + + + groovy + + + true + prev.setIgnore() + + + + + ${application.postfix}/rest/api/2/myself true GET @@ -7447,7 +7837,7 @@ if (response_data.name != vars.get("app_specific_username")) { - + groovy @@ -7460,8 +7850,9 @@ response_data = slurper.parseText(prev.getResponseDataAsString()) if (response_data.name != vars.get("username")) { prev.setSuccessful(false) prev.setResponseMessage("Re-login Failed: username: '" + vars.get("username") + "' failed to login. Check the CAPTCHA for '" + vars.get("username") + "' user.") - prev.setTestLogicalAction(org.apache.jmeter.threads.JMeterContext.TestLogicalAction.BREAK_CURRENT_LOOP) + prev.setTestLogicalAction(org.apache.jmeter.threads.JMeterContext.TestLogicalAction.BREAK_CURRENT_LOOP) } else { + log.info("Logged in back as ${response_data.name}") prev.setIgnore() } @@ -7470,14 +7861,67 @@ if (response_data.name != vars.get("username")) { + + ${application.postfix}/ + true + GET + true + false + + + + + + + + + Accept-Language + en-US,en;q=0.5 + + + Upgrade-Insecure-Requests + 1 + + + Accept-Encoding + gzip, deflate + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 + + + + + + false + x-atl-token + name="atlassian-token" content="(.+?)" + $1$ + + 1 + all + true + + + + groovy + + + true + log.info("atl token after ${x-atl-token}") +prev.setIgnore() + + + - + 1 0 0 - + true diff --git a/app/jmeter/jsm_agents.jmx b/app/jmeter/jsm_agents.jmx index 37c7ba051..6116fd4dc 100644 --- a/app/jmeter/jsm_agents.jmx +++ b/app/jmeter/jsm_agents.jmx @@ -253,54 +253,11 @@ UTF-8 ${application.postfix}/login.jsp true - POST + GET true false - - - false - os_username - ${username} - = - true - - - false - os_password - ${password} - = - true - - - false - os_destination - - = - true - - - false - user_role - - = - true - - - false - atl_token - - = - true - - - true - login - Log In - = - true - - + @@ -337,6 +294,199 @@ + + + 200 + + + Assertion.response_code + false + 2 + + + + false + legacy_form + login-form-remember-me + $1$ + NOT_FOUND + 1 + all + + false + + + + groovy + + + true + String loginform = vars.get("legacy_form"); + +if ("NOT_FOUND".equals(loginform)) { + vars.put("legacy_login_form", "false"); + log.info("2sv flow detected"); +} else { + vars.put("legacy_login_form", "true"); + log.info("Legacy login flow detected"); +} + + + + + groovy + + + true + log.info("Legacy login flow: ${legacy_login_form}") + + + + + + ${__groovy(vars.get("legacy_login_form") == 'true')} + false + true + + + + Detected the start of a redirect chain + UTF-8 + ${application.postfix}/login.jsp + true + POST + true + false + + + + false + os_username + ${username} + = + true + + + false + os_password + ${password} + = + true + + + false + os_destination + + = + true + + + false + user_role + + = + true + + + false + atl_token + + = + true + + + true + login + Log In + = + true + + + + + + + + + Accept-Language + en-US,en;q=0.5 + + + Pragma + no-cache + + + Accept + text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 + + + Upgrade-Insecure-Requests + 1 + + + Content-Type + application/x-www-form-urlencoded + + + Cache-Control + no-cache + + + Accept-Encoding + gzip, deflate + + + + + + + + ${__groovy(vars.get("legacy_login_form") == 'false')} + false + true + + + + 2sv login flow + ${application.postfix}/rest/tsv/1.0/authenticate + POST + true + true + + + + false + {"username": "${username}", + "password": "${password}", + "rememberMe": "True", + "targetUrl": "" +} + = + + + + + + + + + Content-Type + application/json + + + + + + + 200 + + + Assertion.response_code + false + 2 + + + ${application.postfix}/ diff --git a/app/jmeter/jsm_customers.jmx b/app/jmeter/jsm_customers.jmx index 753a1493d..d3806b87c 100644 --- a/app/jmeter/jsm_customers.jmx +++ b/app/jmeter/jsm_customers.jmx @@ -249,33 +249,18 @@ import org.apache.commons.io.FileUtils; false - - ${application.postfix}/servicedesk/customer/user/login + + ${application.postfix}/login.jsp true - POST + GET true false - - - true - os_password - ${password} - = - true - - - true - os_username - ${username} - = - true - - + - + Accept @@ -296,6 +281,147 @@ import org.apache.commons.io.FileUtils; + + false + legacy_form + login-form-remember-me + $1$ + NOT_FOUND + 1 + all + + false + + + + groovy + + + true + String loginform = vars.get("legacy_form"); + +if ("NOT_FOUND".equals(loginform)) { + vars.put("legacy_login_form", "false"); + log.info("2sv flow detected"); +} else { + vars.put("legacy_login_form", "true"); + log.info("Legacy login flow detected"); +} + + + + + groovy + + + true + log.info("Legacy login flow: ${legacy_login_form}") + + + + + + ${__groovy(vars.get("legacy_login_form") == 'true')} + false + true + + + + ${application.postfix}/servicedesk/customer/user/login + true + POST + true + false + + + + true + os_password + ${password} + = + true + + + true + os_username + ${username} + = + true + + + + + + + + + Accept + */* + + + X-Requested-With + XMLHttpRequest + + + Content-Type + application/x-www-form-urlencoded + + + X-Atlassian-Token + no-check + + + + + + + + ${__groovy(vars.get("legacy_login_form") == 'false')} + false + true + + + + 2sv login flow + ${application.postfix}/rest/tsv/1.0/authenticate + POST + true + true + + + + false + {"username": "${username}", + "password": "${password}", + "rememberMe": "True", + "targetUrl": "" +} + = + + + + + + + + + Content-Type + application/json + + + + + + + 200 + + + Assertion.response_code + false + 2 + + + ${application.postfix}/servicedesk/customer/portals diff --git a/app/jsm.yml b/app/jsm.yml index 9eb6eba43..9743b4f50 100644 --- a/app/jsm.yml +++ b/app/jsm.yml @@ -52,8 +52,12 @@ settings: customer_create_request: 16 customer_insight_view_request: 6 customer_standalone_extension: 0 + custom_dataset_query: # Write JQL query to add JQL output to the app/datasets/jsm/custom-issues.csv, e.g. "summary ~ 'AppRequests*'" + local_chrome_binary_path: # e.g. /usr/bin/google-chrome-stable. Leave it blank to use default Chrome binary path. + chrome_options: # Optional Chrome browser options for Selenium. Avoids manual edits to Python files in CI/CD. + arguments: # List of Chrome command-line arguments, e.g. ["--disable-gpu", "--remote-debugging-port=9222"] + experimental_options: # Chrome experimental options. 'prefs' are deep-merged with defaults, so you can override specific keys only. - custom_dataset_query: "" # Write JQL query to add JQL output to the app/datasets/jsm/custom-issues.csv, e.g. "summary ~ 'AppRequests*'" services: - module: shellexec prepare: @@ -68,7 +72,7 @@ services: - python util/post_run/cleanup_results_dir.py - module: pip-install packages: - - selenium==4.25.0 + - selenium==4.39.0 execution: - scenario: ${load_executor}_agents executor: ${load_executor} @@ -167,7 +171,7 @@ modules: httpsampler.ignore_failed_embedded_resources: "true" selenium: chromedriver: - version: "130.0.6723.91" # Supports Chrome version 130. You can refer to https://googlechromelabs.github.io/chrome-for-testing + version: "147.0.7727.57" # Supports Chrome version 147. You can refer to https://googlechromelabs.github.io/chrome-for-testing reporting: - data-source: sample-labels module: junit-xml diff --git a/app/locustio/common_utils.py b/app/locustio/common_utils.py index 1c9149ff8..c751ecbd0 100644 --- a/app/locustio/common_utils.py +++ b/app/locustio/common_utils.py @@ -1,22 +1,20 @@ -import functools - -from locust import events -import time import csv -import re +import functools +import inspect +import json import logging import random -import string -import json +import re import socket -from logging.handlers import RotatingFileHandler +import string +import time from datetime import datetime +from logging.handlers import RotatingFileHandler + +from locust import exception, TaskSet, events + from util.conf import JIRA_SETTINGS, CONFLUENCE_SETTINGS, JSM_SETTINGS, BAMBOO_SETTINGS, BaseAppSettings from util.project_paths import ENV_TAURUS_ARTIFACT_DIR -from locust import exception -import inspect -from locust import TaskSet - TEXT_HEADERS = { 'Accept-Language': 'en-US,en;q=0.5', @@ -66,12 +64,27 @@ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', "X-Atlassian-Token": "no-check" } +LOGIN_BODY = { + 'os_username': '', + 'os_password': '', + 'os_destination': '', + 'os_cookie': True, + 'user_role': '', + 'atl_token': '', + 'login': 'Log in' +} +LOGIN_BODY_CONFLUENCE = { + 'os_username': '', + 'os_password': '', + 'os_cookie': True, + 'os_destination': '', + 'login': 'Log in' + } -JIRA_API_URL = '/' -CONFLUENCE_API_URL = '/' -BAMBOO_API_URL = '/' JIRA_TOKEN_PATTERN = r'name="atlassian-token" content="(.+?)">' CONFLUENCE_TOKEN_PATTERN = r'"ajs-atl-token" content="(.+?)"' +CONFLUENCE_KEYBOARD_HASH_RE = 'name=\"ajs-keyboardshortcut-hash\" content=\"(.*?)\">' +CONFLUENCE_BUILD_NUMBER_RE = 'meta name=\"ajs-build-number\" content=\"(.*?)\"' JIRA = 'jira' JSM = 'jsm' @@ -345,7 +358,125 @@ def raise_if_login_failed(locust): raise exception.StopUser('Action login_and_view_dashboard failed') -def run_as_specific_user(username=None, password=None): +def do_confluence_login(locust, usr, pwd, do_websudo=False): + locust.client.cookies.clear() + r = locust.get('/dologin.action', catch_response=True) + content = r.content.decode('utf-8') + is_legacy_login_form = 'loginform' in content + + if is_legacy_login_form: + + login_body = LOGIN_BODY_CONFLUENCE + login_body['os_username'] = usr + login_body['os_password'] = pwd + + locust.post('/dologin.action', + login_body, + TEXT_HEADERS, + catch_response=True) + else: + + login_body = {'username': usr, + 'password': pwd, + 'rememberMe': 'True', + 'targetUrl': '' + } + + headers = { + "Content-Type": "application/json" + } + + # 15 /rest/tsv/1.0/authenticate + locust.post('/rest/tsv/1.0/authenticate', + json=login_body, + headers=headers, + catch_response=True) + r = locust.get(url='/', catch_response=True) + content = r.content.decode('utf-8') + + if 'Log Out' not in content: + print(f'Login with {usr}, {pwd} failed: {content}') + assert 'Log Out' in content, 'User authentication failed.' + print(f'User {usr} is successfully logged in back') + keyboard_hash = fetch_by_re(CONFLUENCE_KEYBOARD_HASH_RE, content) + build_number = fetch_by_re(CONFLUENCE_BUILD_NUMBER_RE, content) + token = fetch_by_re(locust.session_data_storage['token_pattern'], content) + + # 20 index.action + locust.get('/index.action', catch_response=True) + + locust.session_data_storage['build_number'] = build_number + locust.session_data_storage['keyboard_hash'] = keyboard_hash + locust.session_data_storage['username'] = usr + locust.session_data_storage['password'] = pwd + locust.session_data_storage['token'] = token + + if do_websudo: + auth_body = { + 'authenticate': 'Confirm', + 'destination': '/admin/systeminfo.action', + 'password': pwd, + } + system_info_html = locust.post(url='/doauthenticate.action', data=auth_body, + headers={'X-Atlassian-Token': 'no-check'}, catch_response=True) + + +def do_login_jira(locust, usr, pwd, do_websudo=False): + locust.client.cookies.clear() + body = LOGIN_BODY + body['os_username'] = usr + body['os_password'] = pwd + + legacy_form = False + + # Check if 2sv login form + r = locust.get('/login.jsp', catch_response=True) + content = r.content.decode('utf-8') + if 'login-form-remember-me' in content: + legacy_form = True + + # 100 /login.jsp + if legacy_form: + locust.post('/login.jsp', body, + TEXT_HEADERS, + catch_response=True) + else: + login_body = {'username': usr, + 'password': pwd, + 'rememberMe': 'True', + 'targetUrl': '' + } + + headers = { + "Content-Type": "application/json" + } + + # 15 /rest/tsv/1.0/authenticate + locust.post('/rest/tsv/1.0/authenticate', + json=login_body, + headers=headers, + catch_response=True) + + r = locust.get('/', catch_response=True) + if not r.content: + raise Exception('Please check server hostname in jira.yml file') + if locust.session_data_storage['token_pattern']: + content = r.content.decode('utf-8') + token = fetch_by_re(locust.session_data_storage['token_pattern'], content) + locust.session_data_storage["token"] = token + + if do_websudo: + auth_body = { + 'webSudoDestination': '/secure/admin/ViewSystemInfo.jspa', + 'webSudoIsPost': False, + 'webSudoPassword': pwd, + 'atl_token': locust.session_data_storage["token"] + } + system_info_html = locust.post(url='/secure/admin/WebSudoAuthenticate.jspa', data=auth_body, + headers={'X-Atlassian-Token': 'no-check'}, catch_response=True) + + +def run_as_specific_user(username=None, password=None, do_websudo=False): if not (username and password): raise SystemExit(f'The credentials are not valid: {{username: {username}, password: {password}}}.') @@ -363,41 +494,30 @@ def wrapper(*args, **kwargs): session_user_name = locust.session_data_storage["username"] session_user_password = locust.session_data_storage["password"] app = locust.session_data_storage['app'] + locust.session_data_storage['token_pattern'] = None app_type = locust.session_data_storage.get('app_type', None) token_pattern = None # Jira or JSM Agent - redefine token value if app == JIRA or (app == JSM and app_type == TYPE_AGENT): - url = JIRA_API_URL - token_pattern = JIRA_TOKEN_PATTERN - # JSM Customer - elif app == JSM and app_type == TYPE_CUSTOMER: - url = JIRA_API_URL + locust.session_data_storage['token_pattern'] = JIRA_TOKEN_PATTERN # Confluence - redefine token value elif app == CONFLUENCE: - url = CONFLUENCE_API_URL - token_pattern = CONFLUENCE_TOKEN_PATTERN - # Bamboo - elif app == BAMBOO: - url = BAMBOO_API_URL - else: - raise Exception(f'The "{app}" application type is not known.') - - def do_login(usr, pwd): - locust.client.cookies.clear() - r = locust.get(url, auth=(usr, pwd), catch_response=True) - if token_pattern: - content = r.content.decode('utf-8') - token = fetch_by_re(token_pattern, content) - locust.session_data_storage["token"] = token + locust.session_data_storage['token_pattern'] = CONFLUENCE_TOKEN_PATTERN # send requests by the specific user - do_login(usr=username, pwd=password) + if app == JIRA or (app == JSM and app_type == TYPE_AGENT): + do_login_jira(locust, username, password, do_websudo) + func(*args, **kwargs) + do_login_jira(locust, session_user_name, session_user_password) - func(*args, **kwargs) + if app == CONFLUENCE: + do_confluence_login(locust, username, password, do_websudo) + func(*args, **kwargs) + do_confluence_login(locust, session_user_name, session_user_password) - # send requests by the session user - do_login(usr=session_user_name, pwd=session_user_password) + if app not in [CONFLUENCE, JIRA, JSM]: + raise SystemExit(f"Unsupported app type: {app}") else: raise SystemExit(f"There is no 'locust' object in the '{func.__name__}' function.") diff --git a/app/locustio/confluence/http_actions.py b/app/locustio/confluence/http_actions.py index 805e3ec89..065fa131b 100644 --- a/app/locustio/confluence/http_actions.py +++ b/app/locustio/confluence/http_actions.py @@ -12,6 +12,9 @@ logger = init_logger(app_type='confluence') confluence_dataset = confluence_datasets() +TWO_WORDS_CQL = 'confluence agreement' +THREE_WORDS_CQL = 'shoulder trip discussion' + @confluence_measure('locust_login_and_view_dashboard') def login_and_view_dashboard(locust): @@ -126,9 +129,6 @@ def view_page(locust): headers=RESOURCE_HEADERS, catch_response=True) - # 120 rest/helptips/1.0/tips - locust.get('/rest/helptips/1.0/tips', catch_response=True) - # 130 rest/inlinecomments/1.0/comments r = locust.get(f'/rest/inlinecomments/1.0/comments' f'?containerId={parsed_page_id}' @@ -310,9 +310,6 @@ def view_blog(locust): headers=RESOURCE_HEADERS, catch_response=True) - # 360 rest/helptips/1.0/tips - locust.get('/rest/helptips/1.0/tips', catch_response=True) - # 370 rest/inlinecomments/1.0/comments r = locust.get(f'/rest/inlinecomments/1.0/comments' f'?containerId={blog_id}' @@ -402,9 +399,8 @@ def view_blog(locust): catch_response=True) -def search_cql_and_view_results(locust): +def search_cql_two_words_and_view_results(locust): raise_if_login_failed(locust) - cql = random.choice(confluence_dataset["cqls"])[0] @confluence_measure('locust_search_cql:recently_viewed') def search_recently_viewed(): @@ -413,11 +409,11 @@ def search_recently_viewed(): '?limit=8', catch_response=True) - @confluence_measure('locust_search_cql:search_results') + @confluence_measure('locust_search_cql:search_results_2_words') def search_cql(): # 530 rest/api/search r = locust.get(f"/rest/api/search" - f"?cql=siteSearch~'{cql}'" + f"?cql=siteSearch~'{TWO_WORDS_CQL}'" f"&start=0" f"&limit=20", catch_response=True) @@ -435,6 +431,31 @@ def search_cql(): search_recently_viewed() search_cql() +def search_cql_three_words(locust): + raise_if_login_failed(locust) + + @confluence_measure('locust_search_cql:search_results_3_words') + def search_cql(): + # 530 rest/api/search + r = locust.get(f"/rest/api/search" + f"?cql=siteSearch~'{THREE_WORDS_CQL}'" + f"&start=0" + f"&limit=20", + catch_response=True) + + if '{"results":[' not in r.content.decode('utf-8'): + logger.locust_info(r.content.decode('utf-8')) + content = r.content.decode('utf-8') + if 'results' not in content: + logger.error(f"Search cql failed: {content}") + assert 'results' in content, "Search cql failed." + + # 540 rest/mywork/latest/status/notification/count + locust.get('/rest/mywork/latest/status/notification/count', catch_response=True) + + search_cql() + + def open_editor_and_create_blog(locust): params = CreateBlog() @@ -664,9 +685,6 @@ def create_blog(): locust.get(f'/s/en_GB/{build_number}/{keyboard_hash}/_/images/icons/profilepics/add_profile_pic.svg', catch_response=True) - # 810 rest/helptips/1.0/tips - locust.get('/rest/helptips/1.0/tips', catch_response=True) - # 820 rest/mywork/latest/status/notification/count locust.get(f'/rest/mywork/latest/status/notification/count' f'?pageid={content_id}' @@ -948,9 +966,6 @@ def create_page(): headers=RESOURCE_HEADERS, catch_response=True) - # 1190 rest/helptips/1.0/tips - locust.get('/rest/helptips/1.0/tips', catch_response=True) - # 1200 rest/inlinecomments/1.0/comments locust.get(f'/rest/inlinecomments/1.0/comments' f'?containerId={locust.session_data_storage["content_id"]}' @@ -1207,9 +1222,6 @@ def edit_page(): headers=RESOURCE_HEADERS, catch_response=True) - # 1510 rest/helptips/1.0/tips - locust.get('/rest/helptips/1.0/tips', catch_response=True) - locust.get(f'/rest/inlinecomments/1.0/comments' f'?containerId={locust.session_data_storage["content_id"]}' f'&_={timestamp_int()}', diff --git a/app/locustio/confluence/locustfile.py b/app/locustio/confluence/locustfile.py index 9e9a5ab8e..b60552ef4 100644 --- a/app/locustio/confluence/locustfile.py +++ b/app/locustio/confluence/locustfile.py @@ -2,9 +2,9 @@ from extension.confluence.extension_locust import app_specific_action from locustio.common_utils import LocustConfig, MyBaseTaskSet -from locustio.confluence.http_actions import login_and_view_dashboard, view_dashboard, view_blog, \ - search_cql_and_view_results, open_editor_and_create_blog, create_and_edit_page, comment_page, view_attachments, \ - upload_attachments, like_page, view_page +from locustio.confluence.http_actions import login_and_view_dashboard, view_dashboard, view_blog,\ + open_editor_and_create_blog, create_and_edit_page, comment_page, view_attachments, \ + upload_attachments, like_page, view_page, search_cql_two_words_and_view_results, search_cql_three_words from util.conf import CONFLUENCE_SETTINGS config = LocustConfig(config_yml=CONFLUENCE_SETTINGS) @@ -30,7 +30,11 @@ def view_blog_action(self): @task(config.percentage('search_cql')) def search_cql_action(self): - search_cql_and_view_results(self) + search_cql_two_words_and_view_results(self) + + @task(config.percentage('search_cql')) + def search_cql_action(self): + search_cql_three_words(self) @task(config.percentage('create_blog')) def create_blog_action(self): diff --git a/app/locustio/confluence/requests_params.py b/app/locustio/confluence/requests_params.py index e4d7d26a6..f0a9cd706 100644 --- a/app/locustio/confluence/requests_params.py +++ b/app/locustio/confluence/requests_params.py @@ -1,7 +1,6 @@ # flake8: noqa from locustio.common_utils import read_input_file, BaseResource -from util.project_paths import (CONFLUENCE_PAGES, CONFLUENCE_BLOGS, CONFLUENCE_USERS, CONFLUENCE_STATIC_CONTENT, - CONFLUENCE_CQLS) +from util.project_paths import (CONFLUENCE_PAGES, CONFLUENCE_BLOGS, CONFLUENCE_USERS, CONFLUENCE_STATIC_CONTENT) def confluence_datasets(): @@ -9,7 +8,6 @@ def confluence_datasets(): data_sets["pages"] = read_input_file(CONFLUENCE_PAGES) data_sets["blogs"] = read_input_file(CONFLUENCE_BLOGS) data_sets["users"] = read_input_file(CONFLUENCE_USERS) - data_sets["cqls"] = read_input_file(CONFLUENCE_CQLS) data_sets['static-content'] = read_input_file(CONFLUENCE_STATIC_CONTENT) return data_sets diff --git a/app/locustio/jira/http_actions.py b/app/locustio/jira/http_actions.py index ec5a3eb9e..61a7808ee 100644 --- a/app/locustio/jira/http_actions.py +++ b/app/locustio/jira/http_actions.py @@ -25,11 +25,39 @@ def login_and_view_dashboard(locust): body = params.login_body body['os_username'] = user[0] body['os_password'] = user[1] + legacy_form = False + + # Check if 2sv login form + r = locust.get('/login.jsp', catch_response=True) + content = r.content.decode('utf-8') + if 'login-form-remember-me' in content: + legacy_form = True + # 100 /login.jsp - locust.post('/login.jsp', body, - TEXT_HEADERS, - catch_response=True) + if legacy_form: + locust.post('/login.jsp', body, + TEXT_HEADERS, + catch_response=True) + logger.locust_info(f"Legacy login flow for user {user[0]}") + else: + logger.locust_info(f"2SV login flow for user {user[0]}") + + login_body = {'username': user[0], + 'password': user[1], + 'rememberMe': 'True', + 'targetUrl': '' + } + + headers = { + "Content-Type": "application/json" + } + + # 15 /rest/tsv/1.0/authenticate + locust.post('/rest/tsv/1.0/authenticate', + json=login_body, + headers=headers, + catch_response=True) r = locust.get('/', catch_response=True) if not r.content: diff --git a/app/locustio/jsm/agents/agents_http_actions.py b/app/locustio/jsm/agents/agents_http_actions.py index 562010012..2b4de9e5b 100644 --- a/app/locustio/jsm/agents/agents_http_actions.py +++ b/app/locustio/jsm/agents/agents_http_actions.py @@ -51,7 +51,35 @@ def agent_login_and_view_dashboard(locust, jsm_agent_dataset): body['os_username'] = user[0] body['os_password'] = user[1] - locust.post('/login.jsp', body, TEXT_HEADERS, catch_response=True) + legacy_form = False + + # is 2sv login form + r = locust.get('/login.jsp', catch_response=True) + if b'login-form-remember-me' in r.content: + legacy_form = True + + if legacy_form: + locust.post('/login.jsp', body, TEXT_HEADERS, catch_response=True) + logger.locust_info(f"Legacy login flow for user {user[0]}") + else: + logger.locust_info(f"2SV login flow for user {user[0]}") + + login_body = {'username': user[0], + 'password': user[1], + 'rememberMe': 'True', + 'targetUrl': '' + } + + headers = { + "Content-Type": "application/json" + } + + # 15 /rest/tsv/1.0/authenticate + locust.post('/rest/tsv/1.0/authenticate', + json=login_body, + headers=headers, + catch_response=True) + r = locust.get('/', catch_response=True) if not r.content: raise Exception('Please check server hostname in jsm.yml file') diff --git a/app/locustio/jsm/customers/customers_http_actions.py b/app/locustio/jsm/customers/customers_http_actions.py index a378cd05d..d4b00f7b6 100644 --- a/app/locustio/jsm/customers/customers_http_actions.py +++ b/app/locustio/jsm/customers/customers_http_actions.py @@ -47,22 +47,61 @@ def customer_login_and_view_portals(locust): locust.session_data_storage['username'] = user[0] locust.session_data_storage['password'] = user[1] - r = locust.post( - '/servicedesk/customer/user/login', - body, - headers=JSM_CUSTOMERS_HEADERS, - catch_response=True) + legacy_form = False - locust.get('/servicedesk/customer/portals', catch_response=True) + # is 2sv login form + r = locust.get('/login.jsp', catch_response=True) + if b'login-form-remember-me' in r.content: + legacy_form = True - locust.post( - '/rest/servicedesk/1/customer/models', - json=params.resources_body.get("115"), - headers=RESOURCE_HEADERS, - catch_response=True) + if legacy_form: + r = locust.post( + '/servicedesk/customer/user/login', + body, + headers=JSM_CUSTOMERS_HEADERS, + catch_response=True) + + locust.get('/servicedesk/customer/portals', catch_response=True) + + locust.post( + '/rest/servicedesk/1/customer/models', + json=params.resources_body.get("115"), + headers=RESOURCE_HEADERS, + catch_response=True) + + assert '"loginSucceeded":true' in r.content.decode('utf-8'), 'Customer login is failed' + + else: + logger.locust_info(f"2SV login flow for user {user[0]}") + + login_body = {'username': user[0], + 'password': user[1], + 'rememberMe': 'True', + 'targetUrl': '' + } + + headers = { + "Content-Type": "application/json", + "Accept": "*/*" + } + + locust.post('/rest/tsv/1.0/authenticate?os_authType=none', + json=login_body, + headers=headers, + catch_response=True) + + locust.get('/servicedesk/customer/portals', catch_response=True) + + locust.post( + '/rest/servicedesk/1/customer/models', + json=params.resources_body.get("115"), + headers=RESOURCE_HEADERS, + catch_response=True) - assert '"loginSucceeded":true' in r.content.decode( - 'utf-8'), 'Customer login is failed' + r = locust.get('/', catch_response=True) + if not r.content: + raise Exception('Please check server hostname in jsm.yml file') + r.content.decode('utf-8') @jsm_customer_measure('locust_customer_view_portal') diff --git a/app/reports_generation/scripts/results_archivator.py b/app/reports_generation/scripts/results_archivator.py index 290fefb92..4a5a0ac13 100644 --- a/app/reports_generation/scripts/results_archivator.py +++ b/app/reports_generation/scripts/results_archivator.py @@ -1,14 +1,34 @@ from pathlib import Path from shutil import make_archive +import re from scripts.utils import validate_config, clean_str, resolve_relative_path def __zip_folder(folder_path: Path, destination_path: Path) -> Path: + yml_files = list(folder_path.glob("*.yml")) + if len(yml_files) > 0: + __hide_sensitive_info(yml_files) archive_path = make_archive(destination_path, 'zip', folder_path) return Path(archive_path) +def __hide_sensitive_info(files: list[Path]) -> None: + for file in files: + with file.open('r') as f: + lines = f.readlines() + new_lines = [] + file_edited = False + for line in lines: + if re.match(r'\s*admin_password\s*:', line): + line = re.sub(r'(:\s*).+', r'\1****', line) + file_edited = True + new_lines.append(line) + if file_edited: + with file.open('w') as f: + f.writelines(new_lines) + + def archive_results(config: dict, results_dir: Path): validate_config(config) for run in config['runs']: diff --git a/app/selenium_ui/base_page.py b/app/selenium_ui/base_page.py index 93c94bbea..9bc5348ad 100644 --- a/app/selenium_ui/base_page.py +++ b/app/selenium_ui/base_page.py @@ -4,8 +4,9 @@ import time from packaging import version -from selenium.common.exceptions import WebDriverException +from selenium.common.exceptions import WebDriverException, TimeoutException, StaleElementReferenceException from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support.ui import Select from selenium.webdriver.support.wait import WebDriverWait @@ -78,6 +79,14 @@ def wait_until_visible(self, selector, timeout=timeout): return self.__wait_until(expected_condition=ec.visibility_of_element_located(selector), locator=selector, time_out=timeout) + def became_visible_in_time(self, selector, timeout): + try: + self.__wait_until(expected_condition=ec.visibility_of_element_located(selector), locator=selector, + time_out=timeout) + return True + except TimeoutException: + return False + def wait_until_available_to_switch(self, selector): return self.__wait_until(expected_condition=ec.frame_to_be_available_and_switch_to_it(selector), locator=selector, @@ -107,6 +116,17 @@ def wait_until_any_ec_text_presented_in_el(self, selector_text_list, timeout=tim selector_text in selector_text_list) return self.__wait_until(expected_condition=any_ec, locator=selector_text_list, time_out=timeout) + def safe_click(self, locator, retries=3, delay=0.5): + for attempt in range(retries): + try: + self.wait_until_clickable(locator).click() + return + except StaleElementReferenceException: + if attempt < retries - 1: + time.sleep(delay) + else: + raise + def __wait_until(self, expected_condition, locator, time_out=timeout): message = f"Error in wait_until: " ec_type = type(expected_condition) @@ -129,14 +149,61 @@ def __wait_until(self, expected_condition, locator, time_out=timeout): message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" f"Locator: {locator}{str(expected_condition)}") - return WebDriverWait(self.driver, time_out).until(expected_condition, message=message) + try: + return WebDriverWait(self.driver, time_out).until(expected_condition, message=message) + except StaleElementReferenceException: + print(f"Stale element reference detected for {locator}, retrying wait...") + return WebDriverWait(self.driver, time_out).until(expected_condition, message=message) - def dismiss_popup(self, *args): - for elem in args: - try: - self.driver.execute_script(f"document.querySelector(\'{elem}\').click()") - except(WebDriverException, Exception): - pass + def wait_for_dom_mutations_complete(self, timeout=10): + start_time = time.time() + script = """ + var callback = arguments[arguments.length - 1]; + var timeout = arguments[0] * 1000; + var lastMutation = Date.now(); + var startTime = Date.now(); + + var observer = new MutationObserver(function() { + lastMutation = Date.now(); + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true + }); + + var interval = setInterval(function() { + if (Date.now() - lastMutation > 500) { + observer.disconnect(); + clearInterval(interval); + callback(true); + } else if (Date.now() - startTime > timeout) { + observer.disconnect(); + clearInterval(interval); + callback(false); + } + }, 100); + """ + complete = self.driver.execute_async_script(script, timeout) + if not complete: + print(f'WARNING: DOM mutations did not complete in {timeout} s') + else: + print(f'DOM mutations completed after {time.time() - start_time} s') + return complete + + def dismiss_popup(self, popup_selectors): + dismissed = False + for selector_type, selector_value in popup_selectors: + if self.driver.find_elements(by=selector_type, value=selector_value): + try: + self.wait_until_clickable((selector_type, selector_value), 1).click() + dismissed = True + except (WebDriverException, Exception): + pass + if dismissed: + self.wait_for_dom_mutations_complete() + return dismissed def return_to_parent_frame(self): return self.driver.switch_to.parent_frame() diff --git a/app/selenium_ui/bitbucket/modules.py b/app/selenium_ui/bitbucket/modules.py index 5827bcd64..2354669b1 100644 --- a/app/selenium_ui/bitbucket/modules.py +++ b/app/selenium_ui/bitbucket/modules.py @@ -34,12 +34,11 @@ def login(webdriver, datasets): BITBUCKET_SETTINGS.admin_login, BITBUCKET_SETTINGS.admin_password) webdriver.app_version = version.parse(client.get_bitbucket_version()) - login_page = LoginPage(webdriver) webdriver.debug_info = generate_debug_session_info(webdriver, datasets) + login_page = LoginPage(webdriver) @print_timing("selenium_login") def measure(): - @print_timing("selenium_login:open_login_page") def sub_measure(): login_page.go_to() @@ -48,14 +47,17 @@ def sub_measure(): login_page.go_to() sub_measure() + login_page.wait_for_page_loaded() login_page.set_credentials(datasets['current_session']['username'], datasets['current_session']['password']) @print_timing("selenium_login:login_get_started") def sub_measure(): login_page.submit_login() get_started_page = GetStarted(webdriver) + get_started_page.wait_for_page_loaded() PopupManager(webdriver).dismiss_default_popup() get_started_page.close_whats_new_window() + PopupManager(webdriver).dismiss_default_popup() get_started_page.wait_for_page_loaded() webdriver.node_id = login_page.get_node_id() print(f"node_id:{webdriver.node_id}") @@ -184,6 +186,7 @@ def measure(): PopupManager(webdriver).dismiss_default_popup() pull_request_page.dismiss_updates_info_popup() pull_request_page.click_inline_comment_button_js() + PopupManager(webdriver).dismiss_default_popup() pull_request_page.dismiss_updates_info_popup() pull_request_page.add_code_comment() measure() @@ -237,12 +240,15 @@ def sub_measure(): branch_from = datasets['current_session']['pull_request_branch_from'] branch_to = datasets['current_session']['pull_request_branch_to'] repository_branches_page.open_base_branch(base_branch_name=branch_from) + PopupManager(webdriver).dismiss_default_popup() fork_branch_from = repository_branches_page.create_branch_fork_rnd_name(base_branch_name=branch_from) navigation_panel.wait_for_navigation_panel() + PopupManager(webdriver).dismiss_default_popup() repository_branches_page.open_base_branch(base_branch_name=branch_to) fork_branch_to = repository_branches_page.create_branch_fork_rnd_name(base_branch_name=branch_to) datasets['pull_request_fork_branch_to'] = fork_branch_to navigation_panel.wait_for_navigation_panel() + PopupManager(webdriver).dismiss_default_popup() repo_pull_requests_page.create_new_pull_request(from_branch=fork_branch_from, to_branch=fork_branch_to) pull_request_page.dismiss_updates_info_popup() PopupManager(webdriver).dismiss_default_popup() diff --git a/app/selenium_ui/bitbucket/pages/pages.py b/app/selenium_ui/bitbucket/pages/pages.py index 61dc89bb0..700cde946 100644 --- a/app/selenium_ui/bitbucket/pages/pages.py +++ b/app/selenium_ui/bitbucket/pages/pages.py @@ -3,28 +3,56 @@ from selenium_ui.base_page import BasePage from selenium_ui.bitbucket.pages.selectors import LoginPageLocators, GetStartedLocators, \ DashboardLocators, ProjectsLocators, ProjectLocators, RepoLocators, RepoNavigationPanelLocators, PopupLocators, \ - PullRequestLocator, BranchesLocator, RepoCommitsLocator, LogoutPageLocators, UrlManager + PullRequestLocator, BranchesLocator, RepoCommitsLocator, LogoutPageLocators, UrlManager, AdminLocators, CommonLocators class LoginPage(BasePage): page_url = UrlManager().login_url() + page_loaded_selector = LoginPageLocators.footer_panel + + def __init__(self, driver): + super().__init__(driver) + self.is_2sv_login = False + + def wait_for_page_loaded(self): + self.wait_until_visible(LoginPageLocators.footer_panel) + if not self.get_elements(LoginPageLocators.submit_button): + self.is_2sv_login = True + print("INFO: 2sv login form") + def fill_username(self, username): self.get_element(LoginPageLocators.username_textfield).send_keys(username) + def fill_2sv_username(self, username): + self.wait_until_visible(LoginPageLocators.login_username_field_2sv) + self.get_element(LoginPageLocators.login_username_field_2sv).send_keys(username) + + def fill_2sv_password(self, username): + self.wait_until_visible(LoginPageLocators.login_username_field_2sv) + self.get_element(LoginPageLocators.login_password_field_2sv).send_keys(username) + def fill_password(self, password): self.get_element(LoginPageLocators.password_textfield).send_keys(password) def submit_login(self): - self.wait_until_visible(LoginPageLocators.submit_button).click() + if self.is_2sv_login: + self.wait_until_visible(LoginPageLocators.login_button_2sv).click() + else: + self.wait_until_visible(LoginPageLocators.submit_button).click() def set_credentials(self, username, password): - self.fill_username(username) - self.fill_password(password) + if self.is_2sv_login: + self.fill_2sv_username(username) + self.fill_2sv_password(password) + else: + self.fill_username(username) + self.fill_password(password) def get_node_id(self): text = self.get_element(LoginPageLocators.node_id).text - return text.split('\n')[2] + lines = text.split('\n') + return lines[2] if len(lines) > 2 else None def is_logged_in(self): elements = self.get_elements(GetStartedLocators.user_profile_icon) @@ -76,9 +104,7 @@ def wait_for_navigation_panel(self): class PopupManager(BasePage): def dismiss_default_popup(self): - return self.dismiss_popup(PopupLocators.default_popup, PopupLocators.popup_1, PopupLocators.popup_2, - PopupLocators.popup_3, PopupLocators.popup_4, PopupLocators.popup_5, - PopupLocators.popup_6, PopupLocators.popup_7) + return self.dismiss_popup(PopupLocators.popup_selectors) class Repository(BasePage): @@ -206,8 +232,8 @@ def open_base_branch(self, base_branch_name): self.wait_until_visible(BranchesLocator.branches_name) def create_branch_fork_rnd_name(self, base_branch_name): - self.wait_until_visible(BranchesLocator.branches_action).click() - self.get_element(BranchesLocator.branches_action_create_branch).click() + self.wait_until_visible(self.get_selector(BranchesLocator.branches_action)).click() + self.wait_until_visible(self.get_selector(BranchesLocator.branches_action_create_branch)).click() self.wait_until_visible(BranchesLocator.new_branch_name_textfield) branch_name = f"{base_branch_name}-{self.generate_random_string(5)}".replace(' ', '-') self.get_element(BranchesLocator.new_branch_name_textfield).send_keys(branch_name) @@ -216,10 +242,11 @@ def create_branch_fork_rnd_name(self, base_branch_name): def delete_branch(self, branch_name): self.wait_until_visible(BranchesLocator.search_branch_textfield).send_keys(branch_name) + self.became_visible_in_time(self.get_selector(CommonLocators.spinner), 3) self.wait_until_visible(BranchesLocator.branches_name) - self.wait_until_visible(BranchesLocator.search_branch_action).click() - self.execute_js("document.querySelector('li>a.delete-branch').click()") - self.wait_until_clickable(BranchesLocator.delete_branch_dialog_submit).click() + self.wait_until_clickable(BranchesLocator.search_branch_action).click() + self.wait_until_clickable(self.get_selector(BranchesLocator.delete_branch_action)).click() + self.wait_until_clickable(self.get_selector(BranchesLocator.delete_branch_dialog_submit)).click() class RepositoryCommits(BasePage): @@ -229,3 +256,22 @@ def __init__(self, driver, project_key, repo_slug): BasePage.__init__(self, driver) url_manager = UrlManager(project_key=project_key, repo_slug=repo_slug) self.page_url = url_manager.commits_url() + + +class AdminPage(BasePage): + page_url = AdminLocators.admin_system_page_url + page_loaded_selector = AdminLocators.login_form + + def is_websudo(self): + return True if self.get_elements(AdminLocators.web_sudo_password) else False + + def do_websudo(self, password): + self.wait_until_clickable(AdminLocators.web_sudo_password).send_keys(password) + self.wait_until_clickable(AdminLocators.web_sudo_submit_btn).click() + self.wait_until_visible(AdminLocators.administration_link) + + def go_to(self, password=None): + super().go_to() + self.wait_for_page_loaded() + if self.is_websudo(): + self.do_websudo(password) \ No newline at end of file diff --git a/app/selenium_ui/bitbucket/pages/selectors.py b/app/selenium_ui/bitbucket/pages/selectors.py index 7a4f883ab..a023be017 100644 --- a/app/selenium_ui/bitbucket/pages/selectors.py +++ b/app/selenium_ui/bitbucket/pages/selectors.py @@ -26,6 +26,7 @@ def __init__(self, user=None, project_key=None, repo_slug=None, pull_request_key self.dashboard_params = '/dashboard' self.projects_params = '/projects' self.branches_base_branch = f'/projects/{self.project_key}/repos/{self.repo_slug}/branches?base=' + self.admin_system_params = '/admin' def create_pull_request_url(self, from_branch, to_branch): return f"{self.host}/projects/{self.project_key}/repos/{self.repo_slug}/pull-requests?create&targetBranch=" \ @@ -73,25 +74,41 @@ def dashboard_url(self): def projects_url(self): return f"{self.host}{self.projects_params}" + def admin_system_url(self): + return f"{self.host}{self.admin_system_params}" + class PopupLocators: - default_popup = '.feature-discovery-close' - popup_1 = '.css-1it7f5o' - popup_2 = 'button.aui-button-link.feature-discovery-close' - popup_3 = '.css-15p34h1' - popup_4 = '.css-1dqf51u' - popup_5 = '.css-1kflcxk' - popup_6 = '.css-1gh2dqy' - popup_7 = "[data-testid='whats-new-modal'] button[aria-label='Close modal'] > span > span[aria-hidden='true']" + popup_selectors = [ + (By.CSS_SELECTOR, ".feature-discovery-close"), + (By.CSS_SELECTOR, ".css-1it7f5o"), + (By.CSS_SELECTOR, "button.aui-button-link.feature-discovery-close"), + (By.CSS_SELECTOR, ".css-15p34h1"), + (By.CSS_SELECTOR, ".css-1dqf51u"), + (By.CSS_SELECTOR, ".css-1kflcxk"), + (By.CSS_SELECTOR, ".css-1gh2dqy"), + (By.XPATH, "//*[@data-testid='whats-new-modal']//button[@aria-label='Close modal']//span//span[@aria-hidden='true']") + ] + + +class CommonLocators: + spinner = OrderedDict({"7.0.0": (By.CSS_SELECTOR, ".spinner-wrapper"), + "10.0.0": (By.CSS_SELECTOR, ".centered-spinner")}) class LoginPageLocators: submit_button = (By.ID, "submit") + footer_panel = (By.ID, "footer") username_textfield = (By.ID, "j_username") password_textfield = (By.ID, "j_password") application_version = (By.ID, 'product-version') node_id = (By.CLASS_NAME, 'footer-body') + # 2sv login form + login_button_2sv = (By.ID, "login-button") + login_username_field_2sv = (By.ID, "username-field") + login_password_field_2sv = (By.ID, "password-field") + class LogoutPageLocators: logout_url = UrlManager().logout_url() @@ -162,14 +179,28 @@ class PullRequestLocator: class BranchesLocator: branches_name = (By.ID, "branch-name-column") - branches_action = (By.ID, "branch-actions") - branches_action_create_branch = (By.CSS_SELECTOR, "a.create-branch") + branches_action = OrderedDict({"7.0.0": (By.ID, "branch-actions"), + "10.0.0": (By.CSS_SELECTOR, ".ref-selector-more-actions > button")}) + branches_action_create_branch = OrderedDict({"7.0.0": (By.CSS_SELECTOR, "a.create-branch"), + "10.0.0": (By.XPATH, "//span[contains(text(), 'Create branch from here')]")}) + + delete_branch_action = OrderedDict({"7.0.0": (By.CSS_SELECTOR, "li>a.delete-branch"), + "10.0.0": (By.XPATH, "//button[contains(@data-testid, 'delete-branch-action')]")}) new_branch_name_textfield = (By.CSS_SELECTOR, "input.text.branch-name") new_branch_submit_button = (By.ID, "create-branch-submit") search_branch_textfield = (By.ID, 'paged-table-input-for-branch-list') search_branch_action = (By.CSS_SELECTOR, '.branch-actions-column>button') - delete_branch_dialog_submit = (By.ID, 'delete-branch-dialog-submit') + delete_branch_dialog_submit = OrderedDict({"7.0.0": (By.ID, 'delete-branch-dialog-submit'), + "10.0.0": (By.XPATH, "//button[contains(@data-testid, 'delete-branch-modal-confirm-button')]")}) class RepoCommitsLocator: repo_commits_graph = (By.ID, 'commits-table') + + +class AdminLocators: + admin_system_page_url = UrlManager().admin_system_url() + web_sudo_password = (By.ID, 'j_password-uid1') + web_sudo_submit_btn = (By.XPATH, "//span[contains(text(),'Confirm')]") + login_form = (By.ID, 'websudo-container') + administration_link = (By.XPATH, "//span[contains(text(),'Administration')]") diff --git a/app/selenium_ui/confluence/modules.py b/app/selenium_ui/confluence/modules.py index fc699e313..e32c5204d 100644 --- a/app/selenium_ui/confluence/modules.py +++ b/app/selenium_ui/confluence/modules.py @@ -1,5 +1,5 @@ import random -from selenium_ui.conftest import print_timing, measure_browser_navi_metrics, measure_dom_requests +from selenium_ui.conftest import print_timing, measure_timing, measure_dom_requests, measure_with_browser_metrics from selenium_ui.confluence.pages.pages import Login, AllUpdates, PopupManager, Page, Dashboard, TopNavPanel, Editor, \ Logout @@ -13,7 +13,8 @@ CQLS = "cqls" CUSTOM_PAGES = "custom_pages" BLOGS = "blogs" - +TWO_WORDS_CQL = 'confluence agreement' +THREE_WORDS_CQL = 'shoulder trip discussion' def setup_run_data(datasets): datasets['current_session'] = {} @@ -56,44 +57,45 @@ def login(webdriver, datasets): login_page = Login(webdriver) webdriver.debug_info = generate_debug_session_info(webdriver, datasets) - @print_timing("selenium_login") - def measure(): - def sub_measure(): + def measure(): + login_page.go_to() + if login_page.is_logged_in(): + login_page.delete_all_cookies() login_page.go_to() - if login_page.is_logged_in(): - login_page.delete_all_cookies() - login_page.go_to() - login_page.wait_for_page_loaded() - node_id = login_page.get_node_id() - node_ip = rest_client.get_node_ip(node_id) - webdriver.node_ip = node_ip - print(f"node_id:{node_id}, node_ip: {webdriver.node_ip}") - measure_dom_requests(webdriver, interaction="selenium_login:open_login_page") - - sub_measure() + login_page.wait_for_page_loaded() + node_id = login_page.get_node_id() + node_ip = rest_client.get_node_ip(node_id) + webdriver.node_ip = node_ip + print(f"node_id:{node_id}, node_ip: {webdriver.node_ip}") + measure_dom_requests(webdriver, interaction="selenium_login:open_login_page") login_page.set_credentials(username=datasets['current_session']['username'], password=datasets['current_session']['password']) - def sub_measure(): - login_page.click_login_button() - if login_page.is_first_login(): - login_page.first_user_setup() - all_updates_page = AllUpdates(webdriver) - all_updates_page.wait_for_page_loaded() - measure_dom_requests(webdriver, interaction="selenium_login:login_and_view_dashboard") - if CONFLUENCE_SETTINGS.extended_metrics: - measure_browser_navi_metrics(webdriver, datasets, expected_metrics=browser_metrics['selenium_login']) + login_page.click_login_button() + all_updates_page = AllUpdates(webdriver) + all_updates_page.wait_for_page_loaded() + if login_page.is_first_login(): + login_page.first_user_setup() + all_updates_page.wait_for_page_loaded() + measure_dom_requests(webdriver, interaction="selenium_login:login_and_view_dashboard") - sub_measure() + + def post_metric_measure(): current_session_response = login_page.rest_api_get(url=f'{CONFLUENCE_SETTINGS.server_url}' f'/rest/api/user/current') if 'username' in current_session_response: actual_username = current_session_response['username'] assert actual_username == datasets['current_session']['username'] - measure() + measure_with_browser_metrics( + "selenium_login", + webdriver, + datasets, + measure, + post_metric_measure + ) PopupManager(webdriver).dismiss_default_popup() @@ -105,15 +107,17 @@ def view_page(webdriver, datasets): datasets['current_session']['view_page_cache'] = random_page page = Page(webdriver, page_id=page_id) - @print_timing("selenium_view_page") def measure(): page.go_to() page.wait_for_page_loaded() measure_dom_requests(webdriver, interaction=f"selenium_view_page", description=page_description) - if CONFLUENCE_SETTINGS.extended_metrics: - measure_browser_navi_metrics(webdriver, datasets, expected_metrics=browser_metrics['selenium_view_page']) - measure() + measure_with_browser_metrics( + "selenium_view_page", + webdriver, + datasets, + measure + ) def view_page_from_cache(webdriver, datasets): @@ -124,17 +128,17 @@ def view_page_from_cache(webdriver, datasets): page = Page(webdriver, page_id=page_id) - @print_timing("selenium_view_page_from_cache") def measure(): page.go_to() page.wait_for_page_loaded() measure_dom_requests(webdriver, interaction=f"selenium_view_page_from_cache", description=page_description) - if CONFLUENCE_SETTINGS.extended_metrics: - measure_browser_navi_metrics(webdriver, datasets, - expected_metrics=browser_metrics['selenium_view_page_from_cache']) - - measure() + measure_with_browser_metrics( + "selenium_view_page_from_cache", + webdriver, + datasets, + measure + ) def view_blog(webdriver, datasets): random_blog = random.choice(datasets[BLOGS]) @@ -143,64 +147,65 @@ def view_blog(webdriver, datasets): blog = Page(webdriver, page_id=blog_id) datasets['current_session']['view_blog'] = random_blog - @print_timing("selenium_view_blog") def measure(): blog.go_to() blog.wait_for_page_loaded() measure_dom_requests(webdriver, interaction=f"selenium_view_blog", description=blog_description) - if CONFLUENCE_SETTINGS.extended_metrics: - measure_browser_navi_metrics(webdriver, datasets, expected_metrics=browser_metrics['selenium_view_blog']) - measure() + measure_with_browser_metrics( + "selenium_view_blog", + webdriver, + datasets, + measure + ) def view_dashboard(webdriver, datasets): dashboard_page = Dashboard(webdriver) - @print_timing("selenium_view_dashboard") def measure(): dashboard_page.go_to() dashboard_page.wait_for_page_loaded() measure_dom_requests(webdriver, interaction="selenium_view_dashboard") - if CONFLUENCE_SETTINGS.extended_metrics: - measure_browser_navi_metrics(webdriver, datasets, - expected_metrics=browser_metrics['selenium_view_dashboard']) - - measure() + measure_with_browser_metrics( + "selenium_view_dashboard", + webdriver, + datasets, + measure + ) def create_confluence_page(webdriver, datasets): nav_panel = TopNavPanel(webdriver) create_page = Editor(webdriver) - @print_timing("selenium_create_page") def measure(): - def sub_measure(): - PopupManager(webdriver).dismiss_default_popup() - nav_panel.click_create() - PopupManager(webdriver).dismiss_default_popup() - create_page.wait_for_create_page_open() - measure_dom_requests(webdriver, interaction="selenium_create_page:open_create_page_editor") - if CONFLUENCE_SETTINGS.extended_metrics: - measure_browser_navi_metrics(webdriver, datasets, - expected_metrics=browser_metrics['selenium_create_page']) + PopupManager(webdriver).dismiss_default_popup() + nav_panel.click_create() + PopupManager(webdriver).dismiss_default_popup() + create_page.wait_for_create_page_open() + measure_dom_requests(webdriver, interaction="selenium_create_page:open_create_page_editor") - sub_measure() + def post_metric_measure(): PopupManager(webdriver).dismiss_default_popup() create_page.write_title() create_page.write_content() - def sub_measure(): - create_page.click_submit() - page = Page(webdriver) - page.wait_for_page_loaded() - measure_dom_requests(webdriver, interaction="selenium_create_page:save_created_page") + create_page.click_submit() + page = Page(webdriver) + page.wait_for_page_loaded() + measure_dom_requests(webdriver, interaction="selenium_create_page:save_created_page") - sub_measure() - measure() + measure_with_browser_metrics( + "selenium_create_page", + webdriver, + datasets, + measure, + post_metric_measure + ) def edit_confluence_page_by_url(webdriver, datasets): @@ -210,29 +215,26 @@ def edit_confluence_page_by_url(webdriver, datasets): datasets['current_session']['edit_page'] = random_page edit_page = Editor(webdriver, page_id=page_id) - @print_timing("selenium_edit_page_by_url") def measure(): - def sub_measure(): - edit_page.go_to() - edit_page.wait_for_page_loaded() - measure_dom_requests(webdriver, interaction=f"selenium_edit_page_by_url:open_create_page_editor", - description=page_description) - if CONFLUENCE_SETTINGS.extended_metrics: - measure_browser_navi_metrics(webdriver, datasets, - expected_metrics=browser_metrics['selenium_edit_page_by_url']) - - sub_measure() + edit_page.go_to() + edit_page.wait_for_page_loaded() + measure_dom_requests(webdriver, interaction=f"selenium_edit_page_by_url:open_create_page_editor", + description=page_description) + def post_metric_measure(): edit_page.write_content() - def sub_measure(): - edit_page.save_edited_page() - measure_dom_requests(webdriver, interaction=f"selenium_edit_page_by_url:save_edited_page", - description=page_description) - - sub_measure() + edit_page.save_edited_page() + measure_dom_requests(webdriver, interaction=f"selenium_edit_page_by_url:save_edited_page", + description=page_description) - measure() + measure_with_browser_metrics( + "selenium_edit_page_by_url", + webdriver, + datasets, + measure, + post_metric_measure + ) def edit_confluence_page_quick_edit(webdriver, datasets): @@ -244,32 +246,29 @@ def edit_confluence_page_quick_edit(webdriver, datasets): @print_timing("selenium_quick_edit_page_click") def measure(): - def sub_measure(): - page.go_to() - page.wait_for_resources_loaded() - page.wait_for_page_loaded() - PopupManager(webdriver).dismiss_default_popup() - page.click_edit() - edit_page.wait_for_page_loaded() - measure_dom_requests(webdriver, interaction=f"selenium_quick_edit_page_click:open_create_page_editor", - description=page_description) - if CONFLUENCE_SETTINGS.extended_metrics: - measure_browser_navi_metrics(webdriver, datasets, - expected_metrics=browser_metrics['selenium_quick_edit_page_click']) - - sub_measure() + page.go_to() + page.wait_for_resources_loaded() + page.wait_for_page_loaded() + PopupManager(webdriver).dismiss_default_popup() + page.click_edit() + edit_page.wait_for_page_loaded() + measure_dom_requests(webdriver, interaction=f"selenium_quick_edit_page_click:open_create_page_editor", + description=page_description) + def post_metric_measure(): edit_page.write_content() - def sub_measure(): - edit_page.save_edited_page() - measure_dom_requests(webdriver, interaction=f"selenium_quick_edit_page_click:save_edited_page", - description=page_description) - - sub_measure() - - measure() + edit_page.save_edited_page() + measure_dom_requests(webdriver, interaction=f"selenium_quick_edit_page_click:save_edited_page", + description=page_description) + measure_with_browser_metrics( + "selenium_quick_edit_page_click", + webdriver, + datasets, + measure, + post_metric_measure + ) def create_inline_comment(webdriver, datasets): page = random.choice(datasets[PAGES]) @@ -300,16 +299,23 @@ def sub_measure(): measure() +def cql_search_three_words(webdriver): + return cql_search(webdriver, cql_string=THREE_WORDS_CQL, print_timing_suffix='three_words') + -def cql_search(webdriver, datasets): - random_cql = random.choice(datasets[CQLS]) +def cql_search_two_words(webdriver): + return cql_search(webdriver, cql_string=TWO_WORDS_CQL, print_timing_suffix='two_words') + + +def cql_search(webdriver, cql_string, print_timing_suffix): page = Page(webdriver) page.wait_until_visible(PageLocators.search_box) + page.wait_for_dom_mutations_complete() PopupManager(webdriver).dismiss_default_popup() - @print_timing("selenium_cql_search") + @print_timing(f"selenium_cql_search_{print_timing_suffix}") def measure(): - page.get_element(PageLocators.search_box).send_keys(random_cql) + page.get_element(PageLocators.search_box).send_keys(cql_string) page.wait_until_any_ec_presented((PageLocators.empty_search_results, PageLocators.search_results), timeout=30) page.get_element(PageLocators.close_search_button).click() @@ -318,10 +324,13 @@ def measure(): def log_out(webdriver, datasets): logout_page = Logout(webdriver) + login_page = Login(webdriver) @print_timing("selenium_log_out") def measure(): logout_page.go_to() logout_page.wait_for_logout() + login_page.wait_for_page_loaded() + login_page.delete_all_cookies() measure() diff --git a/app/selenium_ui/confluence/pages/pages.py b/app/selenium_ui/confluence/pages/pages.py index f294cce46..c15a3908d 100644 --- a/app/selenium_ui/confluence/pages/pages.py +++ b/app/selenium_ui/confluence/pages/pages.py @@ -3,7 +3,7 @@ from selenium_ui.base_page import BasePage from selenium_ui.confluence.pages.selectors import UrlManager, LoginPageLocators, AllUpdatesLocators, PopupLocators,\ - PageLocators, DashboardLocators, TopPanelLocators, EditorLocators, LogoutLocators, XsrfTokenLocators + PageLocators, DashboardLocators, TopPanelLocators, EditorLocators, LogoutLocators, XsrfTokenLocators, AdminLocators class Login(BasePage): @@ -19,6 +19,10 @@ def wait_for_page_loaded(self): if not self.get_elements(LoginPageLocators.login_button): self.is_2sv_login = True print("INFO: 2sv login form") + self.wait_until_visible(LoginPageLocators.login_username_field_2sv) + else: + print("INFO: legacy login form") + self.wait_until_visible(LoginPageLocators.login_username_field) def set_credentials(self, username, password): if self.is_2sv_login: @@ -34,10 +38,8 @@ def set_credentials(self, username, password): def click_login_button(self): if self.is_2sv_login: self.wait_until_visible(LoginPageLocators.login_button_2sv).click() - self.wait_until_invisible(LoginPageLocators.login_button_2sv) else: self.wait_until_visible(LoginPageLocators.login_button).click() - self.wait_until_invisible(LoginPageLocators.login_button) def is_first_login(self): elements = self.get_elements(LoginPageLocators.first_login_setup_page) @@ -73,6 +75,12 @@ class Logout(BasePage): def wait_for_logout(self): self.wait_until_visible(LoginPageLocators.sidebar) + if not self.get_elements(LoginPageLocators.login_button): + print("INFO: 2sv login form") + self.wait_until_visible(LoginPageLocators.login_username_field_2sv) + else: + print("INFO: legacy login form") + self.wait_until_visible(LoginPageLocators.login_username_field) class AllUpdates(BasePage): @@ -82,11 +90,7 @@ class AllUpdates(BasePage): class PopupManager(BasePage): def dismiss_default_popup(self): - return self.dismiss_popup(PopupLocators.timezone_popups, PopupLocators.skip_onbording_1, - PopupLocators.skip_onboarding_2, - PopupLocators.time_saving_template, - PopupLocators.welcome_to_confluence, - PopupLocators.dark_theme_popup) + return self.dismiss_popup(PopupLocators.popup_selectors) class Page(BasePage): @@ -182,3 +186,22 @@ def save_edited_page(self): self.wait_until_invisible(EditorLocators.save_spinner) self.wait_until_any_ec_presented(selectors=[PageLocators.page_title, EditorLocators.confirm_publishing_button]) + + +class AdminPage(BasePage): + page_url = AdminLocators.admin_system_page_url + page_loaded_selector = AdminLocators.login_form + + def is_websudo(self): + return True if self.get_elements(AdminLocators.web_sudo_password) else False + + def do_websudo(self, password): + self.wait_until_clickable(AdminLocators.web_sudo_password).send_keys(password) + self.wait_until_clickable(AdminLocators.web_sudo_submit_btn).click() + self.wait_until_visible(AdminLocators.edit_baseurl) + + def go_to(self, password=None): + super().go_to() + self.wait_for_page_loaded() + if self.is_websudo(): + self.do_websudo(password) diff --git a/app/selenium_ui/confluence/pages/selectors.py b/app/selenium_ui/confluence/pages/selectors.py index d027b2c98..608198216 100644 --- a/app/selenium_ui/confluence/pages/selectors.py +++ b/app/selenium_ui/confluence/pages/selectors.py @@ -11,6 +11,7 @@ def __init__(self, page_id=None): self.dashboard_params = '/dashboard.action#all-updates' self.edit_page_params = f'/pages/editpage.action?pageId={page_id}' self.logout_params = "/logout.action" + self.admin_system_params = f"/admin/viewgeneralconfig.action" def login_url(self): return f"{self.host}{self.login_params}" @@ -27,14 +28,20 @@ def edit_page_url(self): def logout_url(self): return f"{self.host}{self.logout_params}" + def admin_system_url(self): + return f"{self.host}{self.admin_system_params}" + class PopupLocators: - timezone_popups = '.button-panel-button .set-timezone-button' - skip_onbording_1 = '.aui-button aui-button-link .skip-onboarding' - skip_onboarding_2 = '.aui-button.aui-button-link.skip-onboarding' - time_saving_template = '#closeDisDialog' - welcome_to_confluence = '.aui-button.aui-button-primary.show-onboarding' - dark_theme_popup = 'button[aria-label="Close this modal"]' + popup_selectors = [ + (By.CSS_SELECTOR, ".button-panel-button .set-timezone-button"), + (By.CSS_SELECTOR, ".aui-button aui-button-link .skip-onboarding"), + (By.CSS_SELECTOR, ".aui-button.aui-button-link.skip-onboarding"), + (By.CSS_SELECTOR, "#closeDisDialog"), + (By.CSS_SELECTOR, ".aui-button.aui-button-primary.show-onboarding"), + (By.CSS_SELECTOR, 'button[aria-label="Close this modal"]'), + (By.XPATH, "//button[.//span[text()='Close']]") + ] class LoginPageLocators: @@ -108,3 +115,10 @@ class LogoutLocators: class XsrfTokenLocators: xsrf_token = (By.ID, "atlassian-token") + +class AdminLocators: + admin_system_page_url = UrlManager().admin_system_url() + web_sudo_password = (By.ID, 'password') + web_sudo_submit_btn = (By.ID, 'authenticateButton') + login_form = (By.ID, 'login-container') + edit_baseurl = (By.ID, 'editbaseurl') diff --git a/app/selenium_ui/confluence_ui.py b/app/selenium_ui/confluence_ui.py index 49b3e9fee..a723b767d 100644 --- a/app/selenium_ui/confluence_ui.py +++ b/app/selenium_ui/confluence_ui.py @@ -22,7 +22,6 @@ def test_1_selenium_view_page(confluence_webdriver, confluence_datasets, conflue def test_1_selenium_view_page_from_cache(confluence_webdriver, confluence_datasets, confluence_screen_shots): modules.view_page_from_cache(confluence_webdriver, confluence_datasets) - def test_1_selenium_create_page(confluence_webdriver, confluence_datasets, confluence_screen_shots): modules.create_confluence_page(confluence_webdriver, confluence_datasets) @@ -31,6 +30,10 @@ def test_1_selenium_edit_by_url(confluence_webdriver, confluence_datasets, confl modules.edit_confluence_page_by_url(confluence_webdriver, confluence_datasets) +def test_1_selenium_cql_search_two_words(confluence_webdriver, confluence_datasets, confluence_screen_shots): + modules.cql_search_two_words(confluence_webdriver) + + def test_1_selenium_edit_page_quick_edit(confluence_webdriver, confluence_datasets, confluence_screen_shots): modules.edit_confluence_page_quick_edit(confluence_webdriver, confluence_datasets) @@ -39,8 +42,9 @@ def test_1_selenium_create_inline_comment(confluence_webdriver, confluence_datas modules.create_inline_comment(confluence_webdriver, confluence_datasets) -def test_1_selenium_cql_search(confluence_webdriver, confluence_datasets, confluence_screen_shots): - modules.cql_search(confluence_webdriver, confluence_datasets) +def test_1_selenium_cql_search_three_words(confluence_webdriver, confluence_datasets, confluence_screen_shots): + modules.cql_search_three_words(confluence_webdriver) + """ diff --git a/app/selenium_ui/conftest.py b/app/selenium_ui/conftest.py index 4b50489a2..c9531db3e 100644 --- a/app/selenium_ui/conftest.py +++ b/app/selenium_ui/conftest.py @@ -18,13 +18,14 @@ from util.common_util import webdriver_pretty_debug from util.conf import CONFLUENCE_SETTINGS, JIRA_SETTINGS, BITBUCKET_SETTINGS, JSM_SETTINGS, BAMBOO_SETTINGS +from util.confluence.browser_metrics import browser_metrics from util.exceptions import WebDriverExceptionPostpone from util.project_paths import JIRA_DATASET_ISSUES, JIRA_DATASET_JQLS, JIRA_DATASET_KANBAN_BOARDS, \ JIRA_DATASET_PROJECTS, JIRA_DATASET_SCRUM_BOARDS, JIRA_DATASET_USERS, JIRA_DATASET_CUSTOM_ISSUES, BITBUCKET_USERS, \ BITBUCKET_PROJECTS, BITBUCKET_REPOS, BITBUCKET_PRS, CONFLUENCE_BLOGS, CONFLUENCE_PAGES, CONFLUENCE_CUSTOM_PAGES, \ CONFLUENCE_USERS, ENV_TAURUS_ARTIFACT_DIR, JSM_DATASET_REQUESTS, JSM_DATASET_CUSTOMERS, JSM_DATASET_AGENTS, \ JSM_DATASET_SERVICE_DESKS_L, JSM_DATASET_SERVICE_DESKS_M, JSM_DATASET_SERVICE_DESKS_S, JSM_DATASET_CUSTOM_ISSUES, \ - JSM_DATASET_INSIGHT_SCHEMAS, JSM_DATASET_INSIGHT_ISSUES, BAMBOO_USERS, BAMBOO_BUILD_PLANS, CONFLUENCE_CQLS + JSM_DATASET_INSIGHT_SCHEMAS, JSM_DATASET_INSIGHT_ISSUES, BAMBOO_USERS, BAMBOO_BUILD_PLANS SCREEN_WIDTH = 1920 SCREEN_HEIGHT = 1080 @@ -87,7 +88,6 @@ def confluence_dataset(self): self.dataset["pages"] = self.__read_input_file(CONFLUENCE_PAGES) self.dataset["blogs"] = self.__read_input_file(CONFLUENCE_BLOGS) self.dataset["users"] = self.__read_input_file(CONFLUENCE_USERS) - self.dataset["cqls"] = self.__read_input_file(CONFLUENCE_CQLS) self.dataset["custom_pages"] = self.__read_input_file( CONFLUENCE_CUSTOM_PAGES) return self.dataset @@ -140,6 +140,55 @@ def is_docker(): ) +def measure_timing(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + start = time() + func(*args, **kwargs) + end = time() + return end - start + return wrapper + + +def measure_with_browser_metrics(interaction_name, webdriver, datasets, measure_func, post_metric_measure_func=None): + """ + Helper function that combines timing for measure_func + post_metric_measure_func + ready_for_user_timing. + """ + # Step 1: Measure core operations + @measure_timing + def measure1(): + measure_func() + + timing = measure1() + + # Step 2: Measure browser metrics AFTER core operations (not included in core timing) + ready_for_user_timing = None + if CONFLUENCE_SETTINGS.extended_metrics: + ready_for_user_timing = measure_browser_navi_metrics( + webdriver, datasets, expected_metrics=browser_metrics[interaction_name] + ) + + # Step 3: Measure post_metric_measure_func (included in total timing) + if post_metric_measure_func is not None: + @measure_timing + def measure_post(): + post_metric_measure_func() + + post_timing = measure_post() + timing = timing + post_timing + + # Step 4: Calculate combined timing (core + post + ready_for_user) + if ready_for_user_timing is not None: + timing = timing + (int(ready_for_user_timing) / 1000) + + # Step 5: Record combined timing using print_timing with explicit_timing + @print_timing(interaction_name, explicit_timing=timing) + def record_result(): + pass + + record_result() + + def print_timing(interaction=None, explicit_timing=None): assert interaction is not None, "Interaction name is not passed to print_timing decorator" @@ -183,8 +232,9 @@ def wrapper(*args, **kwargs): with open(selenium_results_file, "a+") as jtl_file: timestamp = round(time() * 1000) if explicit_timing: + timing = str(int(explicit_timing * 1000)) jtl_file.write( - f"{timestamp},{explicit_timing*1000},{interaction},,{error_msg}," + f"{timestamp},{timing},{interaction},,{error_msg}," f",{success},0,0,0,0,,0\n") else: jtl_file.write( @@ -214,6 +264,9 @@ def driver_init(): chrome_options.add_argument("--headless") if not app_settings.secure: chrome_options.add_argument('--ignore-certificate-errors') + if app_settings.local_chrome_binary_path is not None: + chrome_options.binary_location = app_settings.local_chrome_binary_path + print(f"Using custom local chrome binary path: {chrome_options.binary_location}") chrome_options.add_argument( "--window-size={},{}".format(SCREEN_WIDTH, SCREEN_HEIGHT)) chrome_options.add_argument("--no-sandbox") @@ -222,8 +275,24 @@ def driver_init(): # Prevent Chrome from showing the search engine prompt chrome_options.add_argument("--disable-search-engine-choice-screen") chrome_options.add_argument("--no-first-run") - chrome_options.add_experimental_option( - 'prefs', {'intl.accept_languages': 'en,en_US'}) + # Build prefs by starting with the default language preference and + # deep-merging any user-supplied prefs on top, so user values win. + default_prefs = {'intl.accept_languages': 'en,en_US'} + user_experimental = app_settings.chrome_options['experimental_options'] + user_prefs = user_experimental.get('prefs', {}) + merged_prefs = {**default_prefs, **user_prefs} + chrome_options.add_experimental_option('prefs', merged_prefs) + + # Apply any remaining user-supplied experimental options (excluding prefs, + # which was already handled above). + for key, value in user_experimental.items(): + if key != 'prefs': + chrome_options.add_experimental_option(key, value) + + # Apply any user-supplied Chrome arguments from the YML config. + for argument in app_settings.chrome_options['arguments']: + chrome_options.add_argument(argument) + chrome_options.set_capability( 'goog:loggingPrefs', { 'performance': 'ALL'}) @@ -293,11 +362,22 @@ def get_requests_by_url(requests, url_path): return filtered_requests +def click_page_body(webdriver): + try: + body_element = webdriver.find_element("tag name", "body") + body_element.click() + print("Successfully clicked on page body") + except Exception as e: + print(f"Failed to click on page body: {e}") + + def get_wait_browser_metrics(webdriver, expected_metrics): attempts = 15 sleep_time = 0.5 data = {} + click_page_body(webdriver) #this resolves browser metric + for i in range(attempts): requests = get_performance_logs(webdriver) requests_bulk = get_requests_by_url(requests, 'bulk') @@ -396,8 +476,11 @@ def measure_browser_navi_metrics(webdriver, dataset, expected_metrics): lockfile = f'{selenium_results_file}.lock' error_msg = 'Success' success = True + ready_for_user_timing = None + if not metrics: - return + return ready_for_user_timing + with filelock.SoftFileLock(lockfile): with open(selenium_results_file, "a+") as jtl_file: for metric in metrics: @@ -409,6 +492,8 @@ def measure_browser_navi_metrics(webdriver, dataset, expected_metrics): f"{timestamp},{ready_for_user_timing},{interaction},,{error_msg},,{success},0,0,0,0,{node_ip},0\n") print( f"{timestamp},{ready_for_user_timing},{interaction},{error_msg},{success},{node_ip}") + + return ready_for_user_timing @pytest.fixture(scope="module") diff --git a/app/selenium_ui/jira/modules.py b/app/selenium_ui/jira/modules.py index 35811ce43..04e462d23 100644 --- a/app/selenium_ui/jira/modules.py +++ b/app/selenium_ui/jira/modules.py @@ -70,6 +70,8 @@ def measure(): @print_timing("selenium_login:open_login_page") def sub_measure(): login_page.go_to() + login_page.wait_for_login_page_loaded() + sub_measure() @print_timing("selenium_login:login_and_view_dashboard") @@ -77,6 +79,7 @@ def sub_measure(): login_page.set_credentials( username=datasets['current_session']['username'], password=datasets['current_session']['password']) + login_page.wait_for_dashboard_or_first_login_loaded() if login_page.is_first_login(): login_page.first_login_setup() if login_page.is_first_login_second_page(): diff --git a/app/selenium_ui/jira/pages/pages.py b/app/selenium_ui/jira/pages/pages.py index 6db876d47..39f36f4db 100644 --- a/app/selenium_ui/jira/pages/pages.py +++ b/app/selenium_ui/jira/pages/pages.py @@ -1,4 +1,5 @@ from selenium.webdriver.common.keys import Keys +from selenium.common.exceptions import ElementClickInterceptedException from selenium_ui.conftest import retry import time import random @@ -6,13 +7,13 @@ from selenium_ui.base_page import BasePage from selenium_ui.jira.pages.selectors import UrlManager, LoginPageLocators, DashboardLocators, PopupLocators, \ - IssueLocators, ProjectLocators, SearchLocators, BoardsListLocators, BoardLocators, LogoutLocators + IssueLocators, ProjectLocators, SearchLocators, BoardsListLocators, BoardLocators, LogoutLocators, AdminLocators class PopupManager(BasePage): def dismiss_default_popup(self): - return self.dismiss_popup(PopupLocators.default_popup, PopupLocators.popup_1, PopupLocators.popup_2) + return self.dismiss_popup(PopupLocators.popup_selectors) class Login(BasePage): @@ -20,9 +21,23 @@ class Login(BasePage): page_loaded_selector = LoginPageLocators.system_dashboard base_url = UrlManager().host + def __init__(self, driver): + super().__init__(driver) + self.is_2sv_login = False + def is_first_login(self): return True if self.get_elements(LoginPageLocators.continue_button) else False + def wait_for_login_page_loaded(self): + # TODO DCA-2394: Find proper logic to wait until login page is fully loaded + # Jira 10.0.1 has issue when clicking too fast on login button resulting in user is not logged in. + from time import sleep + sleep(1) + if not self.get_elements(LoginPageLocators.login_submit_button): + self.is_2sv_login = True + print("INFO: 2sv login form") + + def is_first_login_second_page(self): return True if self.get_elements(LoginPageLocators.avatar_page_next_button) else False @@ -38,9 +53,21 @@ def first_login_second_page_setup(self): self.wait_until_visible(DashboardLocators.dashboard_window) def set_credentials(self, username, password): - self.get_element(LoginPageLocators.login_field).send_keys(username) - self.get_element(LoginPageLocators.password_field).send_keys(password) - self.get_element(LoginPageLocators.login_submit_button).click() + login_field = LoginPageLocators.login_field + password_field = LoginPageLocators.password_field + submit_button = LoginPageLocators.login_submit_button + if self.is_2sv_login: + login_field = LoginPageLocators.login_field_2sv + password_field = LoginPageLocators.password_field_2sv + submit_button = LoginPageLocators.login_submit_button_2sv + + self.wait_until_visible(login_field).send_keys(username) + self.wait_until_visible(password_field).send_keys(password) + self.wait_until_visible(submit_button).click() + + def wait_for_dashboard_or_first_login_loaded(self): + self.wait_until_any_ec_presented((LoginPageLocators.system_dashboard, + LoginPageLocators.continue_button)) def __get_footer_text(self): return self.get_element(LoginPageLocators.footer).text @@ -113,7 +140,13 @@ 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() + element = self.get_element(IssueLocators.edit_issue_submit) + try: + element.click() + except ElementClickInterceptedException: + print('INFO: Trying JS click for edit_issue_submit button...') + self.driver.execute_script("arguments[0].click();", element) + def fill_description_edit(self, rte): text_description = f"Edit description form selenium - {self.generate_random_string(30)}" @@ -255,3 +288,22 @@ def go_to_backlog(self): def wait_for_scrum_board_backlog(self): self.wait_until_present(BoardLocators.scrum_board_backlog_content) + + +class AdminPage(BasePage): + page_url = AdminLocators.admin_system_page_url + page_loaded_selector = AdminLocators.login_form + + def is_websudo(self): + return True if self.get_elements(AdminLocators.web_sudo_password) else False + + def do_websudo(self, password): + self.wait_until_clickable(AdminLocators.web_sudo_password).send_keys(password) + self.wait_until_clickable(AdminLocators.web_sudo_submit_btn).click() + self.wait_until_visible(AdminLocators.admin_search_link) + + def go_to(self, password=None): + super().go_to() + self.wait_for_page_loaded() + if self.is_websudo(): + self.do_websudo(password) diff --git a/app/selenium_ui/jira/pages/selectors.py b/app/selenium_ui/jira/pages/selectors.py index 43fb8bde8..fb830b7b9 100644 --- a/app/selenium_ui/jira/pages/selectors.py +++ b/app/selenium_ui/jira/pages/selectors.py @@ -3,9 +3,11 @@ class PopupLocators: - default_popup = '.aui-message .icon-close' - popup_1 = 'form.tip-footer>.helptip-close' - popup_2 = '.aui-inline-dialog-contents .cancel' + popup_selectors = [ + (By.CSS_SELECTOR, ".aui-message .icon-close"), + (By.CSS_SELECTOR, "form.tip-footer>.helptip-close"), + (By.CSS_SELECTOR, ".aui-inline-dialog-contents .cancel") + ] class UrlManager: @@ -26,6 +28,7 @@ def __init__(self, issue_key=None, issue_id=None, project_key=None, jql=None, pr self.boards_list_params = '/secure/ManageRapidViews.jspa' self.scrum_board_backlog_params = f"/secure/RapidBoard.jspa?rapidView={board_id}&view=planning" self.scrum_board_params = f"/secure/RapidBoard.jspa?rapidView={board_id}" + self.admin_system_params = f"/secure/admin/ViewApplicationProperties.jspa" def login_url(self): return f"{self.host}{self.login_params}" @@ -63,6 +66,9 @@ def scrum_board_url(self): def logout_url(self): return f"{self.host}{self.logout_params}" + def admin_system_url(self): + return f"{self.host}{self.admin_system_params}" + class LoginPageLocators: login_url = UrlManager().login_url() @@ -75,6 +81,9 @@ class LoginPageLocators: login_field = (By.ID, 'login-form-username') password_field = (By.ID, 'login-form-password') login_submit_button = (By.ID, 'login-form-submit') + login_field_2sv = (By.ID, 'username-field') + password_field_2sv = (By.ID, 'password-field') + login_submit_button_2sv = (By.ID, 'login-button') system_dashboard = (By.ID, "dashboard") footer = (By.ID, 'footer-build-information') @@ -147,3 +156,11 @@ class BoardLocators: # Scrum boards scrum_board_backlog_content = (By.CSS_SELECTOR, "#ghx-backlog[data-rendered]:not(.browser-metrics-stale)") board_columns = (By.CSS_SELECTOR, ".ghx-column") + + +class AdminLocators: + admin_system_page_url = UrlManager().admin_system_url() + web_sudo_password = (By.ID, 'login-form-authenticatePassword') + web_sudo_submit_btn = (By.ID, 'login-form-submit') + login_form = (By.ID, 'login-form') + admin_search_link = (By.ID, 'admin-search-link') diff --git a/app/selenium_ui/jsm/modules_agents.py b/app/selenium_ui/jsm/modules_agents.py index ace28dc37..bfe991a95 100644 --- a/app/selenium_ui/jsm/modules_agents.py +++ b/app/selenium_ui/jsm/modules_agents.py @@ -92,6 +92,7 @@ def measure(): @print_timing("selenium_agent_login:open_login_page") def sub_measure(): login_page.go_to() + login_page.is_2sv() sub_measure() @print_timing("selenium_agent_login:login_and_view_dashboard") diff --git a/app/selenium_ui/jsm/modules_customers.py b/app/selenium_ui/jsm/modules_customers.py index 9789ab86f..5c4d227bb 100644 --- a/app/selenium_ui/jsm/modules_customers.py +++ b/app/selenium_ui/jsm/modules_customers.py @@ -70,11 +70,11 @@ def measure(): @print_timing("selenium_customer_login:open_login_page") def sub_measure(): login_page.go_to() + login_page.is_2sv() webdriver.app_version = login_page.get_app_version() if login_page.is_logged_in(): login_page.delete_all_cookies() login_page.go_to() - login_page.wait_for_page_loaded() sub_measure() @print_timing("selenium_customer_login:login_and_view_portal") diff --git a/app/selenium_ui/jsm/pages/agent_pages.py b/app/selenium_ui/jsm/pages/agent_pages.py index 888db905f..8e3e8895e 100644 --- a/app/selenium_ui/jsm/pages/agent_pages.py +++ b/app/selenium_ui/jsm/pages/agent_pages.py @@ -7,11 +7,8 @@ class PopupManager(BasePage): - def dismiss_default_popup(self): - return self.dismiss_popup(PopupLocators.default_popup, PopupLocators.popup_1, PopupLocators.popup_2, - PopupLocators.popup_3, PopupLocators.popup_4, - PopupLocators.popup_5, PopupLocators.popup_6, PopupLocators.popup_7) + return self.dismiss_popup(PopupLocators.popup_selectors) class Login(BasePage): @@ -19,9 +16,18 @@ class Login(BasePage): base_url = UrlManager().host page_loaded_selector = LoginPageLocators.system_dashboard + def __init__(self, driver): + super().__init__(driver) + self.is_2sv_login = False + def is_first_login(self): return True if self.get_elements(LoginPageLocators.continue_button) else False + def is_2sv(self): + if not self.get_elements(LoginPageLocators.login_submit_button): + self.is_2sv_login = True + print("INFO: 2sv login form") + def is_first_login_second_page(self): return True if self.get_elements(LoginPageLocators.avatar_page_next_button) else False @@ -37,9 +43,21 @@ def first_login_second_page_setup(self): self.wait_until_visible(DashboardLocators.dashboard_window) def set_credentials(self, username, password): - self.get_element(LoginPageLocators.login_field).send_keys(username) - self.get_element(LoginPageLocators.password_field).send_keys(password) - self.get_element(LoginPageLocators.login_submit_button).click() + login_field = LoginPageLocators.login_field + password_field = LoginPageLocators.password_field + submit_button = LoginPageLocators.login_submit_button + if self.is_2sv_login: + login_field = LoginPageLocators.login_field_2sv + password_field = LoginPageLocators.password_field_2sv + submit_button = LoginPageLocators.login_submit_button_2sv + + self.wait_until_visible(login_field).send_keys(username) + self.wait_until_visible(password_field).send_keys(password) + self.wait_until_visible(submit_button).click() + + def wait_for_dashboard_or_first_login_loaded(self): + self.wait_until_any_ec_presented((LoginPageLocators.system_dashboard, + LoginPageLocators.continue_button)) def __get_footer_text(self): return self.get_element(LoginPageLocators.footer).text @@ -98,38 +116,27 @@ def __init__(self, driver, request_key=None): def wait_for_page_loaded(self): self.wait_until_visible(ViewCustomerRequestLocators.bread_crumbs) - def check_comment_text_is_displayed(self, text, rte_status=None): - if self.get_elements(ViewCustomerRequestLocators.comment_text_field_RTE) or \ - self.get_elements(ViewCustomerRequestLocators.comment_text_field): - if rte_status: - self.wait_until_available_to_switch(ViewCustomerRequestLocators.comment_text_field_RTE) - if self.wait_until_present(ViewCustomerRequestLocators.comment_tinymce_field).text != text: - self.wait_until_present(ViewCustomerRequestLocators.comment_tinymce_field).send_keys(text) - self.return_to_parent_frame() - self.wait_until_present(ViewCustomerRequestLocators.comment_internally_btn).click() - elif self.wait_until_present(ViewCustomerRequestLocators.comment_text_field).text != text: - self.wait_until_present(ViewCustomerRequestLocators.comment_text_field).send_keys(text) - self.wait_until_present(ViewCustomerRequestLocators.comment_internally_btn).click() - def add_request_comment(self, rte_status): comment_text = f"Add comment from selenium - {self.generate_random_string(30)}" self.wait_until_visible(ViewCustomerRequestLocators.comment_area) - textarea = self.get_element(ViewCustomerRequestLocators.comment_collapsed_textarea) - self.driver.execute_script("arguments[0].scrollIntoView(true);", textarea) - textarea.click() - comment_button = self.get_element(ViewCustomerRequestLocators.comment_internally_btn) - self.driver.execute_script("arguments[0].scrollIntoView(true);", comment_button) + self.driver.execute_script("arguments[0].scrollIntoView(true);", + self.wait_until_present(ViewCustomerRequestLocators.comment_collapsed_textarea)) + self.wait_until_clickable(ViewCustomerRequestLocators.comment_collapsed_textarea).click() + if self.get_elements(ViewCustomerRequestLocators.comment_internally_btn): + comment_button_selector = ViewCustomerRequestLocators.comment_internally_btn + else: + comment_button_selector = ViewCustomerRequestLocators.comment_internally_btn_jsm10 + self.driver.execute_script("arguments[0].scrollIntoView(true);", + self.wait_until_present(comment_button_selector)) if rte_status: self.wait_until_available_to_switch(ViewCustomerRequestLocators.comment_text_field_RTE) self.wait_until_present(ViewCustomerRequestLocators.comment_tinymce_field).send_keys(comment_text) self.return_to_parent_frame() - comment_button.click() - self.check_comment_text_is_displayed(comment_text, True) + self.safe_click(comment_button_selector) else: self.wait_until_present(ViewCustomerRequestLocators.comment_text_field).send_keys(comment_text) - comment_button.click() - self.check_comment_text_is_displayed(comment_text) + self.safe_click(comment_button_selector) class Report: @@ -198,9 +205,16 @@ def __init__(self, driver): self.page_url = url_manager.view_insight_all_schemas() def submit_login(self, username, password): - self.get_element(LoginPageLocators.login_field).send_keys(username) - self.get_element(LoginPageLocators.password_field).send_keys(password) - self.get_element(LoginPageLocators.login_submit_button).click() + self.wait_until_visible(LoginPageLocators.login_form) + if self.get_elements(LoginPageLocators.login_field): + self.get_element(LoginPageLocators.login_field).send_keys(username) + self.get_element(LoginPageLocators.password_field).send_keys(password) + self.get_element(LoginPageLocators.login_submit_button).click() + else: + self.get_element(LoginPageLocators.login_field_2sv).send_keys(username) + self.get_element(LoginPageLocators.password_field_2sv).send_keys(password) + self.get_element(LoginPageLocators.login_submit_button_2sv).click() + class InsightNewSchema(BasePage): @@ -307,9 +321,10 @@ def wait_for_page_loaded(self): def view_random_queue_with_insight(self): self.wait_until_visible(InsightViewQueueLocators.table_container) - if not self.get_elements(InsightViewQueueLocators.navigation): + if self.get_elements(InsightViewQueueLocators.all_queues): self.wait_until_visible(InsightViewQueueLocators.view_queue_insight_column) else: + self.wait_until_visible(InsightViewQueueLocators.navigation) self.wait_until_visible(InsightViewQueueLocators.view_queue_insight_column_jsm10) diff --git a/app/selenium_ui/jsm/pages/agent_selectors.py b/app/selenium_ui/jsm/pages/agent_selectors.py index 6c0af3819..d5b11d869 100644 --- a/app/selenium_ui/jsm/pages/agent_selectors.py +++ b/app/selenium_ui/jsm/pages/agent_selectors.py @@ -3,14 +3,17 @@ class PopupLocators: - default_popup = '.aui-message .icon-close' - popup_1 = 'form.tip-footer>.helptip-close' - popup_2 = '.aui-inline-dialog-contents .cancel' - popup_3 = '.aui-close-button' - popup_4 = '.aui-button aui-button-link' - popup_5 = '.buttons-container > div > a' - popup_6 = '.css-19r5em7' - popup_7 = '.css-178ag6o' + popup_selectors = [ + (By.CSS_SELECTOR, ".aui-message .icon-close"), + (By.CSS_SELECTOR, "form.tip-footer>.helptip-close"), + (By.CSS_SELECTOR, ".aui-inline-dialog-contents .cancel"), + (By.CSS_SELECTOR, ".aui-close-button"), + (By.CSS_SELECTOR, ".aui-button aui-button-link"), + (By.CSS_SELECTOR, ".buttons-container > div > a"), + (By.CSS_SELECTOR, ".css-19r5em7"), + (By.CSS_SELECTOR, ".css-178ag6o"), + (By.XPATH, "//button[contains(text(),'Got it')]") + ] class UrlManager: @@ -83,8 +86,12 @@ class LoginPageLocators: login_field = (By.ID, 'login-form-username') password_field = (By.ID, 'login-form-password') login_submit_button = (By.ID, 'login-form-submit') + login_field_2sv = (By.ID, 'username-field') + password_field_2sv = (By.ID, 'password-field') + login_submit_button_2sv = (By.ID, 'login-button') system_dashboard = (By.ID, "dashboard") footer = (By.ID, 'footer-build-information') + login_form = (By.ID, "login-form") class DashboardLocators: @@ -116,6 +123,7 @@ class ViewCustomerRequestLocators: comment_text_field = (By.XPATH, "//textarea[@id='comment']") comment_tinymce_field = (By.ID, "tinymce") comment_internally_btn = (By.XPATH, "//button[contains(text(),'Comment internally')]") + comment_internally_btn_jsm10 = (By.XPATH, "//span[normalize-space()='Comment internally']") customers_sidebar_selector = (By.CSS_SELECTOR, 'span.aui-icon.aui-icon-large.sd-sidebar-icon.icon-sidebar-customers') @@ -168,6 +176,7 @@ class InsightNewObjectLocators: class InsightViewQueueLocators: + all_queues = (By.XPATH, "//div[@class='aui-nav-heading pinned-nav-title nav-group-items']") view_queue_page = (By.XPATH, "//section[@id='sd-page-panel']") view_queue_insight_column = (By.XPATH, "//span[contains(text(),'Insight')]") table_container = (By.XPATH, "//div[@class='queue-react-table-container']") @@ -177,7 +186,7 @@ class InsightViewQueueLocators: class InsightSearchObjectIql: search_object_text_field = (By.CSS_SELECTOR, "textarea[name='iql']") - search_iql_button = (By.CLASS_NAME, "rIcon-search") + search_iql_button = (By.CSS_SELECTOR, ".rIcon-search, .rlIqlEditor ~ button") search_iql_success = (By.XPATH, "//thead/tr[1]") diff --git a/app/selenium_ui/jsm/pages/customer_pages.py b/app/selenium_ui/jsm/pages/customer_pages.py index 3fdd8ecc1..eaf32c0c7 100644 --- a/app/selenium_ui/jsm/pages/customer_pages.py +++ b/app/selenium_ui/jsm/pages/customer_pages.py @@ -14,19 +14,38 @@ class Login(BasePage): base_url = UrlManager().host page_loaded_selector = LoginPageLocators.login_submit_button + def __init__(self, driver): + super().__init__(driver) + self.is_2sv_login = False + + def is_2sv(self): + if not self.get_elements(LoginPageLocators.login_submit_button): + self.is_2sv_login = True + print("INFO: 2sv login form") + def set_credentials(self, username, password): - self.wait_until_visible(LoginPageLocators.login_field) - self.get_element(LoginPageLocators.login_field).send_keys(username) - self.get_element(LoginPageLocators.password_field).send_keys(password) - self.get_element(LoginPageLocators.login_submit_button).click() + if self.is_2sv_login: + self.wait_until_visible(LoginPageLocators.login_field_2sv) + self.get_element(LoginPageLocators.login_field_2sv).send_keys(username) + self.get_element(LoginPageLocators.password_field_2sv).send_keys(password) + self.get_element(LoginPageLocators.login_submit_button_2sv).click() + else: + self.wait_until_visible(LoginPageLocators.login_field) + self.get_element(LoginPageLocators.login_field).send_keys(username) + self.get_element(LoginPageLocators.password_field).send_keys(password) + self.get_element(LoginPageLocators.login_submit_button).click() def is_logged_in(self): elements = self.get_elements(CustomerPortalsSelectors.welcome_logged_in_page) return True if elements else False def get_app_version(self): - version_str = self.get_element(LoginPageLocators.app_version).get_attribute('content') - return version.parse(version_str) + if self.is_2sv_login: + version_str = self.get_element(LoginPageLocators.app_version_2sv).get_attribute('data-version') + return version.parse(version_str) + else: + version_str = self.get_element(LoginPageLocators.app_version).get_attribute('content') + return version.parse(version_str) class TopPanel(BasePage): @@ -37,7 +56,11 @@ def open_profile_menu(self): def logout(self): self.get_element(TopPanelSelectors.logout_button).click() - self.wait_until_visible(LoginPageLocators.login_field) + self.wait_until_visible(LoginPageLocators.login_form) + if not self.get_elements(LoginPageLocators.login_submit_button): + self.wait_until_visible(LoginPageLocators.login_field_2sv) + else: + self.wait_until_visible(LoginPageLocators.login_field) class CustomerPortals(BasePage): @@ -187,7 +210,7 @@ def __init__(self, driver, all_requests=False): url_manager = UrlManager() self.page_url = url_manager.all_requests_url() if all_requests else url_manager.my_requests_url() - page_loaded_selector = RequestsSelectors.requests_label + page_loaded_selector = RequestsSelectors.all_requests_filter class ViewRequestWithInsight(BasePage): diff --git a/app/selenium_ui/jsm/pages/customer_selectors.py b/app/selenium_ui/jsm/pages/customer_selectors.py index ae94d67d1..071a2a910 100644 --- a/app/selenium_ui/jsm/pages/customer_selectors.py +++ b/app/selenium_ui/jsm/pages/customer_selectors.py @@ -38,7 +38,12 @@ class LoginPageLocators: login_field = (By.ID, 'os_username') password_field = (By.ID, 'os_password') login_submit_button = (By.ID, 'js-login-submit') + login_field_2sv = (By.ID, 'username-field') + password_field_2sv = (By.ID, 'password-field') + login_submit_button_2sv = (By.ID, 'login-button') app_version = (By.NAME, "ajs-sdPluginVersion") + app_version_2sv = (By.NAME, "application-name") + login_form = (By.ID, "login-form") class TopPanelSelectors: @@ -107,3 +112,4 @@ class InsightSelectors: class RequestsSelectors: my_requests_url = UrlManager().my_requests_url() requests_label = (By.XPATH, "//h2[contains(text(),'Requests')]") + all_requests_filter = (By.ID, "sd-all-requests-filter") diff --git a/app/util/analytics/analytics.py b/app/util/analytics/analytics.py index 631727961..43e90f8af 100644 --- a/app/util/analytics/analytics.py +++ b/app/util/analytics/analytics.py @@ -52,6 +52,9 @@ def __init__(self, application: BaseApplication): self.application_version = application.version self.nodes_count = application.nodes_count self.dataset_information = application.dataset_information + self.apps_count = application.apps_count + self.custom_apps_count = application.custom_app_count + self.custom_apps_count_enabled = application.custom_app_count_enabled if self.app_type != CROWD: self.processors = application.processors self.deployment = application.deployment diff --git a/app/util/analytics/analytics_utils.py b/app/util/analytics/analytics_utils.py index a73b67030..c6ccd6c67 100644 --- a/app/util/analytics/analytics_utils.py +++ b/app/util/analytics/analytics_utils.py @@ -7,7 +7,7 @@ from datetime import datetime, timezone from util.common_util import get_current_version, get_latest_version -from util.analytics.application_info import BITBUCKET, BAMBOO, CROWD, INSIGHT, JSM, CONFLUENCE +from util.analytics.application_info import BITBUCKET, BAMBOO, CROWD, INSIGHT, JSM, CONFLUENCE, JIRA latest_version = get_latest_version() current_version = get_current_version() @@ -107,6 +107,11 @@ def generate_report_summary(collector): summary_report.append(f'Success|{success}') summary_report.append(f'Has app-specific actions|{bool(collector.app_specific_rates)}') + + summary_report.append(f'Applications count|{collector.apps_count}') + summary_report.append(f'Custom applications count|{collector.custom_apps_count}') + summary_report.append(f'Custom applications count enabled|{collector.custom_apps_count_enabled}') + if collector.app_type == JSM: insight = collector.insight summary_report.append(f'Insight|{insight}') diff --git a/app/util/analytics/application_info.py b/app/util/analytics/application_info.py index 6a1c4b405..d6fc46928 100644 --- a/app/util/analytics/application_info.py +++ b/app/util/analytics/application_info.py @@ -8,6 +8,7 @@ from util.api.bamboo_clients import BambooClient import json from lxml import html +from functools import cached_property JIRA = 'jira' CONFLUENCE = 'confluence' @@ -31,6 +32,9 @@ class BaseApplication: version = None nodes_count = None dataset_information = None + apps_count = None + custom_app_count = None + custom_app_count_enabled = None def __init__(self, api_client, config_yml): self.client = api_client(host=config_yml.server_url, @@ -99,6 +103,36 @@ def __issues_count(self): def dataset_information(self): return f"{self.__issues_count()} issues" + @property + def apps_count(self): + return len(self.__get_apps) + + @cached_property + def __get_apps(self): + try: + return self.client.get_installed_apps() + except Exception as e: + print(f'ERROR: Could not get the installed applications. Error: {e}') + return [] + + @cached_property + def __get_custom_apps(self): + all_apps = self.__get_apps + apps_with_vendor_defined = [app for app in all_apps if 'vendor' in app] + non_atlassian_apps = [app for app in apps_with_vendor_defined if 'Atlassian' not in + app['vendor']['name'] and app['userInstalled'] == True] + return non_atlassian_apps + + @property + def custom_app_count(self): + return len(self.__get_custom_apps) + + @property + def custom_app_count_enabled(self): + non_atlassian_apps = self.__get_custom_apps + non_atlassian_apps_enabled = [app for app in non_atlassian_apps if app['enabled'] == True] + return len(non_atlassian_apps_enabled) + class Confluence(BaseApplication): type = CONFLUENCE @@ -119,12 +153,45 @@ def dataset_information(self): @property def java_version(self): - full_system_info = self.client.get_system_info_page() - java_versions_parsed = html.fromstring(full_system_info).xpath('//*[contains(@id, "java.version")]') - if java_versions_parsed: - return java_versions_parsed[0].text + try: + full_system_info = self.client.get_system_info_page() + java_versions_parsed = html.fromstring(full_system_info).xpath('//*[contains(@id, "java.version")]') + if java_versions_parsed: + return java_versions_parsed[0].text + except Exception as e: + print(f'ERROR: Could not get the Java version. Error: {e}') return None + @property + def apps_count(self): + return len(self.__get_apps) + + @cached_property + def __get_apps(self): + try: + return self.client.get_installed_apps() + except Exception as e: + print(f'ERROR: Could not get the installed applications. Error: {e}') + return [] + + @cached_property + def __get_custom_apps(self): + all_apps = self.__get_apps + apps_with_vendor_defined = [app for app in all_apps if 'vendor' in app] + non_atlassian_apps = [app for app in apps_with_vendor_defined if 'Atlassian' not in + app['vendor']['name'] and app['userInstalled'] == True] + return non_atlassian_apps + + @property + def custom_app_count(self): + return len(self.__get_custom_apps) + + @property + def custom_app_count_enabled(self): + non_atlassian_apps = self.__get_custom_apps + non_atlassian_apps_enabled = [app for app in non_atlassian_apps if app['enabled'] == True] + return len(non_atlassian_apps_enabled) + class Bitbucket(BaseApplication): type = BITBUCKET @@ -142,6 +209,37 @@ def nodes_count(self): def dataset_information(self): return f'{self.client.get_bitbucket_repo_count()} repositories' + @property + def apps_count(self): + return len(self.__get_apps) + + @cached_property + def __get_apps(self): + try: + return self.client.get_installed_apps() + except Exception as e: + print(f'ERROR: Could not get the installed applications. Error: {e}') + return [] + + @cached_property + def __get_custom_apps(self): + all_apps = self.__get_apps + apps_with_vendor_defined = [app for app in all_apps if 'vendor' in app] + non_atlassian_apps = [app for app in apps_with_vendor_defined if 'Atlassian' not in + app['vendor']['name'] and app['userInstalled'] == True] + return non_atlassian_apps + + @property + def custom_app_count(self): + return len(self.__get_custom_apps) + + @property + def custom_app_count_enabled(self): + non_atlassian_apps = self.__get_custom_apps + non_atlassian_apps_enabled = [app for app in non_atlassian_apps if app['enabled'] == True] + return len(non_atlassian_apps_enabled) + + class Jsm(BaseApplication): type = JSM @@ -164,6 +262,37 @@ def __issues_count(self): def dataset_information(self): return f"{self.__issues_count()} issues" + @property + def apps_count(self): + return len(self.__get_apps) + + @cached_property + def __get_apps(self): + try: + return self.client.get_installed_apps() + except Exception as e: + print(f'ERROR: Could not get the installed applications. Error: {e}') + return [] + + @cached_property + def __get_custom_apps(self): + all_apps = self.__get_apps + apps_with_vendor_defined = [app for app in all_apps if 'vendor' in app] + non_atlassian_apps = [app for app in apps_with_vendor_defined if 'Atlassian' not in + app['vendor']['name'] and app['userInstalled'] == True] + return non_atlassian_apps + + @property + def custom_app_count(self): + return len(self.__get_custom_apps) + + @property + def custom_app_count_enabled(self): + non_atlassian_apps = self.__get_custom_apps + non_atlassian_apps_enabled = [app for app in non_atlassian_apps if app['enabled'] == True] + return len(non_atlassian_apps_enabled) + + class Crowd(BaseApplication): type = CROWD @@ -189,6 +318,36 @@ def __users_count(self): def dataset_information(self): return f"{self.__users_count()} users" + @property + def apps_count(self): + return len(self.__get_apps) + + @cached_property + def __get_apps(self): + try: + return self.client.get_installed_apps() + except Exception as e: + print(f'ERROR: Could not get the installed applications. Error: {e}') + return [] + + @cached_property + def __get_custom_apps(self): + all_apps = self.__get_apps + apps_with_vendor_defined = [app for app in all_apps if 'vendor' in app] + non_atlassian_apps = [app for app in apps_with_vendor_defined if 'Atlassian' not in + app['vendor']['name'] and app['userInstalled'] == True] + return non_atlassian_apps + + @property + def custom_app_count(self): + return len(self.__get_custom_apps) + + @property + def custom_app_count_enabled(self): + non_atlassian_apps = self.__get_custom_apps + non_atlassian_apps_enabled = [app for app in non_atlassian_apps if app['enabled'] == True] + return len(non_atlassian_apps_enabled) + class Bamboo(BaseApplication): type = BAMBOO @@ -210,6 +369,36 @@ def __build_plans_count(self): def dataset_information(self): return f"{self.__build_plans_count()} build plans" + @property + def apps_count(self): + return len(self.__get_apps) + + @cached_property + def __get_apps(self): + try: + return self.client.get_installed_apps() + except Exception as e: + print(f'ERROR: Could not get the installed applications. Error: {e}') + return [] + + @cached_property + def __get_custom_apps(self): + all_apps = self.__get_apps + apps_with_vendor_defined = [app for app in all_apps if 'vendor' in app] + non_atlassian_apps = [app for app in apps_with_vendor_defined if 'Atlassian' not in + app['vendor']['name'] and app['userInstalled'] == True] + return non_atlassian_apps + + @property + def custom_app_count(self): + return len(self.__get_custom_apps) + + @property + def custom_app_count_enabled(self): + non_atlassian_apps = self.__get_custom_apps + non_atlassian_apps_enabled = [app for app in non_atlassian_apps if app['enabled'] == True] + return len(non_atlassian_apps_enabled) + class Insight(Jsm): type = INSIGHT diff --git a/app/util/api/abstract_clients.py b/app/util/api/abstract_clients.py index 6fa0bedd2..f975dcf38 100644 --- a/app/util/api/abstract_clients.py +++ b/app/util/api/abstract_clients.py @@ -50,6 +50,7 @@ def __init__(self, host, user, password, verify=False, headers=None, session=Non self._requests_timeout = timeout self._session = session or requests.Session() + self._session.verify = verify self.headers = headers if headers else JSON_HEADERS self.verify = verify diff --git a/app/util/api/bamboo_clients.py b/app/util/api/bamboo_clients.py index 7d796950f..b46ea2f50 100644 --- a/app/util/api/bamboo_clients.py +++ b/app/util/api/bamboo_clients.py @@ -246,3 +246,9 @@ def get_status(self): else: print(f"Warning: failed to get {api_url}: Error: {e}") return False + + def get_installed_apps(self): + api_url = f'{self.host}/rest/plugins/1.0/' + r = self.get(api_url, error_msg="ERROR: Could not get installed plugins.", + headers={'X-Atlassian-Token': 'no-check'}) + return r.json()['plugins'] diff --git a/app/util/api/bitbucket_clients.py b/app/util/api/bitbucket_clients.py index d29557102..3b932361b 100644 --- a/app/util/api/bitbucket_clients.py +++ b/app/util/api/bitbucket_clients.py @@ -200,3 +200,9 @@ def get_status(self): else: print(f"Warning: failed to get {api_url}: Error: {e}") return False + + def get_installed_apps(self): + plugins_url = f'{self.host}/rest/plugins/1.0/' + r = self.get(plugins_url, error_msg="ERROR: Could not get installed plugins.", + headers={'X-Atlassian-Token': 'no-check'}) + return r.json()['plugins'] diff --git a/app/util/api/confluence_clients.py b/app/util/api/confluence_clients.py index 9570a616a..7fb0b35ba 100644 --- a/app/util/api/confluence_clients.py +++ b/app/util/api/confluence_clients.py @@ -135,9 +135,15 @@ def get_confluence_nodes(self): error_msg='Could not get Confluence nodes count via API', expected_status_codes=[ 200, + 401, 403, 500]) - if response.status_code == 403 and 'clustered installation' in response.text: + if (response.status_code in [401,403] and + ('clustered installation' in response.text or + ('www-authenticate' in response.headers and + 'clustered installation' in response.headers['www-authenticate']) + ) + ): return 'Server' nodes = [node['id'] for node in response.json()['nodes']] return nodes @@ -190,6 +196,7 @@ def get_groups_membership(self, username): groups = [group['name'] for group in response.json()['results']] return groups + @retry() def get_system_info_page(self): login_url = f'{self.host}/dologin.action' auth_url = f'{self.host}/doauthenticate.action' @@ -202,9 +209,9 @@ def get_system_info_page(self): } auth_body = { - 'destination': '/admin/systeminfo.action', 'authenticate': 'Confirm', - 'password': self.password + 'destination': '/admin/systeminfo.action', + 'password': self.password, } login_page_response = self.session.get(login_url) @@ -219,15 +226,28 @@ def get_system_info_page(self): else: self.session.post(url=tsv_auth_url, json=tsv_login_body) - self.headers['X-Atlassian-Token'] = 'no-check' - system_info_html = self.session.post(url=auth_url, data=auth_body, headers=self.headers) - return system_info_html.content.decode("utf-8") + system_info_html = self.session.post(url=auth_url, data=auth_body, headers={'X-Atlassian-Token': 'no-check'}, verify=self.verify) + page_decoded = system_info_html.content.decode("utf-8") + if page_decoded: + return page_decoded + else: + raise Exception(f"ERROR: System info page is empty. Content: {page_decoded}") + + def get_installed_apps(self): + plugins_url = f'{self.host}/rest/plugins/1.0/' + r = self.get(plugins_url, error_msg="ERROR: Could not get installed plugins.", + headers={'X-Atlassian-Token': 'no-check'}) + return r.json()['plugins'] + def get_deployment_type(self): - html_pattern = 'com.atlassian.dcapt.deployment=terraform' - confluence_system_page = self.get_system_info_page() - if confluence_system_page.count(html_pattern): - return 'terraform' + try: + html_pattern = 'deployment=terraform' + confluence_system_page = self.get_system_info_page() + if confluence_system_page.count(html_pattern): + return 'terraform' + except Exception as e: + print(f"Warning: Could not get deployment type. Error: {e}") return 'other' def get_node_ip(self, node_id: str) -> str: diff --git a/app/util/api/crowd_clients.py b/app/util/api/crowd_clients.py index 9695249c6..089b4480a 100644 --- a/app/util/api/crowd_clients.py +++ b/app/util/api/crowd_clients.py @@ -71,7 +71,7 @@ def search_users(i): return loop_users num_cores = cpu_count() - pool = ThreadPool(processes=num_cores*2) + pool = ThreadPool(processes=max(round(num_cores / 4), 2)) loop_users_list = pool.map(search_users, [i for i in range(loop_count)]) print("") # new line users = [user for loop_users in loop_users_list for user in loop_users] @@ -108,3 +108,9 @@ def get_status(self): else: print(f"Warning: failed to get {api_url}: Error: {e}") return False + + def get_installed_apps(self): + api_url = f'{self.host}/rest/plugins/1.0/' + r = self.get(api_url, error_msg="ERROR: Could not get installed plugins.", + headers={'X-Atlassian-Token': 'no-check'}) + return r.json()['plugins'] diff --git a/app/util/api/jira_clients.py b/app/util/api/jira_clients.py index 81d16da73..9ed026169 100644 --- a/app/util/api/jira_clients.py +++ b/app/util/api/jira_clients.py @@ -1,8 +1,11 @@ import json import string +import time + from selenium.common.exceptions import WebDriverException -from util.api.abstract_clients import RestClient, JSM_EXPERIMENTAL_HEADERS +from util.api.abstract_clients import RestClient + from selenium_ui.conftest import retry BATCH_SIZE_BOARDS = 1000 @@ -201,15 +204,36 @@ def get_nodes(self): def get_system_info_page(self): login_url = f'{self.host}/login.jsp' auth_url = f'{self.host}/secure/admin/WebSudoAuthenticate.jspa' + tsv_login_url = f'{self.host}/rest/tsv/1.0/authenticate' auth_body = { 'webSudoDestination': '/secure/admin/ViewSystemInfo.jspa', 'webSudoIsPost': False, 'webSudoPassword': self.password } - self.post(login_url, error_msg='Could not login in') + tsv_login_body = {"username": self.user, + "password": self.password, + "rememberMe": "True", + "targetUrl": "" + } + + r = self.session.get(login_url) + if not r.status_code == 200: + raise Exception(f'ERROR: Could not login to Jira with url {login_url}, status code: {r.status_code}') + legacy_login = 'login-form-remember-me' in r.text + if not legacy_login: + print(f'INFO: 2sv login on Jira detected') + self.session.post(url=tsv_login_url, json=tsv_login_body) + else: + self.post(login_url, error_msg='Could not login in') auth_body['atl_token'] = self.session.cookies.get_dict()['atlassian.xsrf.token'] - system_info_html = self._session.post(auth_url, data=auth_body, verify=self.verify) - return system_info_html.content.decode("utf-8") + number_of_attempts = 5 + for _ in range(0, number_of_attempts): + system_info_html = self.session.post(auth_url, data=auth_body, headers={'X-Atlassian-Token': 'no-check'}, verify=self.verify) + if system_info_html.status_code == 200: + return system_info_html.content.decode("utf-8") + time.sleep(1) + print(f"ERROR: Could not get the system information page.") + return "" def get_available_processors(self): try: @@ -284,3 +308,9 @@ def get_license_details(self): 'image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' r = self.get(api_url, "Could not retrieve license details") return r.json() + + def get_installed_apps(self): + api_url = f'{self.host}/rest/plugins/1.0/' + r = self.get(api_url, error_msg="ERROR: Could not get installed plugins.", + headers={'X-Atlassian-Token': 'no-check'}) + return r.json()['plugins'] diff --git a/app/util/bamboo/bamboo_dataset_generator/pom.xml b/app/util/bamboo/bamboo_dataset_generator/pom.xml index 558a4b3a9..0c0df20a7 100644 --- a/app/util/bamboo/bamboo_dataset_generator/pom.xml +++ b/app/util/bamboo/bamboo_dataset_generator/pom.xml @@ -5,7 +5,7 @@ com.atlassian.bamboo bamboo-specs-parent - 9.6.6 + 10.2.14 @@ -18,7 +18,7 @@ org.codehaus.mojo exec-maven-plugin - 3.5.0 + 3.6.2 @@ -62,27 +62,27 @@ com.google.guava guava - 33.3.1-jre + 33.5.0-jre com.fasterxml.jackson.core jackson-core - 2.18.1 + 2.21.1 com.fasterxml.jackson.core jackson-databind - 2.18.1 + 2.20.1 com.jayway.jsonpath json-path - 2.9.0 + 2.10.0 net.minidev json-smart - 2.5.1 + 2.6.0 org.slf4j @@ -92,22 +92,22 @@ commons-codec commons-codec - 1.17.1 + 1.20.0 org.apache.logging.log4j log4j-api - 2.24.1 + 2.25.3 org.apache.logging.log4j log4j-core - 2.24.1 + 2.25.4 org.apache.logging.log4j log4j-slf4j-impl - 2.24.1 + 2.25.3 @@ -124,4 +124,4 @@ https://packages.atlassian.com/mvn/maven-external/ - \ No newline at end of file + diff --git a/app/util/conf.py b/app/util/conf.py index 8e367c966..d21cee4b2 100644 --- a/app/util/conf.py +++ b/app/util/conf.py @@ -2,8 +2,8 @@ from util.project_paths import JIRA_YML, CONFLUENCE_YML, BITBUCKET_YML, JSM_YML, CROWD_YML, BAMBOO_YML -TOOLKIT_VERSION = '8.5.0' -UNSUPPORTED_VERSION = '8.1.0' +TOOLKIT_VERSION = '8.10.2' +UNSUPPORTED_VERSION = '8.7.0' def read_yml_file(file): @@ -35,6 +35,16 @@ def __init__(self, config_yml): def server_url(self): return f'{self.protocol}://{self.hostname}:{self.port}{self.postfix}' + @property + def chrome_options(self): + # Returns user-defined chrome options from the YML env section. + # Supports 'arguments' (list) and 'experimental_options' (dict). + options = self.env_settings.get('chrome_options') or {} + return { + 'arguments': options.get('arguments') or [], + 'experimental_options': options.get('experimental_options') or {}, + } + def get_property(self, property_name): if property_name not in self.env_settings: raise Exception(f'Application property {property_name} was not found in .yml configuration file') @@ -50,6 +60,7 @@ def __init__(self, config_yml): self.custom_dataset_query = self.get_property('custom_dataset_query') or "" self.verbose = self.settings['verbose'] self.total_actions_per_hour = self.get_property('total_actions_per_hour') + self.local_chrome_binary_path = self.get_property('local_chrome_binary_path') class ConfluenceSettings(BaseAppSettings): @@ -62,6 +73,7 @@ def __init__(self, config_yml): self.verbose = self.settings['verbose'] self.total_actions_per_hour = self.get_property('total_actions_per_hour') self.extended_metrics = self.get_property('extended_metrics') + self.local_chrome_binary_path = self.get_property('local_chrome_binary_path') class BitbucketSettings(BaseAppSettings): @@ -72,6 +84,7 @@ def __init__(self, config_yml): self.concurrency = self.get_property('concurrency') self.verbose = self.settings['verbose'] self.total_actions_per_hour = self.get_property('total_actions_per_hour') + self.local_chrome_binary_path = self.get_property('local_chrome_binary_path') class JsmSettings(BaseAppSettings): @@ -87,6 +100,7 @@ def __init__(self, config_yml): self.custom_dataset_query = self.get_property('custom_dataset_query') or "" self.verbose = self.settings['verbose'] self.insight = self.get_property('insight') + self.local_chrome_binary_path = self.get_property('local_chrome_binary_path') class CrowdSettings(BaseAppSettings): @@ -112,6 +126,7 @@ def __init__(self, config_yml): self.start_plan_timeout = self.env_settings['start_plan_timeout'] self.default_dataset_plan_duration = self.env_settings['default_dataset_plan_duration'] self.total_actions_per_hour = self.get_property('total_actions_per_hour') + self.local_chrome_binary_path = self.get_property('local_chrome_binary_path') JIRA_SETTINGS = JiraSettings(config_yml=JIRA_YML) diff --git a/app/util/data_preparation/bamboo_prepare_data.py b/app/util/data_preparation/bamboo_prepare_data.py index 73cfad2b3..d8075fc8f 100644 --- a/app/util/data_preparation/bamboo_prepare_data.py +++ b/app/util/data_preparation/bamboo_prepare_data.py @@ -75,6 +75,21 @@ def write_test_data_to_files(dataset): __write_to_file(BAMBOO_USERS, users) +def __check_number_of_custom_app(client): + try: + all_apps = client.get_installed_apps() + apps_with_vendor_defined = [app for app in all_apps if 'vendor' in app] + non_atlassian_apps = [app for app in apps_with_vendor_defined if 'Atlassian' not in + app['vendor']['name'] and app['userInstalled'] == True] + non_atlassian_apps_names = [app['name'] for app in non_atlassian_apps] + print(f"Custom application count: {len(non_atlassian_apps)}") + if non_atlassian_apps: + print(f'Custom app names:') + print(*non_atlassian_apps_names, sep='\n') + except Exception as e: + print(f'ERROR: Could not get the installed applications. Error: {e}') + + def main(): print("Started preparing data") verify_agents_plans_setup() @@ -84,7 +99,7 @@ def main(): client = BambooClient(url, BAMBOO_SETTINGS.admin_login, BAMBOO_SETTINGS.admin_password, verify=BAMBOO_SETTINGS.secure) - + __check_number_of_custom_app(client) dataset = __create_dataset(client) write_test_data_to_files(dataset) assert_number_of_agents(client) diff --git a/app/util/data_preparation/bitbucket_prepare_data.py b/app/util/data_preparation/bitbucket_prepare_data.py index fc4fdf21c..09ee1a550 100644 --- a/app/util/data_preparation/bitbucket_prepare_data.py +++ b/app/util/data_preparation/bitbucket_prepare_data.py @@ -134,6 +134,21 @@ def __check_for_admin_permissions(bitbucket_api): bitbucket_api.get_user_global_permissions() +def __check_number_of_custom_app(bitbucket_api): + try: + all_apps = bitbucket_api.get_installed_apps() + apps_with_vendor_defined = [app for app in all_apps if 'vendor' in app] + non_atlassian_apps = [app for app in apps_with_vendor_defined if 'Atlassian' not in + app['vendor']['name'] and app['userInstalled'] == True] + non_atlassian_apps_names = [app['name'] for app in non_atlassian_apps] + print(f"Custom application count: {len(non_atlassian_apps)}") + if non_atlassian_apps: + print(f'Custom app names:') + print(*non_atlassian_apps_names, sep='\n') + except Exception as e: + print(f'ERROR: Could not get the installed applications. Error: {e}') + + def main(): print("Started preparing data") @@ -142,7 +157,7 @@ def main(): client = BitbucketRestClient(url, BITBUCKET_SETTINGS.admin_login, BITBUCKET_SETTINGS.admin_password, verify=BITBUCKET_SETTINGS.secure) - + __check_number_of_custom_app(client) __check_current_language(client) __check_for_admin_permissions(client) diff --git a/app/util/data_preparation/confluence_prepare_data.py b/app/util/data_preparation/confluence_prepare_data.py index 0bec9c027..5ede2e234 100644 --- a/app/util/data_preparation/confluence_prepare_data.py +++ b/app/util/data_preparation/confluence_prepare_data.py @@ -8,7 +8,7 @@ from util.api.confluence_clients import ConfluenceRpcClient, ConfluenceRestClient from util.common_util import print_timing from util.conf import CONFLUENCE_SETTINGS -from util.project_paths import (CONFLUENCE_USERS, CONFLUENCE_PAGES, CONFLUENCE_BLOGS, CONFLUENCE_CQLS, +from util.project_paths import (CONFLUENCE_USERS, CONFLUENCE_PAGES, CONFLUENCE_BLOGS, CONFLUENCE_CUSTOM_PAGES, CONFLUENCE_WORDS) __warnings_filter() @@ -76,8 +76,6 @@ def __create_data_set(rest_client, rpc_client): dataset[PAGES] = async_pages.get() dataset[BLOGS] = async_blogs.get() - dataset[CQLS] = __generate_cqls(words_count=CQL_WORDS_COUNT) - dataset[CUSTOM_PAGES] = __get_custom_pages(perf_user_api, 5000, CONFLUENCE_SETTINGS.custom_dataset_query) print(f'Users count: {len(dataset[USERS])}') print(f'Pages count: {len(dataset[PAGES])}') @@ -211,8 +209,6 @@ def write_test_data_to_files(dataset): users = [f"{user['user']['username']},{DEFAULT_USER_PASSWORD}" for user in dataset[USERS]] __write_to_file(CONFLUENCE_USERS, users) - __write_to_file(CONFLUENCE_CQLS, dataset[CQLS]) - custom_pages = [f"{page['id']},{page['space']['key']}" for page in dataset[CUSTOM_PAGES]] __write_to_file(CONFLUENCE_CUSTOM_PAGES, custom_pages) @@ -251,6 +247,20 @@ def __check_license(rest_client): print(f"The license expiry date: {expiry_date_human}.\n" f"License available seats: {license_remaining_seats['count']}") +def __check_number_of_custom_app(rest_client): + try: + all_apps = rest_client.get_installed_apps() + apps_with_vendor_defined = [app for app in all_apps if 'vendor' in app] + non_atlassian_apps = [app for app in apps_with_vendor_defined if 'Atlassian' not in + app['vendor']['name'] and app['userInstalled'] == True] + non_atlassian_apps_names = [app['name'] for app in non_atlassian_apps] + print(f"Custom application count: {len(non_atlassian_apps)}") + if non_atlassian_apps: + print(f'Custom app names:') + print(*non_atlassian_apps_names, sep='\n') + except Exception as e: + print(f'ERROR: Could not get the installed applications. Error: {e}') + @print_timing('Confluence data preparation') def main(): @@ -262,6 +272,7 @@ def main(): rest_client = ConfluenceRestClient(url, CONFLUENCE_SETTINGS.admin_login, CONFLUENCE_SETTINGS.admin_password, verify=CONFLUENCE_SETTINGS.secure) rpc_client = ConfluenceRpcClient(url, CONFLUENCE_SETTINGS.admin_login, CONFLUENCE_SETTINGS.admin_password) + __check_number_of_custom_app(rest_client) __is_remote_api_enabled(rest_client) __check_license(rest_client) __check_for_admin_permissions(rest_client) diff --git a/app/util/data_preparation/crowd_prepare_data.py b/app/util/data_preparation/crowd_prepare_data.py index 458e50ddd..177261b52 100644 --- a/app/util/data_preparation/crowd_prepare_data.py +++ b/app/util/data_preparation/crowd_prepare_data.py @@ -40,6 +40,21 @@ def write_test_data_to_files(dataset): __write_to_file(CROWD_USERS, users) +def __check_number_of_custom_app(client): + try: + all_apps = client.get_installed_apps() + apps_with_vendor_defined = [app for app in all_apps if 'vendor' in app] + non_atlassian_apps = [app for app in apps_with_vendor_defined if 'Atlassian' not in + app['vendor']['name'] and app['userInstalled'] == True] + non_atlassian_apps_names = [app['name'] for app in non_atlassian_apps] + print(f"Custom application count: {len(non_atlassian_apps)}") + if non_atlassian_apps: + print(f'Custom app names:') + print(*non_atlassian_apps_names, sep='\n') + except Exception as e: + print(f'ERROR: Could not get the installed applications. Error: {e}') + + def main(): print("Started preparing data") @@ -48,9 +63,11 @@ def main(): client = CrowdRestClient(url, CROWD_SETTINGS.application_name, CROWD_SETTINGS.application_password, verify=CROWD_SETTINGS.secure) - + crowd_admin_client = CrowdRestClient(url, CROWD_SETTINGS.admin_login, + CROWD_SETTINGS.admin_password, verify=CROWD_SETTINGS.secure) dataset = __create_data_set(client) write_test_data_to_files(dataset) + __check_number_of_custom_app(crowd_admin_client) print("Finished preparing data") diff --git a/app/util/data_preparation/jira_prepare_data.py b/app/util/data_preparation/jira_prepare_data.py index 77867e7c8..d7b489769 100644 --- a/app/util/data_preparation/jira_prepare_data.py +++ b/app/util/data_preparation/jira_prepare_data.py @@ -177,6 +177,22 @@ def __check_license(client): raise SystemExit(f'ERROR: Jira license is valid: {license_valid}, license has expired: {license_expired}.') +def __check_number_of_custom_app(client): + try: + all_apps = client.get_installed_apps() + apps_with_vendor_defined = [app for app in all_apps if 'vendor' in app] + non_atlassian_apps = [app for app in apps_with_vendor_defined if 'Atlassian' not in + app['vendor']['name'] and app['userInstalled'] == True] + non_atlassian_apps_names = [app['name'] for app in non_atlassian_apps] + print(f"Custom application count: {len(non_atlassian_apps)}") + if non_atlassian_apps: + print(f'Custom app names:') + print(*non_atlassian_apps_names, sep='\n') + except Exception as e: + print(f'ERROR: Could not get the installed applications. Error: {e}') + + + def main(): print("Started preparing data") @@ -188,6 +204,7 @@ def main(): __check_for_admin_permissions(client) __check_current_language(client) __check_license(client) + __check_number_of_custom_app(client) dataset = __create_data_set(client) write_test_data_to_files(dataset) diff --git a/app/util/default_test_actions.json b/app/util/default_test_actions.json index 6321e0396..8c07a9eaa 100644 --- a/app/util/default_test_actions.json +++ b/app/util/default_test_actions.json @@ -87,7 +87,8 @@ "selenium_create_comment", "selenium_create_comment:write_comment", "selenium_create_comment:save_comment", - "selenium_cql_search", + "selenium_cql_search_two_words", + "selenium_cql_search_three_words", "selenium_log_out" ], "jmeter": [ @@ -95,7 +96,8 @@ "jmeter_view_page:open_page", "jmeter_view_blog", "jmeter_search_cql:recently_viewed", - "jmeter_search_cql:search_results", + "jmeter_search_cql:search_results_2_words", + "jmeter_search_cql:search_results_3_words", "jmeter_view_dashboard", "jmeter_create_and_edit_page:create_page_editor", "jmeter_create_and_edit_page:create_page", @@ -116,7 +118,8 @@ "locust_view_dashboard", "locust_view_blog", "locust_search_cql:recently_viewed", - "locust_search_cql:search_results", + "locust_search_cql:search_results_3_words", + "locust_search_cql:search_results_2_words", "locust_comment_page", "locust_create_and_edit_page:create_page_editor", "locust_create_and_edit_page:create_page", diff --git a/app/util/jmeter/start_jmeter_ui.py b/app/util/jmeter/start_jmeter_ui.py index b3c640657..8f45ef22d 100644 --- a/app/util/jmeter/start_jmeter_ui.py +++ b/app/util/jmeter/start_jmeter_ui.py @@ -1,4 +1,5 @@ import argparse +import re from pathlib import Path from platform import system from subprocess import run @@ -6,7 +7,7 @@ import yaml -SUPPORTED_PYTHON_VERSIONS = ["3.9", "3.10", "3.11", "3.12"] +SUPPORTED_PYTHON_VERSIONS = ["3.10", "3.11", "3.12", "3.13"] python_full_version = '.'.join(map(str, version_info[0:3])) python_short_version = '.'.join(map(str, version_info[0:2])) @@ -109,6 +110,10 @@ def trim_string(text): def get_settings(self): obj = self.read_yml_file(self.yml) self.env_settings = obj['settings']['env'] + jmeter_version = self.env_settings.get('JMETER_VERSION') + if jmeter_version and not re.match(r'^\d+(\.\d+)*$', str(jmeter_version)): + raise SystemExit( + f"ERROR: Invalid JMETER_VERSION format '{jmeter_version}'") hostname = self.env_settings['application_hostname'] if hostname in DEFAULT_HOSTNAMES and not self.args.skip_check: raise SystemExit(f"ERROR: Check 'application_hostname' correctness in " @@ -140,6 +145,7 @@ def print_settings(self, settings): def launch_jmeter_ui(self): jmeter_path = JMETER_HOME / str(self.env_settings['JMETER_VERSION']) / 'bin' / 'jmeter' + print(jmeter_path) command = [str(jmeter_path), "-p", str(PROPERTIES), "-t", str(self.jmx)] print(f"JMeter start command: {' '.join(command)}") print(f"Working dir: {APP_DIR}") diff --git a/app/util/k8s/README.MD b/app/util/k8s/README.MD index 48506c694..791d3fa1a 100644 --- a/app/util/k8s/README.MD +++ b/app/util/k8s/README.MD @@ -32,7 +32,7 @@ docker run --pull=always --env-file aws_envs \ -v "/$PWD/dcapt-small.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ --it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars +-it atlassianlabs/terraform:2.9.10 ./install.sh -c conf.tfvars ``` ### Terminate development environment Note: install and uninstall commands have to use the same `atlassianlabs/terraform:TAG` image tag. @@ -44,7 +44,7 @@ docker run --pull=always --env-file aws_envs \ -v "/$PWD/dcapt-small.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ --it atlassianlabs/terraform:2.9.3 ./uninstall.sh -c conf.tfvars +-it atlassianlabs/terraform:2.9.10 ./uninstall.sh -c conf.tfvars ``` # Enterprise-scale environment @@ -61,7 +61,7 @@ docker run --pull=always --env-file aws_envs \ -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ --it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars +-it atlassianlabs/terraform:2.9.10 ./install.sh -c conf.tfvars ``` ### Terminate enterprise-scale environment Note: install and uninstall commands have to use the same `atlassianlabs/terraform:TAG` image tag. @@ -73,7 +73,7 @@ docker run --pull=always --env-file aws_envs \ -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ --it atlassianlabs/terraform:2.9.3 ./uninstall.sh -c conf.tfvars +-it atlassianlabs/terraform:2.9.10 ./uninstall.sh -c conf.tfvars ``` # Collect detailed k8s logs @@ -93,7 +93,7 @@ export REGION=us-east-2 docker run --pull=always --env-file aws_envs \ -v "/$PWD/k8s_logs:/data-center-terraform/k8s_logs" \ -v "/$PWD/logs:/data-center-terraform/logs" \ --it atlassianlabs/terraform:2.9.3 ./scripts/collect_k8s_logs.sh atlas-$ENVIRONMENT_NAME-cluster $REGION k8s_logs +-it atlassianlabs/terraform:2.9.10 ./scripts/collect_k8s_logs.sh atlas-$ENVIRONMENT_NAME-cluster $REGION k8s_logs ``` # Force terminate cluster @@ -126,7 +126,7 @@ atlassian/dcapt terminate_cluster.py --cluster_name atlas-$ENVIRONMENT_NAME-clus docker run --pull=always --env-file aws_envs \ -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -e REGION=$REGION \ - -it atlassianlabs/terraform:2.9.3 bash + -it atlassianlabs/terraform:2.9.10 bash ``` 5. Connect to the product pod. Example below for jira pod with number 0. For other product or pod number change `PRODUCT_POD` accordingly. @@ -150,7 +150,7 @@ atlassian/dcapt terminate_cluster.py --cluster_name atlas-$ENVIRONMENT_NAME-clus -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -e REGION=$REGION \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ - -it atlassianlabs/terraform:2.9.3 bash + -it atlassianlabs/terraform:2.9.10 bash ``` 5. Copy code base and connect to the execution environment pod: ``` bash @@ -178,7 +178,7 @@ atlassian/dcapt terminate_cluster.py --cluster_name atlas-$ENVIRONMENT_NAME-clus -e REGION=$REGION \ -e PRODUCT=$PRODUCT \ -v "/$PWD/script-runner.yml:/data-center-terraform/script-runner.yml" \ - -it atlassianlabs/terraform:2.9.3 bash + -it atlassianlabs/terraform:2.9.10 bash ``` 5. Run following commands one by one inside docker container: ``` bash @@ -206,7 +206,7 @@ To enable detailed CPU/Memory monitoring and Grafana dashboards for visualisatio docker run --pull=always --env-file aws_envs \ -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -e REGION=$REGION \ - -it atlassianlabs/terraform:2.9.3 bash + -it atlassianlabs/terraform:2.9.10 bash ``` ``` bash aws eks update-kubeconfig --name atlas-$ENVIRONMENT_NAME-cluster --region $REGION @@ -242,7 +242,7 @@ Note: this option is **not** suitable for full-scale performance runs as local n -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh jira.yml + -it atlassianlabs/terraform:2.9.10 bash bzt_on_pod.sh jira.yml ``` # Retry to copy run results from the execution environment pod to local @@ -259,7 +259,7 @@ Note: this option is **not** suitable for full-scale performance runs as local n -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/copy_run_results.sh:/data-center-terraform/copy_run_results.sh" \ - -it atlassianlabs/terraform:2.9.3 bash copy_run_results.sh + -it atlassianlabs/terraform:2.9.10 bash copy_run_results.sh ``` # Debug AWS required policies @@ -268,7 +268,7 @@ Note: this option is **not** suitable for full-scale performance runs as local n 3. Start and ssh to `atlassianlabs/terraform` docker container: ``` bash docker run --pull=always --env-file aws_envs \ - -it atlassianlabs/terraform:2.9.3 bash + -it atlassianlabs/terraform:2.9.10 bash ``` 4. Make sure you have IAM policies with names `policy1`, `policy2`, created from [policy1.json](https://github.com/atlassian-labs/data-center-terraform/blob/main/permissions/policy1.json) and [policy2.json](https://github.com/atlassian-labs/data-center-terraform/blob/main/permissions/policy2.json). 5. Run following commands one by one inside docker container to get effective policies permissions: diff --git a/app/util/k8s/bzt_on_pod.sh b/app/util/k8s/bzt_on_pod.sh index 9c4019afc..cbd9416c0 100644 --- a/app/util/k8s/bzt_on_pod.sh +++ b/app/util/k8s/bzt_on_pod.sh @@ -28,6 +28,22 @@ else exit 1 fi +# Parse arguments for --ci and --no-tmux flags (starting from 2nd argument) +ci=false +no_tmux=false +for ((i=2; i<=$#; i++)); do + if [[ "${!i}" == "--ci" ]]; then + ci=true + elif [[ "${!i}" == "--no-tmux" ]]; then + no_tmux=true + fi +done + +# Validate mutual exclusivity +if [[ "$ci" == "true" && "$no_tmux" == "true" ]]; then + echo "ERROR: --ci and --no-tmux flags are mutually exclusive. Please use only one." + exit 1 +fi echo "INFO: Update kubeconfig" aws eks update-kubeconfig --name atlas-"$ENVIRONMENT_NAME"-cluster --region "$REGION" @@ -43,32 +59,197 @@ fi echo "INFO: Execution environment pod name: $exec_pod_name" -echo "INFO: Cleanup dc-app-performance-toolkit folder on the exec env pod" -kubectl exec -it "$exec_pod_name" -n atlassian -- rm -rf /dc-app-performance-toolkit - -echo "INFO: Copy latest dc-app-performance-toolkit folder to the exec env pod" +# Prepare the dcapt archive locally first +echo "INFO: Preparing dc-app-performance-toolkit archive" start=$(date +%s) -# tar only app folder, exclude results and util/k8s folder tar -czf dcapt.tar.gz -C dc-app-performance-toolkit --exclude results --exclude util/k8s app Dockerfile requirements.txt -kubectl cp --retries 10 dcapt.tar.gz atlassian/"$exec_pod_name":/dcapt.tar.gz -kubectl exec -it "$exec_pod_name" -n atlassian -- mkdir /dc-app-performance-toolkit -kubectl exec -it "$exec_pod_name" -n atlassian -- tar -xf /dcapt.tar.gz -C /dc-app-performance-toolkit + +# Copy the archive to the pod +echo "INFO: Copying archive to pod" +kubectl cp dcapt.tar.gz atlassian/"$exec_pod_name":/tmp/dcapt.tar.gz rm -rf dcapt.tar.gz end=$(date +%s) runtime=$((end-start)) -echo "INFO: Copy finished in $runtime seconds" +echo "INFO: Archive preparation and copy finished in $runtime seconds" + +if [[ "$no_tmux" == "true" ]]; then + echo "INFO: Running bzt execution directly without tmux" + + # Run setup and bzt directly in the pod + kubectl exec -i "$exec_pod_name" -n atlassian -- sh -c " + echo 'INFO: Starting setup' + + # Cleanup and recreate directory + echo 'INFO: Cleanup dc-app-performance-toolkit folder' + rm -rf /dc-app-performance-toolkit + mkdir -p /dc-app-performance-toolkit + + # Extract the archive + echo 'INFO: Extracting dc-app-performance-toolkit' + tar xzf /tmp/dcapt.tar.gz -C /dc-app-performance-toolkit + rm -f /tmp/dcapt.tar.gz + + # Docker image rebuild if requested + if [ '$2' = '--docker_image_rebuild' ]; then + echo 'INFO: Rebuilding docker image' + docker build -t $DCAPT_DOCKER_IMAGE dc-app-performance-toolkit + fi + + echo 'INFO: Setup complete, starting bzt execution' + # Run bzt + docker run --shm-size=4g -v /dc-app-performance-toolkit:/dc-app-performance-toolkit $DCAPT_DOCKER_IMAGE '$1' + " + exit_code=$? + + if [[ $exit_code -eq 0 ]]; then + echo "INFO: bzt execution completed successfully." + else + echo "ERROR: bzt execution failed with exit code $exit_code." + exit $exit_code + fi +else + # Ensure tmux is installed in the pod (using apk for Alpine) + echo "INFO: Ensuring tmux is available in the pod" + kubectl exec -i "$exec_pod_name" -n atlassian -- sh -c "command -v tmux || apk add --no-cache tmux" -if [[ $2 == "--docker_image_rebuild" ]]; then - echo "INFO: Rebuild docker image" - kubectl exec -it "$exec_pod_name" -n atlassian -- docker build -t $DCAPT_DOCKER_IMAGE dc-app-performance-toolkit + # Check if tmux session already exists and contains our setup + session_exists=$(kubectl exec "$exec_pod_name" -n atlassian -- sh -c "tmux has-session -t bzt_session 2>/dev/null; echo \$?") + + if [[ "$session_exists" == "0" ]]; then + echo "INFO: Found existing tmux session 'bzt_session', attaching to it..." + else + echo "INFO: Creating new tmux session and running setup inside it" + + # Create the setup script that will run inside tmux + setup_script=" + echo 'INFO: Starting setup inside tmux session' + + # Cleanup and recreate directory + echo 'INFO: Cleanup dc-app-performance-toolkit folder' + rm -rf /dc-app-performance-toolkit + mkdir -p /dc-app-performance-toolkit + + # Extract the archive + echo 'INFO: Extracting dc-app-performance-toolkit' + tar xzf /tmp/dcapt.tar.gz -C /dc-app-performance-toolkit + rm -f /tmp/dcapt.tar.gz + + # Docker image rebuild if requested + if [ '$2' = '--docker_image_rebuild' ]; then + echo 'INFO: Rebuilding docker image' + docker build -t $DCAPT_DOCKER_IMAGE dc-app-performance-toolkit + fi + + # Create a marker file to indicate setup is complete + touch /tmp/dcapt_setup_complete + + echo 'INFO: Setup complete, starting bzt execution' + # Run bzt + docker run --shm-size=4g -v /dc-app-performance-toolkit:/dc-app-performance-toolkit $DCAPT_DOCKER_IMAGE '$1' + " + + # Start tmux session with the complete setup and execution + kubectl exec -i "$exec_pod_name" -n atlassian -- sh -c " + rm -f /tmp/bzt_session.log + tmux new-session -d -s bzt_session \"$setup_script\" + tmux pipe-pane -t bzt_session \"cat > /tmp/bzt_session.log\" + " + + echo "INFO: Tmux session 'bzt_session' created with setup and execution" + fi + + attempt=1 + max_attempts=5 + echo "INFO: Attaching to tmux session 'bzt_session'..." + + while [ $attempt -le $max_attempts ]; do + echo "INFO: Attempt $attempt to attach to tmux session 'bzt_session'..." + + # Check if session still exists before attempting to attach + session_check=$(kubectl exec "$exec_pod_name" -n atlassian -- sh -c "tmux has-session -t bzt_session 2>/dev/null; echo \$?" 2>/dev/null) + + if [[ "$session_check" != "0" ]]; then + echo "INFO: bzt session has finished or does not exist." + break + fi + + # Different behavior based on ci flag + if [[ "$ci" == "true" ]]; then + # CI mode: stream logs from /tmp/bzt_session.log (non-interactive) + kubectl exec "$exec_pod_name" -n atlassian -- sh -c " + tail -f /tmp/bzt_session.log & + tail_pid=\$! + + while true; do + sleep 5 + if ! tmux has-session -t bzt_session 2>/dev/null; then + break + fi + done + + kill \$tail_pid 2>/dev/null || true + " 2>/dev/null + exit_code=$? + else + # Interactive mode: attach to tmux session + kubectl exec -it "$exec_pod_name" -n atlassian -- tmux attach-session -t bzt_session 2>/dev/null + exit_code=$? + fi + + # Handle different exit scenarios + if [[ $exit_code -eq 0 ]]; then + echo "INFO: Successfully detached from tmux session." + # Double-check if session still exists after clean detachment + session_exists=$(kubectl exec "$exec_pod_name" -n atlassian -- sh -c "tmux has-session -t bzt_session 2>/dev/null; echo \$?" 2>/dev/null) + if [[ "$session_exists" != "0" ]]; then + echo "INFO: bzt session has completed." + break + fi + # Clean detachment but session still exists, continue monitoring + echo "INFO: Session still active, continuing to monitor..." + elif [[ $exit_code -ne 0 ]]; then + # Handle network errors or other failures + echo "WARNING: Connection lost (exit code: $exit_code)" + + # Verify session still exists before retrying + session_exists=$(kubectl exec "$exec_pod_name" -n atlassian -- sh -c "tmux has-session -t bzt_session 2>/dev/null; echo \$?" 2>/dev/null) + + if [[ "$session_exists" != "0" ]]; then + echo "INFO: bzt session has finished during disconnection." + break + fi + + if [ $attempt -eq $max_attempts ]; then + echo "ERROR: Reached maximum number of attempts ($max_attempts). Session may still be running." + echo "ERROR: You can manually reconnect using: kubectl exec -it $exec_pod_name -n atlassian -- tmux attach-session -t bzt_session" + break + fi + + # Exponential backoff for retries + sleep_time=$((2 + attempt)) + echo "INFO: Network error or disconnect detected, reconnecting to tmux session in $sleep_time seconds (attempt $((attempt+1)))..." + sleep $sleep_time + attempt=$((attempt+1)) + fi + done fi -echo "INFO: Run bzt on the exec env pod" -kubectl exec -it "$exec_pod_name" -n atlassian -- docker run --shm-size=4g -v "/dc-app-performance-toolkit:/dc-app-performance-toolkit" $DCAPT_DOCKER_IMAGE "$1" sleep 10 echo "INFO: Copy results folder from the exec env pod to local" -kubectl cp --retries 10 atlassian/"$exec_pod_name":dc-app-performance-toolkit/app/results dc-app-performance-toolkit/app/results +# Ensure the local results directory exists +local_results_dir="/data-center-terraform/dc-app-performance-toolkit/app/results" +mkdir -p "$local_results_dir" + +for _ in {1..3}; do + if kubectl exec -n atlassian "$exec_pod_name" --request-timeout=60s -- tar czf - -C /dc-app-performance-toolkit/app results | tar xzf - -C "$local_results_dir" --strip-components=1; then + break + else + echo "Copying failed, retrying..." + sleep 5 + fi +done + if [[ $? -ne 0 ]]; then echo "ERROR: Copy results folder failed" exit 1 diff --git a/app/util/k8s/copy_run_results.sh b/app/util/k8s/copy_run_results.sh index 367cce03e..ea19b6b36 100755 --- a/app/util/k8s/copy_run_results.sh +++ b/app/util/k8s/copy_run_results.sh @@ -30,7 +30,19 @@ fi echo "INFO: Execution environment pod name: $exec_pod_name" echo "INFO: Copy results folder from the exec env pod to local" -kubectl cp --retries 100 atlassian/"$exec_pod_name":dc-app-performance-toolkit/app/results dc-app-performance-toolkit/app/results +# Ensure the local results directory exists +local_results_dir="/data-center-terraform/dc-app-performance-toolkit/app/results" +mkdir -p "$local_results_dir" + +for _ in {1..3}; do + if kubectl exec -n atlassian "$exec_pod_name" --request-timeout=60s -- tar czf - -C /dc-app-performance-toolkit/app results | tar xzf - -C "$local_results_dir" --strip-components=1; then + break + else + echo "Copying failed, retrying..." + sleep 5 + fi +done + if [[ $? -ne 0 ]]; then echo "ERROR: Copy results folder failed" exit 1 diff --git a/app/util/k8s/dcapt-small.tfvars b/app/util/k8s/dcapt-small.tfvars index 1db52ed20..6353d67ad 100644 --- a/app/util/k8s/dcapt-small.tfvars +++ b/app/util/k8s/dcapt-small.tfvars @@ -89,10 +89,10 @@ jira_image_repository = "atlassian/jira-software" # Supported versions by DCAPT: https://github.com/atlassian/dc-app-performance-toolkit#supported-versions # Jira version. -jira_version_tag = "9.12.12" +jira_version_tag = "11.3.1" # JSM version # ! REQUIRED for JSM ! -# jira_version_tag = "5.12.12" +# jira_version_tag = "11.3.1" # Dataset size. Used only when snapshots_json_file_path is defined. Defaults to large jira_dataset_size = "small" @@ -161,7 +161,7 @@ jira_additional_jvm_args = ["-Dupm.plugin.upload.enabled=true"] ################################################################################ # Supported versions by DCAPT: https://github.com/atlassian/dc-app-performance-toolkit#supported-versions -confluence_version_tag = "8.5.14" +confluence_version_tag = "10.2.2" # Dataset size. Used only when snapshots_json_file_path is defined. Defaults to large confluence_dataset_size = "small" @@ -238,7 +238,7 @@ confluence_additional_jvm_args = ["-Dupm.plugin.upload.enabled=true"] ################################################################################ # Supported versions by DCAPT: https://github.com/atlassian/dc-app-performance-toolkit#supported-versions -bitbucket_version_tag = "8.9.18" +bitbucket_version_tag = "9.4.12" # Dataset size. Used only when snapshots_json_file_path is defined. Defaults to large bitbucket_dataset_size = "small" diff --git a/app/util/k8s/dcapt-snapshots.json b/app/util/k8s/dcapt-snapshots.json index f03970d60..dfc81978d 100644 --- a/app/util/k8s/dcapt-snapshots.json +++ b/app/util/k8s/dcapt-snapshots.json @@ -2,45 +2,15 @@ "jira": { "versions": [ { - "version": "9.12.12", + "version": "10.3.15", "data": [ - { - "type": "local-home", - "size": "large", - "snapshots": [ - { - "us-east-2": "snap-0edc32e9a8fb4f56d", - "us-east-1": "snap-0dc75041c98efa31e" - } - ] - }, - { - "type": "ebs", - "size": "large", - "snapshots": [ - { - "us-east-2": "snap-0fa88c439fd1e26bf", - "us-east-1": "snap-02b543a2f7c6cd857" - } - ] - }, - { - "type": "rds", - "size": "large", - "snapshots": [ - { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jira-9-12-12", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jira-9-12-12" - } - ] - }, { "type": "ebs", "size": "small", "snapshots": [ { - "us-east-2": "snap-03fa93593b6986515", - "us-east-1": "snap-022515b35736dc918" + "us-east-2": "snap-02eb1cd14e3469db7", + "us-east-1": "snap-0a9106a09c4ac7d7d" } ] }, @@ -49,23 +19,8 @@ "size": "small", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jira-small-9-12-12", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jira-small-9-12-12" - } - ] - } - ] - }, - { - "version": "9.4.25", - "data": [ - { - "type": "local-home", - "size": "large", - "snapshots": [ - { - "us-east-2": "snap-0e90b3edf64661cd9", - "us-east-1": "snap-0a6f40e62b2d2717b" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jira-small-10-3-15", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jira-small-10-3-15" } ] }, @@ -74,8 +29,8 @@ "size": "large", "snapshots": [ { - "us-east-2": "snap-0bdff246c26d37056", - "us-east-1": "snap-031107775912a1561" + "us-east-2": "snap-059045232a2854831", + "us-east-1": "snap-05cf4def8804c4da5" } ] }, @@ -84,73 +39,33 @@ "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jira-9-4-25", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jira-9-4-25" - } - ] - }, - { - "type": "ebs", - "size": "small", - "snapshots": [ - { - "us-east-2": "snap-08518a678828ac8bd", - "us-east-1": "snap-0eb15a0f1652f6daa" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jira-10-3-15", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jira-10-3-15" } ] }, { - "type": "rds", - "size": "small", + "type": "local-home", + "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jira-small-9-4-25", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jira-small-9-4-25" + "us-east-2": "snap-0ad935ea594e2f3ec", + "us-east-1": "snap-0dcaeea7150f12c80" } ] } ] }, { - "version": "10.0.1", + "version": "11.3.1", "data": [ - { - "type": "local-home", - "size": "large", - "snapshots": [ - { - "us-east-2": "snap-034eee5f34bf936b3", - "us-east-1": "snap-001d16b1ffa28ac4a" - } - ] - }, - { - "type": "ebs", - "size": "large", - "snapshots": [ - { - "us-east-2": "snap-09b742ca1bb9fff88", - "us-east-1": "snap-0506e2d0d2b39e92a" - } - ] - }, - { - "type": "rds", - "size": "large", - "snapshots": [ - { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jira-10-0-1", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jira-10-0-1" - } - ] - }, { "type": "ebs", "size": "small", "snapshots": [ { - "us-east-2": "snap-0ea31388ab389d097", - "us-east-1": "snap-09c71b1d40d21e408" + "us-east-2": "snap-0eced159eea56a6e3", + "us-east-1": "snap-07f9fdfaaf4107997" } ] }, @@ -159,27 +74,8 @@ "size": "small", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jira-small-10-0-1", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jira-small-10-0-1" - } - ] - } - ] - } - ] - }, - "jsm": { - "versions": [ - { - "version": "5.12.12", - "data": [ - { - "type": "local-home", - "size": "large", - "snapshots": [ - { - "us-east-2": "snap-08bca73a2953fcfa4", - "us-east-1": "snap-03f59681b58506b9b" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jira-small-11-3-1", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jira-small-11-3-1" } ] }, @@ -188,8 +84,8 @@ "size": "large", "snapshots": [ { - "us-east-2": "snap-0420b7230a8fc6427", - "us-east-1": "snap-05046d32748fffb03" + "us-east-2": "snap-05cb805552827adf7", + "us-east-1": "snap-0c936325b25fd3899" } ] }, @@ -198,73 +94,37 @@ "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jsm-5-12-12", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jsm-5-12-12" - } - ] - }, - { - "type": "ebs", - "size": "small", - "snapshots": [ - { - "us-east-2": "snap-0ede570b707cb70b4", - "us-east-1": "snap-0c862b084379be99e" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jira-11-3-1", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jira-11-3-1" } ] }, { - "type": "rds", - "size": "small", + "type": "local-home", + "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jsm-small-5-12-12", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jsm-small-5-12-12" + "us-east-2": "snap-0dc413e149a0e6664", + "us-east-1": "snap-0b141d82744a59622" } ] } ] - }, + } + ] + }, + "jsm": { + "versions": [ { - "version": "5.4.25", + "version": "10.3.15", "data": [ - { - "type": "local-home", - "size": "large", - "snapshots": [ - { - "us-east-2": "snap-047ef4054461dd0fa", - "us-east-1": "snap-0cd08ef369f1df34e" - } - ] - }, - { - "type": "ebs", - "size": "large", - "snapshots": [ - { - "us-east-2": "snap-0d0975b249e46ad98", - "us-east-1": "snap-088b77461fd57c9a2" - } - ] - }, - { - "type": "rds", - "size": "large", - "snapshots": [ - { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jsm-5-4-25", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jsm-5-4-25" - } - ] - }, { "type": "ebs", "size": "small", "snapshots": [ { - "us-east-2": "snap-02402e9518294e486", - "us-east-1": "snap-048268c30f4165bae" + "us-east-2": "snap-0a2905cc27a9416a3", + "us-east-1": "snap-07425ee15645e6c03" } ] }, @@ -273,23 +133,8 @@ "size": "small", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jsm-small-5-4-25", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jsm-small-5-4-25" - } - ] - } - ] - }, - { - "version": "10.0.1", - "data": [ - { - "type": "local-home", - "size": "large", - "snapshots": [ - { - "us-east-2": "snap-019d79330995b9db7", - "us-east-1": "snap-08478133b48785725" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jsm-small-10-3-15", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jsm-small-10-3-15" } ] }, @@ -298,8 +143,8 @@ "size": "large", "snapshots": [ { - "us-east-2": "snap-0f67c466251f5ead9", - "us-east-1": "snap-0d8a6668edce4d004" + "us-east-2": "snap-0a7d11c1dab549c1a", + "us-east-1": "snap-0b857f1dbf2e86a1f" } ] }, @@ -308,47 +153,33 @@ "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jsm-10-0-1", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jsm-10-0-1" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jsm-10-3-15", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jsm-10-3-15" } ] }, { - "type": "ebs", - "size": "small", - "snapshots": [ - { - "us-east-2": "snap-0d582a31cfa7f908c", - "us-east-1": "snap-05b5d63ad1f2c0802" - } - ] - }, - { - "type": "rds", - "size": "small", + "type": "local-home", + "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jsm-small-10-0-1", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jsm-small-10-0-1" + "us-east-2": "snap-0a8c284701dd1663b", + "us-east-1": "snap-03c6f5c7e527df09a" } ] } ] - } - ] - }, - "confluence": { - "versions": [ + }, { - "version": "8.5.14", + "version": "11.3.1", "data": [ { "type": "ebs", "size": "small", "snapshots": [ { - "us-east-2": "snap-0ab8b4f943983c080", - "us-east-1": "snap-0b8a90c1e52f81419" + "us-east-2": "snap-065286c2c445a2775", + "us-east-1": "snap-0b5a3b6127ed8e148" } ] }, @@ -357,8 +188,8 @@ "size": "small", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-confluence-small-8-5-14", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-confluence-small-8-5-14" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jsm-small-11-3-1", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jsm-small-11-3-1" } ] }, @@ -367,8 +198,8 @@ "size": "large", "snapshots": [ { - "us-east-2": "snap-07d8d8583822fb512", - "us-east-1": "snap-0324e1b2130be6fef" + "us-east-2": "snap-0bdea6e84ec67ddfb", + "us-east-1": "snap-005c5b261056d32be" } ] }, @@ -377,8 +208,8 @@ "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-confluence-8-5-14", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-confluence-8-5-14" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-jsm-11-3-1", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-jsm-11-3-1" } ] }, @@ -387,24 +218,27 @@ "size": "large", "snapshots": [ { - "us-east-2": "snap-0f100550395f59e86", - "us-east-1": "snap-00624aeb7d3068a07" + "us-east-2": "snap-06d90281190cd0b01", + "us-east-1": "snap-0fc071a9262e8599b" } ] } - ], - "build_number": "9012" - }, + ] + } + ] + }, + "confluence": { + "versions": [ { - "version": "7.19.26", + "version": "9.2.13", "data": [ { "type": "ebs", "size": "small", "snapshots": [ { - "us-east-2": "snap-0110ef301c0517b63", - "us-east-1": "snap-02790fdfa10bbb675" + "us-east-2": "snap-066c381fdc33458bf", + "us-east-1": "snap-0e39690f01312f00e" } ] }, @@ -413,8 +247,8 @@ "size": "small", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-confluence-small-7-19-26", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-confluence-small-7-19-26" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-confluence-small-9-2-13", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-confluence-small-9-2-13" } ] }, @@ -423,8 +257,8 @@ "size": "large", "snapshots": [ { - "us-east-2": "snap-0b5b1710dba17a851", - "us-east-1": "snap-09c20285b19c42232" + "us-east-2": "snap-0c088f193f2d35664", + "us-east-1": "snap-01048de412bc5d8e8" } ] }, @@ -433,8 +267,8 @@ "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-confluence-7-19-26", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-confluence-7-19-26" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-confluence-9-2-13", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-confluence-9-2-13" } ] }, @@ -443,24 +277,24 @@ "size": "large", "snapshots": [ { - "us-east-2": "snap-054156e5c540b25fa", - "us-east-1": "snap-07b216c16361eca04" + "us-east-2": "snap-04fc92a8f62bf76b9", + "us-east-1": "snap-0b43639ae4bf65a88" } ] } ], - "build_number": "8804" + "build_number": "9111" }, { - "version": "9.0.2", + "version": "10.2.2", "data": [ { "type": "ebs", "size": "small", "snapshots": [ { - "us-east-2": "snap-0708fe4076049b8ef", - "us-east-1": "snap-01f51febcf56e2ae5" + "us-east-2": "snap-0a81429e6b4fd3099", + "us-east-1": "snap-06b8cc895b8a63d54" } ] }, @@ -469,8 +303,8 @@ "size": "small", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-confluence-small-9-0-2", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-confluence-small-9-0-2" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-confluence-small-10-2-2", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-confluence-small-10-2-2" } ] }, @@ -479,8 +313,8 @@ "size": "large", "snapshots": [ { - "us-east-2": "snap-082ccc7959ebdc851", - "us-east-1": "snap-0f2bf3233418c8a30" + "us-east-2": "snap-0aec260dbb558b9e0", + "us-east-1": "snap-090156ad8c8963599" } ] }, @@ -489,8 +323,8 @@ "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-confluence-9-0-2", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-confluence-9-0-2" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-confluence-10-2-2", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-confluence-10-2-2" } ] }, @@ -499,148 +333,148 @@ "size": "large", "snapshots": [ { - "us-east-2": "snap-06c5d1e6a8009fefb", - "us-east-1": "snap-063e03017a826e78e" + "us-east-2": "snap-02fac9e3f5c373412", + "us-east-1": "snap-056af91c80b436604" } ] } ], - "build_number": "9104" + "build_number": "9405" } ] }, "bitbucket": { "versions": [ { - "version": "8.9.18", + "version": "10.0.1", "data": [ { "type": "ebs", - "size": "large", + "size": "small", "snapshots": [ { - "us-east-2": "snap-09345723dd6284ba5", - "us-east-1": "snap-077120ab369e77e11" + "us-east-2": "snap-03d8b3154a0e9e556", + "us-east-1": "snap-004df1ae00ed2c18a" } ] }, { "type": "rds", - "size": "large", + "size": "small", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-8-9-18", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-8-9-18" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-small-10-0-1", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-small-10-0-1" } ] }, { "type": "ebs", - "size": "small", + "size": "large", "snapshots": [ { - "us-east-2": "snap-0efc4e58392e3c97c", - "us-east-1": "snap-0621ce307cfc464a5" + "us-east-2": "snap-0760e512fac94a904", + "us-east-1": "snap-060ecf1efa68d59ea" } ] }, { "type": "rds", - "size": "small", + "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-small-8-9-18", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-small-8-9-18" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-10-0-1", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-10-0-1" } ] } ] }, { - "version": "8.19.7", + "version": "8.19.24", "data": [ { "type": "ebs", - "size": "large", + "size": "small", "snapshots": [ { - "us-east-2": "snap-0763969979fa4a043", - "us-east-1": "snap-0cae984360987eb88" + "us-east-2": "snap-0ce99f571499433ba", + "us-east-1": "snap-03da486f36a53f108" } ] }, { "type": "rds", - "size": "large", + "size": "small", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-8-19-7", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-8-19-7" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-small-8-19-24", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-small-8-19-24" } ] }, { "type": "ebs", - "size": "small", + "size": "large", "snapshots": [ { - "us-east-2": "snap-0a29829d9651d8f0f", - "us-east-1": "snap-0514287d8adcd80ea" + "us-east-2": "snap-09d75beb905d1da6f", + "us-east-1": "snap-02cd4b6b42ee5cd89" } ] }, { "type": "rds", - "size": "small", + "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-small-8-19-7", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-small-8-19-7" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-8-19-24", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-8-19-24" } ] } ] }, { - "version": "9.0.0", + "version": "9.4.12", "data": [ { "type": "ebs", - "size": "large", + "size": "small", "snapshots": [ { - "us-east-2": "snap-0ac664e91507f0e9d", - "us-east-1": "snap-0961da714ef84fa67" + "us-east-2": "snap-0dc86ba7060d61b7d", + "us-east-1": "snap-0abdf3cec6e151b12" } ] }, { "type": "rds", - "size": "large", + "size": "small", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-9-0-0", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-9-0-0" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-small-9-4-12", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-small-9-4-12" } ] }, { "type": "ebs", - "size": "small", + "size": "large", "snapshots": [ { - "us-east-2": "snap-04268cd0703e449ae", - "us-east-1": "snap-0e7c7b549b51011ed" + "us-east-2": "snap-033d7ed424cd6d6d5", + "us-east-1": "snap-005a067ddf7bebcc2" } ] }, { "type": "rds", - "size": "small", + "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-small-9-0-0", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-small-9-0-0" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-bitbucket-9-4-12", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-bitbucket-9-4-12" } ] } @@ -651,15 +485,15 @@ "crowd": { "versions": [ { - "version": "6.0.0", + "version": "7.1.3", "data": [ { "type": "ebs", "size": "large", "snapshots": [ { - "us-east-2": "snap-0447ca5809d646efe", - "us-east-1": "snap-0877485f60faa521c" + "us-east-2": "snap-0226bd02f53c8cade", + "us-east-1": "snap-0b9becd90f8c4227d" } ] }, @@ -668,13 +502,13 @@ "size": "large", "snapshots": [ { - "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-crowd-6-0-0", - "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-crowd-6-0-0" + "us-east-2": "arn:aws:rds:us-east-2:585036043680:snapshot:dcapt-crowd-7-1-3", + "us-east-1": "arn:aws:rds:us-east-1:585036043680:snapshot:dcapt-crowd-7-1-3" } ] } ], - "build_number": "2091" + "build_number": "2503" } ] } diff --git a/app/util/k8s/dcapt.tfvars b/app/util/k8s/dcapt.tfvars index 593501b53..066ca4605 100644 --- a/app/util/k8s/dcapt.tfvars +++ b/app/util/k8s/dcapt.tfvars @@ -87,10 +87,10 @@ max_cluster_capacity = 6 # Create a docker-in-docker privileged container as execution environment pod start_test_deployment = "true" -test_deployment_cpu_request = "3" -test_deployment_cpu_limit = "4" -test_deployment_mem_request = "8Gi" -test_deployment_mem_limit = "8Gi" +test_deployment_cpu_request = "7" +test_deployment_cpu_limit = "8" +test_deployment_mem_request = "15Gi" +test_deployment_mem_limit = "16Gi" ################################################################################ # Jira/JSM Settings @@ -110,11 +110,11 @@ jira_image_repository = "atlassian/jira-software" # Supported versions by DCAPT: https://github.com/atlassian/dc-app-performance-toolkit#supported-versions # Jira version -jira_version_tag = "9.12.12" +jira_version_tag = "11.3.1" # JSM version # ! REQUIRED for JSM ! -# jira_version_tag = "5.12.12" +# jira_version_tag = "11.3.1" # Dataset size. Used only when snapshots_json_file_path is defined. Defaults to large. jira_dataset_size = "large" @@ -172,7 +172,7 @@ jira_additional_jvm_args = ["-Dupm.plugin.upload.enabled=true"] ################################################################################ # Supported versions by DCAPT: https://github.com/atlassian/dc-app-performance-toolkit#supported-versions -confluence_version_tag = "8.5.14" +confluence_version_tag = "10.2.2" # Dataset size. Used only when snapshots_json_file_path is defined. Defaults to large confluence_dataset_size = "large" @@ -247,7 +247,7 @@ confluence_additional_jvm_args = ["-Dupm.plugin.upload.enabled=true"] ################################################################################ # Supported versions by DCAPT: https://github.com/atlassian/dc-app-performance-toolkit#supported-versions -bitbucket_version_tag = "8.9.18" +bitbucket_version_tag = "9.4.12" # Dataset size. Used only when snapshots_json_file_path is defined. Defaults to large bitbucket_dataset_size = "large" @@ -335,7 +335,7 @@ bitbucket_additional_jvm_args = ["-Dupm.plugin.upload.enabled=true"] ################################################################################ # Supported versions by DCAPT: https://github.com/atlassian/dc-app-performance-toolkit#supported-versions -crowd_version_tag = "6.0.0" +crowd_version_tag = "7.1.3" # Helm chart version of Crowd and Crowd agent instances. By default the latest version is installed. # crowd_helm_chart_version = "" @@ -346,7 +346,7 @@ crowd_version_tag = "6.0.0" crowd_installation_timeout = 20 # Crowd instance resource configuration -crowd_cpu = "2" +crowd_cpu = "4" crowd_mem = "8Gi" crowd_min_heap = "2048m" crowd_max_heap = "2048m" @@ -366,7 +366,7 @@ crowd_nfs_limits_memory = "2Gi" # Documentation can be found via: # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.DBInstanceClass.html # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html#USER_PIOPS -crowd_db_major_engine_version = "14" +crowd_db_major_engine_version = "17" crowd_db_instance_class = "db.m5.large" crowd_db_allocated_storage = 200 crowd_db_iops = 1000 @@ -390,7 +390,7 @@ crowd_db_master_password = "Password1!" # A list of JVM arguments to be passed to the server. Defaults to an empty list. # Example: ["-Dproperty=value", "-Dproperty1=value1"] -crowd_additional_jvm_args = ["-Dupm.plugin.upload.enabled=true"] +crowd_additional_jvm_args = ["-Dupm.plugin.upload.enabled=true", "-Datlassian.upm.config.loosecheck.allowed=true"] ################################################################################ # Bamboo Settings @@ -399,8 +399,8 @@ crowd_additional_jvm_args = ["-Dupm.plugin.upload.enabled=true"] # By default, latest supported by DCAPT version is set. # https://hub.docker.com/r/atlassian/bamboo/tags # https://hub.docker.com/r/atlassian/bamboo-agent-base/tags -bamboo_version_tag = "9.6.6" -bamboo_agent_version_tag = "9.6.6" +bamboo_version_tag = "10.2.14" +bamboo_agent_version_tag = "10.2.14" # Helm chart version of Bamboo and Bamboo agent instances # bamboo_helm_chart_version = "" @@ -461,7 +461,7 @@ bamboo_nfs_limits_memory = "2Gi" # Documentation can be found via: # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.DBInstanceClass.html # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html#USER_PIOPS -bamboo_db_major_engine_version = "14" +bamboo_db_major_engine_version = "16" bamboo_db_instance_class = "db.t3.medium" bamboo_db_allocated_storage = 100 bamboo_db_iops = 1000 diff --git a/app/util/k8s/terminate_cluster.py b/app/util/k8s/terminate_cluster.py index f5522ee18..52999cbc9 100644 --- a/app/util/k8s/terminate_cluster.py +++ b/app/util/k8s/terminate_cluster.py @@ -2,14 +2,13 @@ from argparse import ArgumentParser from datetime import datetime, timedelta from time import sleep, time +from functools import wraps import boto3 import botocore from boto3.exceptions import Boto3Error from botocore import exceptions -from retry import retry - DEFAULT_RETRY_COUNT = 3 DEFAULT_RETRY_DELAY = 10 @@ -18,6 +17,23 @@ REGIONS = [US_EAST_2, US_EAST_1] +def retry(exception_type=Exception, tries=DEFAULT_RETRY_COUNT, delay=DEFAULT_RETRY_DELAY): + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + for attempt in range(tries): + try: + return func(*args, **kwargs) + except exception_type as e: + logging.error(f"Attempt {attempt + 1} failed with error: {e}") + if attempt < tries - 1: + sleep(delay) + else: + raise + return wrapper + return decorator + + def is_float(element): try: float(element) @@ -268,7 +284,7 @@ def delete_hosted_zone_record_if_exists(aws_region, cluster_name): logging.error(f"Unexpected error occurs: {e}") -@retry(Exception, tries=DEFAULT_RETRY_COUNT, delay=DEFAULT_RETRY_DELAY) +@retry() def delete_lb(aws_region, vpc_id): elb_client = boto3.client('elb', region_name=aws_region) try: @@ -313,7 +329,7 @@ def wait_for_nat_gateway_delete(ec2, nat_gateway_id): logging.error(f"NAT gateway with id {nat_gateway_id} was not deleted in {timeout} seconds.") -@retry(Exception, tries=DEFAULT_RETRY_COUNT, delay=DEFAULT_RETRY_DELAY) +@retry() def delete_nat_gateway(aws_region, vpc_id): ec2_client = boto3.client('ec2', region_name=aws_region) filters = [{'Name': 'vpc-id', 'Values': [f'{vpc_id}', ]}, ] @@ -333,7 +349,7 @@ def delete_nat_gateway(aws_region, vpc_id): logging.error(f"Deleting NAT gateway with id {nat_gateway_id} failed with error: {e}") -@retry(Exception, tries=DEFAULT_RETRY_COUNT, delay=DEFAULT_RETRY_DELAY) +@retry() def delete_igw(ec2_resource, vpc_id): vpc_resource = ec2_resource.Vpc(vpc_id) igws = vpc_resource.internet_gateways.all() @@ -355,7 +371,7 @@ def delete_igw(ec2_resource, vpc_id): logging.error(f"Deleting igw failed with error: {e}") -@retry(Exception, tries=12, delay=DEFAULT_RETRY_DELAY) +@retry(tries=12) def delete_subnets(ec2_resource, vpc_id, aws_region): vpc_resource = ec2_resource.Vpc(vpc_id) subnets_all = vpc_resource.subnets.all() @@ -381,7 +397,7 @@ def delete_subnets(ec2_resource, vpc_id, aws_region): logging.error(f"Delete of subnet failed with error: {e}") -@retry(Exception, tries=DEFAULT_RETRY_COUNT, delay=DEFAULT_RETRY_DELAY) +@retry() def delete_route_tables(ec2_resource, vpc_id): vpc_resource = ec2_resource.Vpc(vpc_id) rtbs = vpc_resource.route_tables.all() @@ -398,7 +414,7 @@ def delete_route_tables(ec2_resource, vpc_id): logging.error(f"Delete of route table failed with error: {e}") -@retry(Exception, tries=DEFAULT_RETRY_COUNT, delay=DEFAULT_RETRY_DELAY) +@retry() def delete_security_groups(ec2_resource, vpc_id): vpc_resource = ec2_resource.Vpc(vpc_id) sgps = vpc_resource.security_groups.all() @@ -969,6 +985,70 @@ def delete_dynamo_bucket_tf_state(cluster_name, aws_region): logging.warning(f"Could not delete dynamo db '{table}': {e}") +def dynamodb_list_tables_all(dynamodb_client): + table_names = [] + paginator = dynamodb_client.get_paginator('list_tables') + for page in paginator.paginate(): + table_names.extend(page.get('TableNames', [])) + return table_names + + +def delete_expired_dynamodb_tables(): + for rgn in REGIONS: + logging.info(f"Region: {rgn}") + dynamodb_client = boto3.client('dynamodb', region_name=rgn) + try: + table_names = dynamodb_list_tables_all(dynamodb_client) + except exceptions.EndpointConnectionError as e: + logging.error(f"Could not connect to DynamoDB endpoint URL: {e}") + continue + + for table_name in table_names: + if not table_name.startswith('atl_dc_'): + continue + try: + table_desc = dynamodb_client.describe_table(TableName=table_name).get('Table', {}) + except botocore.exceptions.ClientError as e: + logging.warning(f"Could not describe DynamoDB table {table_name}: {e}") + continue + + table_arn = table_desc.get('TableArn') + if not table_arn: + continue + + try: + response = dynamodb_client.list_tags_of_resource(ResourceArn=table_arn) + tags = response.get('Tags', []) + except botocore.exceptions.ClientError as e: + raise RuntimeError(f"Could not list tags for {table_arn}: {e}") + persist_days = next((tag.get('Value') for tag in tags if tag.get('Key') == 'persist_days'), None) + if not persist_days: + logging.warning(f"DynamoDB table {table_name} does not have 'persist_days' tag; skipping") + continue + if not is_float(persist_days): + persist_days = 0 + + created_at = table_desc.get('CreationDateTime') + if not created_at: + logging.warning(f"DynamoDB table {table_name} has no CreationDateTime; skipping") + continue + + created_date_timestamp = created_at.timestamp() + persist_seconds = float(persist_days) * 24 * 60 * 60 + now = time() + if created_date_timestamp + persist_seconds > now: + logging.info(f"DynamoDB table {table_name} is not EOL yet; skipping") + continue + + try: + logging.info( + f"DynamoDB table {table_name} is EOL (created_at={created_at.isoformat()}, persist_days={persist_days}); deleting" + ) + dynamodb_client.delete_table(TableName=table_name) + except botocore.exceptions.ClientError as e: + logging.warning(f"Could not delete DynamoDB table {table_name}: {e}") + + def delete_expired_tf_state_s3_buckets(): for rgn in REGIONS: logging.info(f"Region: {rgn}") @@ -981,7 +1061,14 @@ def delete_expired_tf_state_s3_buckets(): for bucket in response["Buckets"]: if bucket_name_template in bucket["Name"]: created_date = bucket["CreationDate"] - tags = s3_client.get_bucket_tagging(Bucket=bucket["Name"])["TagSet"] + try: + tags = s3_client.get_bucket_tagging(Bucket=bucket["Name"])["TagSet"] + except botocore.exceptions.ClientError as e: + if e.response['Error']['Code'] == 'NoSuchTagSet': + raise RuntimeError(f"S3 bucket {bucket['Name']} does not have any tags.") + else: + logging.error(f"Unexpected error for bucket: {bucket['Name']}") + raise e persist_days = next((tag["Value"] for tag in tags if tag["Key"] == "persist_days"), None) if persist_days: if not is_float(persist_days): @@ -995,7 +1082,7 @@ def delete_expired_tf_state_s3_buckets(): logging.info(f"S3 bucket {bucket['Name']} is EOL and should be deleted.") delete_bucket_by_name(s3_client, bucket['Name']) else: - logging.warning(f"S3 bucket {bucket['Name']} does not have tags.") + logging.warning(f"S3 bucket {bucket['Name']} is missing persis_days tag.") def is_policy_attached(policy_arn): @@ -1123,6 +1210,8 @@ def main(): delete_unused_volumes() logging.info("Search for abandoned S3 buckets") delete_expired_tf_state_s3_buckets() + logging.info("Search for abandoned DynamoDB tables") + delete_expired_dynamodb_tables() if __name__ == '__main__': diff --git a/app/util/pre_run/environment_checker.py b/app/util/pre_run/environment_checker.py index 70d5a49da..0113f2cee 100644 --- a/app/util/pre_run/environment_checker.py +++ b/app/util/pre_run/environment_checker.py @@ -1,6 +1,6 @@ from sys import version_info -SUPPORTED_PYTHON_VERSIONS = ["3.9", "3.10", "3.11", "3.12"] +SUPPORTED_PYTHON_VERSIONS = ["3.10", "3.11", "3.12", "3.13"] python_full_version = '.'.join(map(str, version_info[0:3])) python_short_version = '.'.join(map(str, version_info[0:2])) diff --git a/app/util/pre_run/environment_compliance_check.py b/app/util/pre_run/environment_compliance_check.py index 5b5504a62..5138c6559 100644 --- a/app/util/pre_run/environment_compliance_check.py +++ b/app/util/pre_run/environment_compliance_check.py @@ -1,5 +1,6 @@ import subprocess import sys +import os from packaging import version from selenium import webdriver @@ -80,6 +81,11 @@ def validate_chromedriver_version(app_name, app_settings): options = webdriver.ChromeOptions() options.add_argument("--headless") options.add_argument("--no-sandbox") + if app_settings.local_chrome_binary_path is not None: + print("INFO: Setting up local chrome binary path") + if not os.path.exists(app_settings.local_chrome_binary_path): + raise FileNotFoundError(f"ERROR: Chrome binary not found at {app_settings.local_chrome_binary_path}") + options.binary_location = app_settings.local_chrome_binary_path driver = webdriver.Chrome(options=options) current_chrome_version = version.parse(driver.capabilities['browserVersion']) if app_settings.chromedriver_version: @@ -162,6 +168,7 @@ def analyze_application_configuration(app_name, app_settings): if app_name.upper() == "CROWD": print("INFO: Skipping Chromedriver check for Crowd.") else: + print(f"INFO: Local chrome binary path: {app_settings.local_chrome_binary_path}") validate_chromedriver_version(app_name, app_settings) validate_java_version() diff --git a/app/util/project_paths.py b/app/util/project_paths.py index a439fab3b..f4ba35504 100644 --- a/app/util/project_paths.py +++ b/app/util/project_paths.py @@ -121,7 +121,6 @@ def __get_default_test_actions(): CONFLUENCE_USERS = __get_confluence_dataset('users.csv') CONFLUENCE_PAGES = __get_confluence_dataset('pages.csv') CONFLUENCE_BLOGS = __get_confluence_dataset('blogs.csv') -CONFLUENCE_CQLS = __get_confluence_dataset('cqls.csv') CONFLUENCE_STATIC_CONTENT = __get_confluence_dataset('static-content/files_upload.csv') CONFLUENCE_CUSTOM_PAGES = __get_confluence_dataset('custom_pages.csv') CONFLUENCE_WORDS = __get_confluence_dataset('static-content/words.csv') diff --git a/docs/dc-apps-performance-toolkit-user-guide-bamboo.md b/docs/dc-apps-performance-toolkit-user-guide-bamboo.md index 68f51df1b..aadae8408 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-bamboo.md +++ b/docs/dc-apps-performance-toolkit-user-guide-bamboo.md @@ -4,7 +4,7 @@ platform: platform product: marketplace category: devguide subcategory: build -date: "2024-10-22" +date: "2026-04-21" --- # Data Center App Performance Toolkit User Guide For Bamboo @@ -91,8 +91,7 @@ specifically for performance testing during the DC app review process. - `region` - **Do not change default region (`us-east-2`). If specific region is required, contact support.** {{% note %}} - New trial license could be generated on [my atlassian](https://my.atlassian.com/license/evaluation). - Use `BX02-9YO1-IN86-LO5G` Server ID for generation. + If you don't have a license, you can request a trial one for DCAPT performance testing purposes via your open DC Annual Review ECOHELP ticket. {{% /note %}} 6. From local terminal (Git Bash for Windows users) start the installation: @@ -102,7 +101,7 @@ specifically for performance testing during the DC app review process. -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 7. Copy product URL from the console output. Product url should look like `http://a1234-54321.us-east-2.elb.amazonaws.com/bamboo`. 8. Wait for all remote agents to be started and connected. It can take up to 10 minutes. Agents can be checked in `Settings` > `Agents`. @@ -203,7 +202,7 @@ So, our test has to open plan summary page and measure time to load of this new 1. Set desired execution percentage for `standalone_extension`. Default value is `0`, which means that `standalone_extension` action will not be executed. For example, for app-specific action development you could set percentage of `standalone_extension` to 100 and for all other actions to 0 - this way only `login_and_view_all_builds` and `standalone_extension` actions would be executed. -1. Navigate to `dc-app-performance-toolkit/app` folder and run from virtualenv(as described in `dc-app-performance-toolkit/README.md`): +1.Navigate to `dc-app-performance-toolkit/app` folder and follow [start JMeter UI README](https://github.com/atlassian/dc-app-performance-toolkit/tree/master/app/util/jmeter#start-jmeter-ui): ```python util/jmeter/start_jmeter_ui.py --app bamboo``` @@ -303,7 +302,7 @@ To receive performance baseline results **without** an app installed and **witho -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh bamboo.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh bamboo.yml ``` 1. View the following main results of the run in the `dc-app-performance-toolkit/app/results/bamboo/YY-MM-DD-hh-mm-ss` folder: - `results_summary.log`: detailed run summary @@ -334,7 +333,7 @@ To receive performance results with an app installed (still use master branch): -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh bamboo.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh bamboo.yml ``` {{% note %}} @@ -368,7 +367,7 @@ To receive results for Bamboo DC **with app** and **with app-specific actions**: -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh bamboo.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh bamboo.yml ``` {{% note %}} @@ -414,8 +413,8 @@ Do not forget to attach performance testing results to your ECOHELP ticket. ## Support -If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. For instructions on how to collect detailed logs, see [Collect detailed k8s logs](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#collect-detailed-k8s-logs). For failed cluster uninstall use [Force terminate command](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#force-terminate-cluster). -In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. diff --git a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md index 666fe4a66..d99710e22 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md +++ b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md @@ -4,7 +4,7 @@ platform: platform product: marketplace category: devguide subcategory: build -date: "2024-10-22" +date: "2026-04-21" --- # Data Center App Performance Toolkit User Guide For Bitbucket @@ -103,8 +103,7 @@ Below process describes how to install low-tier Bitbucket DC with "small" datase - `region` - AWS region for deployment. **Do not change default region (`us-east-2`). If specific region is required, contact support.** {{% note %}} - New trial license could be generated on [my atlassian](https://my.atlassian.com/license/evaluation). - Use `BX02-9YO1-IN86-LO5G` Server ID for generation. + If you don't have a license, you can request a trial one for DCAPT performance testing purposes via your open DC Annual Review ECOHELP ticket. {{% /note %}} 6. Optional variables to override: @@ -115,7 +114,7 @@ Below process describes how to install low-tier Bitbucket DC with "small" datase -v "/$PWD/dcapt-small.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 8. Copy product URL from the console output. Product url should look like `http://a1234-54321.us-east-2.elb.amazonaws.com/bitbucket`. @@ -307,8 +306,7 @@ Below process describes how to install enterprise-scale Bitbucket DC with "large - `region` - AWS region for deployment. **Do not change default region (`us-east-2`). If specific region is required, contact support.** {{% note %}} - New trial license could be generated on [my atlassian](https://my.atlassian.com/license/evaluation). - Use this server id for generation `BX02-9YO1-IN86-LO5G`. + If you don't have a license, you can request a trial one for DCAPT performance testing purposes via your open DC Annual Review ECOHELP ticket. {{% /note %}} 6. Optional variables to override: @@ -319,7 +317,7 @@ Below process describes how to install enterprise-scale Bitbucket DC with "large -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 8. Copy product URL from the console output. Product url should look like `http://a1234-54321.us-east-2.elb.amazonaws.com/bitbucket`. @@ -394,7 +392,7 @@ To receive performance baseline results **without** an app installed: -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh bitbucket.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh bitbucket.yml ``` 1. View the following main results of the run in the `dc-app-performance-toolkit/app/results/bitbucket/YY-MM-DD-hh-mm-ss` folder: @@ -425,7 +423,7 @@ To receive performance results with an app installed (still use master branch): -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh bitbucket.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh bitbucket.yml ``` {{% note %}} @@ -475,7 +473,7 @@ To receive scalability benchmark results for one-node Bitbucket DC **with** app- -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh bitbucket.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh bitbucket.yml ``` {{% note %}} @@ -500,7 +498,7 @@ To receive scalability benchmark results for two-node Bitbucket DC **with** app- -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 1. Navigate to `dc-app-performance-toolkit` folder and start tests execution: ``` bash @@ -513,7 +511,7 @@ To receive scalability benchmark results for two-node Bitbucket DC **with** app- -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh bitbucket.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh bitbucket.yml ``` {{% note %}} @@ -542,7 +540,7 @@ To receive scalability benchmark results for four-node Bitbucket DC with app-spe -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh bitbucket.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh bitbucket.yml ``` {{% note %}} @@ -587,8 +585,8 @@ Do not forget to attach performance testing results to your ECOHELP ticket. 2. Attach two reports folders to your ECOHELP ticket. ## Support -If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. For instructions on how to collect detailed logs, see [Collect detailed k8s logs](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#collect-detailed-k8s-logs). For failed cluster uninstall use [Force terminate command](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#force-terminate-cluster). -In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. diff --git a/docs/dc-apps-performance-toolkit-user-guide-confluence.md b/docs/dc-apps-performance-toolkit-user-guide-confluence.md index 4fc520dd3..8107712fb 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-confluence.md +++ b/docs/dc-apps-performance-toolkit-user-guide-confluence.md @@ -4,7 +4,7 @@ platform: platform product: marketplace category: devguide subcategory: build -date: "2024-10-22" +date: "2026-04-21" --- # Data Center App Performance Toolkit User Guide For Confluence @@ -102,8 +102,7 @@ Below process describes how to install low-tier Confluence DC with "small" datas - `region` - AWS region for deployment. **Do not change default region (`us-east-2`). If specific region is required, contact support.** {{% note %}} - New trial license could be generated on [my atlassian](https://my.atlassian.com/license/evaluation). - Use `BX02-9YO1-IN86-LO5G` Server ID for generation. + If you don't have a license, you can request a trial one for DCAPT performance testing purposes via your open DC Annual Review ECOHELP ticket. {{% /note %}} 6. Optional variables to override: @@ -114,7 +113,7 @@ Below process describes how to install low-tier Confluence DC with "small" datas -v "/$PWD/dcapt-small.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 8. Copy product URL from the console output. Product url should look like `http://a1234-54321.us-east-2.elb.amazonaws.com/confluence`. @@ -248,7 +247,7 @@ Set `standalone_extension` weight in accordance with the expected frequency of y 1. Check that `confluence.yml` file has correct settings of `application_hostname`, `application_protocol`, `application_port`, `application_postfix`, etc. 1. Set desired execution percentage for `standalone_extension`. Default value is `0`, which means that `standalone_extension` action will not be executed. For example, for app-specific action development you could set percentage of `standalone_extension` to 100 and for all other actions to 0 - this way only `login_and_view_dashboard` and `standalone_extension` actions would be executed. -1. Navigate to `dc-app-performance-toolkit/app` folder and run from virtualenv(as described in `dc-app-performance-toolkit/README.md`): +1. Navigate to `dc-app-performance-toolkit/app` folder and follow [start JMeter UI README](https://github.com/atlassian/dc-app-performance-toolkit/tree/master/app/util/jmeter#start-jmeter-ui): ```python util/jmeter/start_jmeter_ui.py --app confluence``` @@ -387,8 +386,7 @@ Below process describes how to install enterprise-scale Confluence DC with "larg - `region` - AWS region for deployment. **Do not change default region (`us-east-2`). If specific region is required, contact support.** {{% note %}} - New trial license could be generated on [my atlassian](https://my.atlassian.com/license/evaluation). - Use this server id for generation `BX02-9YO1-IN86-LO5G`. + If you don't have a license, you can request a trial one for DCAPT performance testing purposes via your open DC Annual Review ECOHELP ticket. {{% /note %}} 6. Optional variables to override: @@ -399,7 +397,7 @@ Below process describes how to install enterprise-scale Confluence DC with "larg -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 8. Copy product URL from the console output. Product url should look like `http://a1234-54321.us-east-2.elb.amazonaws.com/confluence`. @@ -474,7 +472,7 @@ To receive performance baseline results **without** an app installed: -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh confluence.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh confluence.yml ``` 1. View the following main results of the run in the `dc-app-performance-toolkit/app/results/confluence/YY-MM-DD-hh-mm-ss` folder: - `results_summary.log`: detailed run summary @@ -504,7 +502,7 @@ To receive performance results with an app installed (still use master branch): -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh confluence.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh confluence.yml ``` {{% note %}} @@ -565,7 +563,7 @@ To receive scalability benchmark results for one-node Confluence DC **with** app -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh confluence.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh confluence.yml ``` {{% note %}} @@ -590,7 +588,7 @@ To receive scalability benchmark results for two-node Confluence DC **with** app -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 1. Navigate to `dc-app-performance-toolkit` folder and start tests execution: ``` bash @@ -603,7 +601,7 @@ To receive scalability benchmark results for two-node Confluence DC **with** app -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh confluence.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh confluence.yml ``` {{% note %}} @@ -632,7 +630,7 @@ To receive scalability benchmark results for four-node Confluence DC with app-sp -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh confluence.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh confluence.yml ``` {{% note %}} @@ -677,8 +675,8 @@ Do not forget to attach performance testing results to your ECOHELP ticket. 2. Attach two reports folders to your ECOHELP ticket. ## Support -If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. For instructions on how to collect detailed logs, see [Collect detailed k8s logs](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#collect-detailed-k8s-logs). For failed cluster uninstall use [Force terminate command](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#force-terminate-cluster). -In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. diff --git a/docs/dc-apps-performance-toolkit-user-guide-crowd.md b/docs/dc-apps-performance-toolkit-user-guide-crowd.md index eb7bac975..5069a9161 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-crowd.md +++ b/docs/dc-apps-performance-toolkit-user-guide-crowd.md @@ -4,7 +4,7 @@ platform: platform product: marketplace category: devguide subcategory: build -date: "2024-10-22" +date: "2026-04-21" --- # Data Center App Performance Toolkit User Guide For Crowd @@ -87,8 +87,7 @@ specifically for performance testing during the DC app review process. - `region` - **Do not change default region (`us-east-2`). If specific region is required, contact support.** {{% note %}} - New trial license could be generated on [my atlassian](https://my.atlassian.com/license/evaluation). - Use `BX02-9YO1-IN86-LO5G` Server ID for generation. + If you don't have a license, you can request a trial one for DCAPT performance testing purposes via your open DC Annual Review ECOHELP ticket. {{% /note %}} 6. From local terminal (Git Bash for Windows users) start the installation (~40min): @@ -97,7 +96,7 @@ specifically for performance testing during the DC app review process. -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 7. Copy product URL from the console output. Product url should look like `http://a1234-54321.us-east-2.elb.amazonaws.com/crowd`. @@ -134,7 +133,7 @@ Data Center App Performance Toolkit has its own set of default [JMeter](https:// 1. Set up local environment for toolkit using the [README](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/README.md). 1. Check that `crowd.yml` file has correct settings of `application_hostname`, `application_protocol`, `application_port`, `application_postfix`, etc. -1. Navigate to `dc-app-performance-toolkit/app` folder and run from virtualenv(as described in `dc-app-performance-toolkit/README.md`): +1. Navigate to `dc-app-performance-toolkit/app` folder and follow [start JMeter UI README](https://github.com/atlassian/dc-app-performance-toolkit/tree/master/app/util/jmeter#start-jmeter-ui): ```python util/jmeter/start_jmeter_ui.py --app crowd``` 1. Open `Crowd` thread group and add new transaction controller. @@ -208,7 +207,7 @@ To receive performance baseline results **without** an app installed and **witho -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh crowd.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh crowd.yml ``` 1. View the following main results of the run in the `dc-app-performance-toolkit/app/results/crowd/YY-MM-DD-hh-mm-ss` folder: - `results_summary.log`: detailed run summary @@ -237,7 +236,7 @@ To receive performance results with an app installed (still use master branch): -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh crowd.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh crowd.yml ``` {{% note %}} @@ -296,7 +295,7 @@ To receive scalability benchmark results for one-node Crowd DC **with** app-spec -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh crowd.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh crowd.yml ``` {{% note %}} @@ -320,7 +319,7 @@ To receive scalability benchmark results for two-node Crowd DC **with** app-spec -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 1. Edit **run parameters** for 2 nodes run. To do it, left uncommented only 2 nodes scenario parameters in `crowd.yml` file. ``` @@ -347,7 +346,7 @@ To receive scalability benchmark results for two-node Crowd DC **with** app-spec -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh crowd.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh crowd.yml ``` {{% note %}} @@ -390,7 +389,7 @@ To receive scalability benchmark results for four-node Crowd DC with app-specifi -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh crowd.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh crowd.yml ``` {{% note %}} @@ -435,8 +434,8 @@ Do not forget to attach performance testing results to your ECOHELP ticket. 2. Attach two reports folders to your ECOHELP ticket. ## Support -If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. For instructions on how to collect detailed logs, see [Collect detailed k8s logs](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#collect-detailed-k8s-logs). For failed cluster uninstall use [Force terminate command](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#force-terminate-cluster). -In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. diff --git a/docs/dc-apps-performance-toolkit-user-guide-jira.md b/docs/dc-apps-performance-toolkit-user-guide-jira.md index 88386d8c5..0c280195b 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-jira.md +++ b/docs/dc-apps-performance-toolkit-user-guide-jira.md @@ -4,7 +4,7 @@ platform: platform product: marketplace category: devguide subcategory: build -date: "2024-10-22" +date: "2026-04-21" --- # Data Center App Performance Toolkit User Guide For Jira @@ -110,8 +110,7 @@ Below process describes how to install low-tier Jira DC with "small" dataset inc - `region` - AWS region for deployment. **Do not change default region (`us-east-2`). If specific region is required, contact support.** {{% note %}} - New trial license could be generated on [my atlassian](https://my.atlassian.com/license/evaluation). - Use `BX02-9YO1-IN86-LO5G` Server ID for generation. + If you don't have a license, you can request a trial one for DCAPT performance testing purposes via your open DC Annual Review ECOHELP ticket. {{% /note %}} 6. Optional variables to override: @@ -122,7 +121,7 @@ Below process describes how to install low-tier Jira DC with "small" dataset inc -v "/$PWD/dcapt-small.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 8. Copy product URL from the console output. Product url should look like `http://a1234-54321.us-east-2.elb.amazonaws.com/jira`. @@ -255,7 +254,7 @@ You develop an app that introduces new GET and POST endpoints in Jira Data Cente 1. Check that `jira.yml` file has correct settings of `application_hostname`, `application_protocol`, `application_port`, `application_postfix`, etc. 1. Set desired execution percentage for `standalone_extension`. Default value is `0`, which means that `standalone_extension` action will not be executed. For example, for app-specific action development you could set percentage of `standalone_extension` to 100 and for all other actions to 0 - this way only `login_and_view_dashboard` and `standalone_extension` actions would be executed. -1. Navigate to `dc-app-performance-toolkit/app` folder and run from virtualenv(as described in `dc-app-performance-toolkit/README.md`): +1. Navigate to `dc-app-performance-toolkit/app` folder and follow [start JMeter UI README](https://github.com/atlassian/dc-app-performance-toolkit/tree/master/app/util/jmeter#start-jmeter-ui): ```python util/jmeter/start_jmeter_ui.py --app jira``` @@ -411,8 +410,7 @@ Below process describes how to install enterprise-scale Jira DC with "large" dat - `region` - AWS region for deployment. **Do not change default region (`us-east-2`). If specific region is required, contact support.** {{% note %}} - New trial license could be generated on [my atlassian](https://my.atlassian.com/license/evaluation). - Use `BX02-9YO1-IN86-LO5G` Server ID for generation. + If you don't have a license, you can request a trial one for DCAPT performance testing purposes via your open DC Annual Review ECOHELP ticket. {{% /note %}} 6. Optional variables to override: @@ -423,7 +421,7 @@ Below process describes how to install enterprise-scale Jira DC with "large" dat -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 8. Copy product URL from the console output. Product url should look like `http://a1234-54321.us-east-2.elb.amazonaws.com/jira`. @@ -498,7 +496,7 @@ To receive performance baseline results **without** an app installed: -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh jira.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh jira.yml ``` 1. View the results files of the run in the local `dc-app-performance-toolkit/app/results/jira/YY-MM-DD-hh-mm-ss` folder: - `results_summary.log`: detailed run summary @@ -550,7 +548,7 @@ The re-index time for Jira is about ~50-70 minutes. -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh jira.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh jira.yml ``` {{% note %}} @@ -611,7 +609,7 @@ To receive scalability benchmark results for one-node Jira DC **with** app-speci -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh jira.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh jira.yml ``` {{% note %}} @@ -636,7 +634,7 @@ To receive scalability benchmark results for two-node Jira DC **with** app-speci -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 1. Navigate to `dc-app-performance-toolkit` folder and start tests execution: ``` bash @@ -649,7 +647,7 @@ To receive scalability benchmark results for two-node Jira DC **with** app-speci -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh jira.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh jira.yml ``` {{% note %}} @@ -678,7 +676,7 @@ To receive scalability benchmark results for four-node Jira DC with app-specific -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh jira.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh jira.yml ``` {{% note %}} @@ -723,8 +721,8 @@ Do not forget to attach performance testing results to your ECOHELP ticket. 2. Attach two reports folders to your ECOHELP ticket. ## Support -If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. For instructions on how to collect detailed logs, see [Collect detailed k8s logs](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#collect-detailed-k8s-logs). For failed cluster uninstall use [Force terminate command](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#force-terminate-cluster). -In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. diff --git a/docs/dc-apps-performance-toolkit-user-guide-jsm.md b/docs/dc-apps-performance-toolkit-user-guide-jsm.md index 130d834f3..c35c6ea13 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-jsm.md +++ b/docs/dc-apps-performance-toolkit-user-guide-jsm.md @@ -4,7 +4,7 @@ platform: platform product: marketplace category: devguide subcategory: build -date: "2024-10-22" +date: "2026-04-21" --- # Data Center App Performance Toolkit User Guide For Jira Service Management @@ -112,8 +112,7 @@ Below process describes how to install low-tier Jira Service Management DC with - `region` - AWS region for deployment. **Do not change default region (`us-east-2`). If specific region is required, contact support.** {{% note %}} - New trial license could be generated on [my atlassian](https://my.atlassian.com/license/evaluation). - Use `BX02-9YO1-IN86-LO5G` Server ID for generation. + If you don't have a license, you can request a trial one for DCAPT performance testing purposes via your open DC Annual Review ECOHELP ticket. {{% /note %}} 6. Optional variables to override: @@ -124,7 +123,7 @@ Below process describes how to install low-tier Jira Service Management DC with -v "/$PWD/dcapt-small.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 8. Copy product URL from the console output. Product url should look like `http://a1234-54321.us-east-2.elb.amazonaws.com/jira`. @@ -272,7 +271,7 @@ Set `agent_standalone_extension`/`customer_standalone_extension` weight in accor 1. Check that `jsm.yml` file has correct settings of `application_hostname`, `application_protocol`, `application_port`, `application_postfix`, etc. 1. Set desired execution percentage for `agent_standalone_extension` and/or `customer_standalone_extension`. Default values are `0`, which means that `agent_standalone_extension` and `customer_standalone_extension` actions will not be executed. For example, for app-specific action development you could set percentage of `agent_standalone_extension` and/or `customer_standalone_extension` to 100 and for all other actions to 0 - this way only `jmeter_agent_login_and_view_dashboard` and `agent_standalone_extension` or `jmeter_customer_login_and_view_dashboard` and `customer_standalone_extension` actions would be executed. -1. Navigate to `dc-app-performance-toolkit/app` folder and run from virtualenv(as described in `dc-app-performance-toolkit/README.md`): +1. Navigate to `dc-app-performance-toolkit/app` folder and follow [start JMeter UI README](https://github.com/atlassian/dc-app-performance-toolkit/tree/master/app/util/jmeter#start-jmeter-ui): ``` bash python util/jmeter/start_jmeter_ui.py --app jsm --type agents @@ -447,8 +446,7 @@ Below process describes how to install enterprise-scale Jira Service Management - `region` - AWS region for deployment. **Do not change default region (`us-east-2`). If specific region is required, contact support.** {{% note %}} - New trial license could be generated on [my atlassian](https://my.atlassian.com/license/evaluation). - Use `BX02-9YO1-IN86-LO5G` Server ID for generation. + If you don't have a license, you can request a trial one for DCAPT performance testing purposes via your open DC Annual Review ECOHELP ticket. {{% /note %}} 6. Optional variables to override: @@ -459,7 +457,7 @@ Below process describes how to install enterprise-scale Jira Service Management -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 8. Copy product URL from the console output. Product url should look like `http://a1234-54321.us-east-2.elb.amazonaws.com/jira`. @@ -539,7 +537,7 @@ To receive performance baseline results **without** an app installed: -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh jsm.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh jsm.yml ``` 1. View the following main results of the run in the `dc-app-performance-toolkit/app/results/jsm/YY-MM-DD-hh-mm-ss` folder: @@ -594,7 +592,7 @@ Re-index information window is displayed on the **Indexing page**. If the window -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh jsm.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh jsm.yml ``` {{% note %}} @@ -654,7 +652,7 @@ To receive scalability benchmark results for one-node Jira Service Management DC -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh jsm.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh jsm.yml ``` {{% note %}} @@ -679,7 +677,7 @@ To receive scalability benchmark results for two-node Jira Service Management DC -v "/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars" \ -v "/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json" \ -v "/$PWD/logs:/data-center-terraform/logs" \ - -it atlassianlabs/terraform:2.9.3 ./install.sh -c conf.tfvars + -it atlassianlabs/terraform:2.9.15 ./install.sh -c conf.tfvars ``` 1. Navigate to `dc-app-performance-toolkit` folder and start tests execution: ``` bash @@ -692,7 +690,7 @@ To receive scalability benchmark results for two-node Jira Service Management DC -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh jsm.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh jsm.yml ``` {{% note %}} @@ -721,7 +719,7 @@ To receive scalability benchmark results for four-node Jira Service Management D -e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \ -v "/$PWD:/data-center-terraform/dc-app-performance-toolkit" \ -v "/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh" \ - -it atlassianlabs/terraform:2.9.3 bash bzt_on_pod.sh jsm.yml + -it atlassianlabs/terraform:2.9.15 bash bzt_on_pod.sh jsm.yml ``` {{% note %}} @@ -766,8 +764,8 @@ Do not forget to attach performance testing results to your ECOHELP ticket. 2. Attach two reports folders to your ECOHELP ticket. ## Support -If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +If the installation script fails on installing Helm release or any other reason, collect the logs, zip and share to [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. For instructions on how to collect detailed logs, see [Collect detailed k8s logs](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#collect-detailed-k8s-logs). For failed cluster uninstall use [Force terminate command](https://github.com/atlassian/dc-app-performance-toolkit/blob/master/app/util/k8s/README.MD#force-terminate-cluster). -In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. +In case of any technical questions or issues with DC Apps Performance Toolkit, contact us for support in the [community Slack](https://go.atlassian.com/dcapt-community-slack) **#data-center-app-performance-toolkit** channel. diff --git a/requirements.txt b/requirements.txt index 6b91e32c0..58cf2b243 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,16 @@ -matplotlib==3.9.2 -pandas==2.2.3 -pytest==8.3.3 -locust==2.32.0 -selenium==4.25.0 -filelock==3.16.1 -packaging==24.1 -prettytable==3.11.0 -bzt==1.16.32 # bzt 1.16.34 has pinned setuptools==65.5.0, which does not have distutils -boto3==1.35.47 +matplotlib==3.10.8 +pandas==2.3.3 +pytest==9.0.3 +locust==2.43.1 +selenium==4.39.0 +filelock==3.20.3 +packaging==25.0 +prettytable==3.17.0 +bzt==1.16.49 +urwid==3.0.2 +boto3==1.42.33 retry==0.9.2 +apiritif==1.1.3 +pytest-xdist==3.8.0 +chardet>=5.0,<6.0 +urllib3==2.6.3 \ No newline at end of file