-
Notifications
You must be signed in to change notification settings - Fork 148
[test_operator] Add stackviz integration for Tempest test visualization #3642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| --- | ||
| # Main: Generate stackviz HTML reports from tempest subunit results | ||
| # This is the main orchestrator that coordinates stackviz report generation | ||
| # Included after log collection when tempest tests complete | ||
| # Handles multiple test stages by generating separate reports for each | ||
| # Calls generate-stackviz-worker.yml for each subunit file found | ||
|
|
||
| - name: Find all tempest subunit files (results and retries, compressed or uncompressed) | ||
| ansible.builtin.find: | ||
| paths: "{{ cifmw_test_operator_artifacts_basedir }}" | ||
| patterns: | ||
| - "tempest_results.subunit*" | ||
| - "tempest_retry.subunit*" | ||
| recurse: true | ||
| register: subunit_gz_files | ||
|
|
||
| - name: Display found subunit files | ||
| ansible.builtin.debug: | ||
| msg: "Found {{ subunit_gz_files.files | length }} subunit file(s) (results + retries)" | ||
|
|
||
| - name: Process stackviz reports | ||
| when: subunit_gz_files.files | length > 0 | ||
| block: | ||
| - name: Install required RPM packages for stackviz | ||
| ansible.builtin.package: | ||
| name: | ||
| - python3-subunit | ||
| - python3-testtools | ||
| state: present | ||
| become: true | ||
| when: cifmw_test_operator_stackviz_auto_install_deps | ||
|
|
||
| # Initialize list to track generated reports | ||
| - name: Initialize stackviz reports list | ||
| ansible.builtin.set_fact: | ||
| _stackviz_generated_reports: [] | ||
|
|
||
| # Process each subunit file | ||
| - name: Process each subunit file and generate individual reports | ||
| vars: | ||
| _current_subunit_source: "{{ subunit_file.path }}" | ||
| _current_dir: "{{ subunit_file.path | dirname }}" | ||
| _current_stage_name: "{{ subunit_file.path | dirname | basename }}" | ||
| _current_is_compressed: "{{ subunit_file.path.endswith('.gz') }}" | ||
| _is_retry_file: "{{ 'tempest_retry' in subunit_file.path }}" | ||
| _base_filename: "{{ 'tempest_retry' if 'tempest_retry' in subunit_file.path else 'tempest_results' }}" | ||
| _html_filename: "{{ 'tempest_retry_viz.html' if 'tempest_retry' in subunit_file.path else 'tempest-viz.html' }}" | ||
| ansible.builtin.include_tasks: generate-stackviz-worker.yml | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. now I see, that in then in as it was |
||
| loop: "{{ subunit_gz_files.files }}" | ||
| loop_control: | ||
| loop_var: subunit_file | ||
|
|
||
| # Create summary index page | ||
| - name: Create stackviz summary index | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why create index? |
||
| when: | ||
| - _stackviz_generated_reports | length > 0 | ||
| - cifmw_test_operator_stackviz_create_index | ||
| block: | ||
| - name: Create stackviz directory | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stackviz? |
||
| ansible.builtin.file: | ||
| path: "{{ cifmw_test_operator_artifacts_basedir }}/stackviz" | ||
| state: directory | ||
| mode: '0755' | ||
|
|
||
| - name: Generate summary index HTML | ||
| ansible.builtin.template: | ||
| src: stackviz-index.html.j2 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why? |
||
| dest: "{{ cifmw_test_operator_artifacts_basedir }}/stackviz/index.html" | ||
| mode: '0644' | ||
|
|
||
| - name: Display stackviz summary | ||
| ansible.builtin.debug: | ||
| msg: | | ||
| =================================================== | ||
| Stackviz Report Generation Complete! | ||
|
|
||
| Total Reports Generated: {{ _stackviz_generated_reports | length }} | ||
|
|
||
| {% for report in _stackviz_generated_reports %} | ||
| - {{ report.report_label }} | ||
| {{ report.html_path }} | ||
| {% endfor %} | ||
|
|
||
| {% if cifmw_test_operator_stackviz_create_index %} | ||
| Summary Index: {{ cifmw_test_operator_artifacts_basedir }}/stackviz/index.html | ||
|
|
||
| To view all reports: | ||
| {% if ansible_os_family == 'Darwin' %} | ||
| open {{ cifmw_test_operator_artifacts_basedir }}/stackviz/index.html | ||
| {% else %} | ||
| xdg-open {{ cifmw_test_operator_artifacts_basedir }}/stackviz/index.html | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agree, why? |
||
| {% endif %} | ||
| {% endif %} | ||
| =================================================== | ||
| when: _stackviz_generated_reports | length > 0 | ||
|
|
||
| - name: Display warning when no subunit files found | ||
| ansible.builtin.debug: | ||
| msg: > | ||
| WARNING: No tempest subunit files (tempest_results.subunit* or tempest_retry.subunit*) found in {{ cifmw_test_operator_artifacts_basedir }}. | ||
| Stackviz report generation skipped. | ||
| when: subunit_gz_files.files | length == 0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| --- | ||
| # Worker: Process a single subunit.gz file and generate HTML report | ||
| # This file is included in a loop from generate-stackviz-main.yml | ||
| # Variables available: | ||
| # - subunit_file: the file object from find results | ||
| # - All path and naming variables are passed as vars from the main file | ||
|
|
||
| - name: Set output paths for decompressed file and HTML report | ||
| ansible.builtin.set_fact: | ||
| _current_subunit: "{{ _current_dir }}/{{ _base_filename }}.subunit" | ||
| _current_html: "{{ _current_dir }}/{{ _html_filename }}" | ||
|
|
||
| # Tempest can produce compressed .subunit.gz files to save disk space, | ||
| # especially for large test runs. We decompress them for processing. | ||
| - name: Decompress subunit file if compressed | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why it can be compressed? I don't believe in that phase it is compressed, or? |
||
| ansible.builtin.shell: | | ||
| gunzip -c "{{ _current_subunit_source }}" > "{{ _current_subunit }}" | ||
| args: | ||
| creates: "{{ _current_subunit }}" | ||
| when: _current_is_compressed | bool | ||
|
|
||
| - name: Use uncompressed file directly if not compressed | ||
| ansible.builtin.set_fact: | ||
| _current_subunit: "{{ _current_subunit_source }}" | ||
| when: not (_current_is_compressed | bool) | ||
|
|
||
| - name: Verify subunit file exists | ||
| ansible.builtin.stat: | ||
| path: "{{ _current_subunit }}" | ||
| register: _current_subunit_stat | ||
|
|
||
| - name: Fail if subunit file is missing | ||
| ansible.builtin.fail: | ||
| msg: | | ||
| ERROR: Subunit file not found: {{ _current_subunit }} | ||
| Expected file after decompression or direct usage. | ||
| This may indicate an issue with test execution or file handling. | ||
| when: not _current_subunit_stat.stat.exists | ||
|
|
||
| - name: Generate stackviz HTML report for this stage | ||
| ansible.builtin.command: | ||
| cmd: > | ||
| python3 {{ cifmw_repo }}/scripts/generate-stackviz-report.py | ||
| {{ _current_subunit }} | ||
| {{ _current_html }} | ||
| register: _current_stackviz_generation | ||
|
|
||
| - name: Display generation output | ||
| ansible.builtin.debug: | ||
| var: _current_stackviz_generation.stdout_lines | ||
| when: | ||
| - _current_stackviz_generation is defined | ||
| - _current_stackviz_generation is not skipped | ||
| - cifmw_test_operator_stackviz_debug | default(false) | ||
|
|
||
| - name: Track generated report with type metadata | ||
| ansible.builtin.set_fact: | ||
| _stackviz_generated_reports: >- | ||
| {{ | ||
| _stackviz_generated_reports + [{ | ||
| 'stage_name': _current_stage_name, | ||
| 'html_path': _current_html, | ||
| 'subunit_path': _current_subunit, | ||
| 'directory': _current_dir, | ||
| 'is_retry': _is_retry_file | default(false), | ||
| 'report_label': ('Retry Results: ' if (_is_retry_file | default(false)) else 'Original Results: ') + _current_stage_name | ||
| }] | ||
| }} | ||
|
|
||
| - name: Display success for this report | ||
| ansible.builtin.debug: | ||
| msg: "✓ Generated report for {{ _current_stage_name }}: {{ _current_html }}" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <title>Tempest Test Reports - Summary</title> | ||
| <style> | ||
| body { | ||
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; | ||
| background-color: #0d1117; | ||
| color: #c9d1d9; | ||
| margin: 40px; | ||
| line-height: 1.6; | ||
| } | ||
| h1 { color: #58a6ff; margin-bottom: 30px; } | ||
| h2 { color: #8b949e; margin-top: 30px; } | ||
| .report-list { list-style: none; padding: 0; } | ||
| .report-item { | ||
| background-color: #161b22; | ||
| border: 1px solid #30363d; | ||
| border-radius: 6px; | ||
| padding: 20px; | ||
| margin-bottom: 15px; | ||
| transition: background-color 0.2s; | ||
| } | ||
| .report-item:hover { background-color: #1c2128; } | ||
| .report-link { | ||
| color: #58a6ff; | ||
| text-decoration: none; | ||
| font-size: 18px; | ||
| font-weight: bold; | ||
| } | ||
| .report-link:hover { text-decoration: underline; } | ||
| .report-path { | ||
| color: #8b949e; | ||
| font-size: 14px; | ||
| margin-top: 5px; | ||
| font-family: monospace; | ||
| } | ||
| .report-count { | ||
| color: #7ee787; | ||
| font-size: 24px; | ||
| font-weight: bold; | ||
| } | ||
| .timestamp { | ||
| color: #8b949e; | ||
| font-size: 12px; | ||
| margin-top: 20px; | ||
| } | ||
| .badge { | ||
| padding: 2px 6px; | ||
| border-radius: 3px; | ||
| font-size: 10px; | ||
| margin-right: 8px; | ||
| font-weight: bold; | ||
| } | ||
| .retry-badge { | ||
| background-color: #9e6a03; | ||
| color: white; | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <h1>Tempest Test Reports</h1> | ||
| <p>Total Reports Generated: <span class="report-count">{{ _stackviz_generated_reports | length }}</span></p> | ||
|
|
||
| <h2>Individual Test Stage Reports</h2> | ||
| <ul class="report-list"> | ||
| {% for report in _stackviz_generated_reports %} | ||
| <li class="report-item"> | ||
| <a href="{{ report.html_path | relpath(cifmw_test_operator_artifacts_basedir ~ '/stackviz') }}" class="report-link"> | ||
| {% if report.is_retry %} | ||
| <span class="badge retry-badge">RETRY</span> | ||
| {% endif %} | ||
| {{ report.stage_name }} | ||
| </a> | ||
| <div class="report-path">{{ report.html_path }}</div> | ||
| </li> | ||
| {% endfor %} | ||
| </ul> | ||
|
|
||
| <div class="timestamp"> | ||
| Generated: {{ ansible_date_time.iso8601 }} | ||
| </div> | ||
| </body> | ||
| </html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why this is needed (
/.) 🤔 The change above (/mnt/logs...) seems like a good change as the mountpath earlier also starts with backslash.