From d8e9dc1d20d851dcc712353106b482408f4685c3 Mon Sep 17 00:00:00 2001 From: Ishwar Kanse Date: Tue, 15 Jul 2025 20:59:26 +0530 Subject: [PATCH] Fix and improve Tracing UI plugin tests --- tests/PATTERNFLY_COMMANDS_EXAMPLES.md | 163 ++++++ tests/README.md | 30 +- tests/SELECTOR_BEST_PRACTICES.md | 286 ++++++++++ tests/cypress.config.ts | 3 +- tests/cypress/support/commands.ts | 282 +++++++--- tests/cypress/support/e2e.js | 15 + tests/e2e/dt-plugin-tests.cy.ts | 497 ++++++++++++++++++ .../01-assert.yaml | 94 ---- .../01-install-tempo.yaml | 83 --- .../02-install-otelcol.yaml | 50 -- .../03-assert.yaml | 13 - .../03-generate-traces.yaml | 36 -- .../04-assert.yaml | 27 - .../04-verify-traces.yaml | 107 ---- .../chainsaw-test.yaml | 43 -- .../01-assert.yaml | 44 ++ .../01-install-tempo.yaml | 52 ++ .../02-assert.yaml | 4 +- .../02-install-otelcol.yaml | 132 +++++ .../assert-create-sas.yaml | 42 ++ .../assert-kubeadmin-traces-verify.yaml | 15 + .../assert-tempo-rbac-sa-1-traces-gen.yaml | 15 + .../assert-tempo-rbac-sa-1-traces-verify.yaml | 15 + .../assert-tempo-rbac-sa-2-traces-gen.yaml | 15 + .../chainsaw-test.yaml | 49 ++ .../create-SAs-with-namespace-access.yaml | 91 ++++ .../kubeadmin-traces-verify.yaml | 201 +++++++ .../tempo-rbac-sa-1-traces-gen.yaml | 42 ++ .../tempo-rbac-sa-1-traces-verify.yaml | 207 ++++++++ .../tempo-rbac-sa-2-traces-gen.yaml | 42 ++ .../00-assert.yaml | 2 +- .../00-install-storage.yaml | 14 +- .../01-assert.yaml | 221 ++------ .../01-install-tempo.yaml | 35 +- .../02-assert.yaml | 2 +- .../multitenancy-rbac/02-install-otelcol.yaml | 139 +++++ .../multitenancy-rbac/assert-create-sas.yaml | 42 ++ .../assert-kubeadmin-traces-verify.yaml | 15 + .../assert-tempo-rbac-sa-1-traces-gen.yaml | 15 + .../assert-tempo-rbac-sa-1-traces-verify.yaml | 15 + .../assert-tempo-rbac-sa-2-traces-gen.yaml | 15 + .../multitenancy-rbac/chainsaw-test.yaml | 55 ++ .../create-SAs-with-namespace-access.yaml | 91 ++++ .../kubeadmin-traces-verify.yaml | 201 +++++++ .../tempo-rbac-sa-1-traces-gen.yaml | 42 ++ .../tempo-rbac-sa-1-traces-verify.yaml | 208 ++++++++ .../tempo-rbac-sa-2-traces-gen.yaml | 42 ++ .../multitenancy/02-install-otelcol.yaml | 86 --- .../multitenancy/03-assert.yaml | 15 - .../multitenancy/03-generate-traces.yaml | 38 -- .../multitenancy/04-assert.yaml | 31 -- .../multitenancy/04-verify-traces.yaml | 129 ----- .../multitenancy/chainsaw-test.yaml | 39 -- tests/package-lock.json | 42 +- tests/tests/dt-plugin-tests.cy.ts | 348 ------------ 55 files changed, 3119 insertions(+), 1458 deletions(-) create mode 100644 tests/PATTERNFLY_COMMANDS_EXAMPLES.md create mode 100644 tests/SELECTOR_BEST_PRACTICES.md create mode 100644 tests/e2e/dt-plugin-tests.cy.ts delete mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/01-assert.yaml delete mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/01-install-tempo.yaml delete mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/02-install-otelcol.yaml delete mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/03-assert.yaml delete mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/03-generate-traces.yaml delete mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/04-assert.yaml delete mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/04-verify-traces.yaml delete mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/chainsaw-test.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/01-assert.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/01-install-tempo.yaml rename tests/fixtures/chainsaw-tests/{monolithic-multitenancy-openshift => monolithic-multitenancy-rbac}/02-assert.yaml (82%) create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/02-install-otelcol.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-create-sas.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-kubeadmin-traces-verify.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-1-traces-gen.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-1-traces-verify.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-2-traces-gen.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/chainsaw-test.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/create-SAs-with-namespace-access.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/kubeadmin-traces-verify.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-1-traces-gen.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-1-traces-verify.yaml create mode 100644 tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-2-traces-gen.yaml rename tests/fixtures/chainsaw-tests/{multitenancy => multitenancy-rbac}/00-assert.yaml (71%) rename tests/fixtures/chainsaw-tests/{multitenancy => multitenancy-rbac}/00-install-storage.yaml (86%) rename tests/fixtures/chainsaw-tests/{multitenancy => multitenancy-rbac}/01-assert.yaml (54%) rename tests/fixtures/chainsaw-tests/{multitenancy => multitenancy-rbac}/01-install-tempo.yaml (61%) rename tests/fixtures/chainsaw-tests/{multitenancy => multitenancy-rbac}/02-assert.yaml (74%) create mode 100644 tests/fixtures/chainsaw-tests/multitenancy-rbac/02-install-otelcol.yaml create mode 100644 tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-create-sas.yaml create mode 100644 tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-kubeadmin-traces-verify.yaml create mode 100644 tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-1-traces-gen.yaml create mode 100644 tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-1-traces-verify.yaml create mode 100644 tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-2-traces-gen.yaml create mode 100755 tests/fixtures/chainsaw-tests/multitenancy-rbac/chainsaw-test.yaml create mode 100644 tests/fixtures/chainsaw-tests/multitenancy-rbac/create-SAs-with-namespace-access.yaml create mode 100644 tests/fixtures/chainsaw-tests/multitenancy-rbac/kubeadmin-traces-verify.yaml create mode 100644 tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-1-traces-gen.yaml create mode 100644 tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-1-traces-verify.yaml create mode 100644 tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-2-traces-gen.yaml delete mode 100644 tests/fixtures/chainsaw-tests/multitenancy/02-install-otelcol.yaml delete mode 100644 tests/fixtures/chainsaw-tests/multitenancy/03-assert.yaml delete mode 100644 tests/fixtures/chainsaw-tests/multitenancy/03-generate-traces.yaml delete mode 100644 tests/fixtures/chainsaw-tests/multitenancy/04-assert.yaml delete mode 100644 tests/fixtures/chainsaw-tests/multitenancy/04-verify-traces.yaml delete mode 100755 tests/fixtures/chainsaw-tests/multitenancy/chainsaw-test.yaml delete mode 100644 tests/tests/dt-plugin-tests.cy.ts diff --git a/tests/PATTERNFLY_COMMANDS_EXAMPLES.md b/tests/PATTERNFLY_COMMANDS_EXAMPLES.md new file mode 100644 index 0000000..4cc0cdd --- /dev/null +++ b/tests/PATTERNFLY_COMMANDS_EXAMPLES.md @@ -0,0 +1,163 @@ +# PatternFly Cypress Commands - Usage Examples + +This file demonstrates how to properly use the PatternFly-aware Cypress commands to avoid React state management issues. + +## ✅ Recommended Usage Patterns + +### 1. Simple Component Interactions + +```typescript +// Empty State - Clean and reliable +cy.pfEmptyState().within(() => { + cy.byRole('heading').should('contain', 'No items found'); +}); + +// Buttons - Direct and stable +cy.pfButton('Create Instance').click(); +cy.pfButton('Save Changes').should('be.disabled'); +cy.pfButton('Cancel').should('be.visible'); +``` + +### 2. Menu Navigation + +```typescript +// Basic menu toggle +cy.pfMenuToggle('Select Instance').click(); +cy.pfMenuItem('TempoStack').click(); + +// Or with error handling for dynamic content +cy.get('body').then(($body) => { + if ($body.find('.pf-v6-c-menu-toggle:contains("Create Instance")').length > 0) { + cy.pfMenuToggle('Create Instance').click(); + cy.pfMenuItem('TempoStack Instance').click(); + } +}); +``` + +### 3. Toolbar Interactions (Recommended Approach) + +```typescript +// Simple toolbar item selection +cy.pfToolbarItem(0).within(() => { + cy.get('.pf-v6-c-menu-toggle, .pf-v5-c-menu-toggle').first().click(); +}); +cy.get('.pf-v6-c-menu__item, .pf-v5-c-menu__item').contains('Option 1').click(); + +// Wait for stability between interactions +cy.wait(1000); +cy.pfToolbarItem(1).within(() => { + cy.get('.pf-v6-c-menu-toggle, .pf-v5-c-menu-toggle').first().click(); +}); +``` + +### 4. Form Interactions + +```typescript +// Using label-based selection +cy.byLabelText('Instance Name').type('my-instance'); +cy.byLabelText('Namespace').select('default'); + +// Form submission +cy.pfButton('Submit').click(); +``` + +## ⚠️ Patterns to Avoid + +### 1. Complex Chained Operations + +```typescript +// ❌ AVOID: Too many chained operations without waits +cy.pfToolbarItem(0).within(() => { + cy.pfMenuToggle().click(); +}).then(() => { + cy.pfMenuItem('Item').click(); +}).then(() => { + cy.pfToolbarItem(1).within(() => { + cy.pfMenuToggle().click(); + }); +}); + +// ✅ BETTER: Break into separate operations with waits +cy.pfToolbarItem(0).within(() => { + cy.get('.pf-v6-c-menu-toggle').first().click(); +}); +cy.get('.pf-v6-c-menu__item').contains('Item').click(); +cy.wait(1000); // Allow React state to stabilize +cy.pfToolbarItem(1).within(() => { + cy.get('.pf-v6-c-menu-toggle').first().click(); +}); +``` + +### 2. Rapid Sequential Clicks + +```typescript +// ❌ AVOID: Multiple rapid interactions +cy.pfMenuToggle().click(); +cy.pfMenuItem('Item1').click(); +cy.pfMenuToggle().click(); // Too fast, React state not updated +cy.pfMenuItem('Item2').click(); + +// ✅ BETTER: Add stabilization waits +cy.pfMenuToggle().click(); +cy.pfMenuItem('Item1').click(); +cy.wait(500); // Let React update +cy.pfMenuToggle().click(); +cy.pfMenuItem('Item2').click(); +``` + +## 🛡️ Defensive Programming Patterns + +### 1. Check Element Existence + +```typescript +// Safe menu interaction +cy.get('body').then(($body) => { + if ($body.find('.pf-v6-c-menu-toggle:contains("Actions")').length > 0) { + cy.pfMenuToggle('Actions').click(); + if ($body.find('.pf-v6-c-menu__item:contains("Delete")').length > 0) { + cy.pfMenuItem('Delete').click(); + } + } +}); +``` + +### 2. Wait for Stability + +```typescript +// Wait for page load and React hydration +cy.visit('/traces'); +cy.wait(2000); // Allow initial React state to settle + +// Perform interactions +cy.pfEmptyState().should('be.visible'); +cy.pfButton('Create Instance').click(); +``` + +### 3. Use Timeouts + +```typescript +// Custom timeouts for slow-loading components +cy.pfMenuToggle('Select Instance', { timeout: 15000 }).click(); +cy.pfMenuItem('TempoStack', { timeout: 10000 }).click(); +``` + +## 🎯 Best Practices Summary + +1. **Add waits between complex interactions** to let React state stabilize +2. **Use defensive checks** for dynamic content +3. **Prefer direct PatternFly commands** over complex CSS selectors +4. **Break complex operations** into smaller, testable steps +5. **Handle uncaught exceptions** gracefully in support files +6. **Use timeouts** for components that load asynchronously + +## 🔧 Troubleshooting + +If you encounter "e is not a function" errors: + +1. **Add `cy.wait(1000-2000)`** between interactions +2. **Check if elements exist** before interacting +3. **Use more specific selectors** instead of nth-child +4. **Verify React components have finished rendering** +5. **Review browser console** for additional React errors + +These patterns will make your tests more reliable and less prone to React state management issues. \ No newline at end of file diff --git a/tests/README.md b/tests/README.md index 71b59e1..ce5955f 100644 --- a/tests/README.md +++ b/tests/README.md @@ -17,17 +17,25 @@ node_modules/ -> dependencies will be installed at runtime here ## Directory structure After dependencies are installed successfully and before we run actual tests, please confirm if we have correct structure as below. ```bash -% ls -ltr tests -drwxr-xr-x views --rw-r--r-- reporter-config.json --rw-r--r-- package.json -drwxr-xr-x node_modules -drwxr-xr-x cypress --rw-r--r-- cypress.config.ts --rw-r--r-- README.md -drwxr-xr-x tests --rw-r--r-- tsconfig.json -drwxr-xr-x fixtures +tree -L 1 +. +├── cypress +├── cypress.config.ts +├── Dockerfile +├── Dockerfile-cypress +├── e2e +├── fixtures +├── node_modules +├── package-lock.json +├── package.json +├── PATTERNFLY_COMMANDS_EXAMPLES.md +├── README.md +├── reporter-config.json +├── SELECTOR_BEST_PRACTICES.md +├── tsconfig.json +└── views + +6 directories, 10 files ```` ### Export necessary variables diff --git a/tests/SELECTOR_BEST_PRACTICES.md b/tests/SELECTOR_BEST_PRACTICES.md new file mode 100644 index 0000000..80d3072 --- /dev/null +++ b/tests/SELECTOR_BEST_PRACTICES.md @@ -0,0 +1,286 @@ +# Cypress Selector Best Practices for PatternFly Applications + +This guide outlines the best practices for element selection in Cypress tests for PatternFly-based applications to ensure maintainable, reliable, and accessible test automation. + +## Selector Priority (Recommended Order) + +1. **`data-cy` attributes** - Custom test attributes (highest priority) +2. **`data-testid` attributes** - Common testing attributes +3. **`data-test` attributes** - Legacy test attributes +4. **PatternFly component attributes** - `data-ouia-component-*`, `data-pf-*` +5. **ARIA attributes** - Accessibility attributes (PatternFly components are ARIA-compliant) +6. **Semantic roles** - Role-based selection +7. **Text content** - Visible text +8. **PatternFly CSS classes** - Component-specific classes (better than generic CSS) +9. **Generic CSS classes/IDs** - Last resort (lowest priority) + +## PatternFly-Specific Considerations + +PatternFly components come with built-in accessibility features and consistent patterns: +- Most components have proper ARIA attributes +- Components follow consistent naming conventions +- Use PatternFly's data attributes when available +- Leverage component-specific selectors over generic CSS classes + +## Custom Commands Available + +### Test-Specific Selectors +```typescript +// Primary recommendation: data-cy attributes +cy.byCy('submit-button') // [data-cy="submit-button"] + +// Alternative test attributes +cy.byTestID('username-input') // [data-test="username-input"] +cy.findByTestId('password-field') // [data-testid="password-field"] +cy.byTestSelector('menu-dropdown') // [data-test-selector="menu-dropdown"] +``` + +### PatternFly Component Selectors +```typescript +// PatternFly-specific helpers +cy.pfMenuToggle('Create a Tempo instance') // Menu toggle by text +cy.pfMenuItem('TempoStack instance') // Menu item selection +cy.pfButton('View documentation') // PatternFly button by text +cy.pfEmptyState() // Empty state component +cy.pfToolbarItem(0) // First toolbar item +``` + +### Accessibility-Based Selectors +```typescript +// ARIA labels (PatternFly components have good ARIA support) +cy.byAriaLabel('Close dialog') // [aria-label="Close dialog"] +cy.byLabelText('Instance Type') // Form field by label + +// Role-based selection +cy.byRole('button') // [role="button"] +cy.byRole('button', 'Submit form') // [role="button"][aria-label="Submit form"] +``` + +### Content-Based Selectors +```typescript +// Text content (use sparingly, text can change) +cy.byText('View documentation') // contains('View documentation') +cy.byButtonText('Create instance') // button[type="button"] containing text +``` + +## Examples: Converting Brittle Selectors + +### ❌ Avoid: Complex CSS Selectors +```typescript +// BAD: Fragile, implementation-dependent +cy.get(':nth-child(1) > .pf-v6-c-toolbar__item > .pf-v6-c-form > .pf-v6-c-form__group') +cy.get('.pf-v6-c-empty-state__title-text') +cy.get('.MuiDataGrid-row--firstVisible > [data-field="name"] > .MuiBox-root') +cy.get('.pf-v6-c-menu-toggle__toggle-icon').click() +``` + +### ✅ Preferred: Stable, Semantic Selectors +```typescript +// GOOD: Stable and meaningful +cy.byCy('tempo-instance-dropdown') +cy.byAriaLabel('Select Tempo instance') // PatternFly components have ARIA labels +cy.byRole('button', 'Create a Tempo instance') // Use semantic roles +cy.byTestID('documentation-link') +cy.pfMenuToggle('Create a Tempo instance') // PatternFly-specific helper +``` + +## PatternFly Component Examples + +### Empty State +```typescript +// Current (brittle): +cy.get('.pf-v6-c-empty-state__title-text') + +// Improved: +cy.byCy('empty-state-title') +// OR leverage PatternFly's semantic structure: +cy.byRole('heading').contains('No Tempo instances yet') +``` + +### Toolbar & Dropdowns +```typescript +// Current (fragile): +cy.get(':nth-child(1) > .pf-v6-c-toolbar__item > .pf-v6-c-form') + +// Improved: +cy.byCy('tempo-instance-selector') +// OR use PatternFly menu patterns: +cy.pfMenuToggle().contains('Select instance') +cy.pfMenuItem('tempo-stack-instance') +``` + +### Buttons +```typescript +// Current: +cy.get('.pf-v6-c-button__text') + +// Improved: +cy.byCy('submit-button') +// OR use ARIA (PatternFly buttons have proper ARIA): +cy.byRole('button', 'Submit') +``` + +### Forms +```typescript +// Current: +cy.get('.pf-v6-c-form__group-control > .pf-v6-c-menu-toggle') + +// Improved: +cy.byCy('form-field-selector') +// OR use form labels (PatternFly forms are accessible): +cy.byLabelText('Instance Type') +``` + +## Implementation Strategy + +### For New Elements +1. **Add data-cy attributes** to UI components: + ```jsx + + + ``` + +2. **Use custom commands** in tests: + ```typescript + cy.byCy('create-tempo-instance').click(); + cy.byCy('trace-search-input').type('my-service'); + ``` + +### For Existing Tests +1. **Identify brittle selectors** (CSS classes, nth-child, complex paths) +2. **Add data-cy attributes** to corresponding UI components +3. **Replace selectors** with custom commands gradually + +## Specific Improvements for Current Tests + +Based on your existing test file, here are specific improvements: + +### Empty State Testing +```typescript +// Current (brittle): +cy.get('.pf-v6-c-empty-state__title-text') + .should('be.visible') + .and('have.text', 'No Tempo instances yet'); + +// Improved options: +// Option 1: Custom data attribute +cy.byCy('empty-state-title') + .should('have.text', 'No Tempo instances yet'); + +// Option 2: PatternFly helper + semantic role +cy.pfEmptyState().within(() => { + cy.byRole('heading').should('have.text', 'No Tempo instances yet'); +}); + +// Option 3: Direct ARIA/semantic approach +cy.byRole('heading', 'No Tempo instances yet').should('be.visible'); +``` + +### Menu Toggle & Dropdown Selection +```typescript +// Current (fragile): +cy.get(':nth-child(1) > .pf-v6-c-toolbar__item > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button > .pf-v6-c-menu-toggle__controls > .pf-v6-c-menu-toggle__toggle-icon').click(); + +// Improved options: +// Option 1: Custom data attribute (best) +cy.byCy('tempo-instance-selector').click(); + +// Option 2: PatternFly helper +cy.pfMenuToggle('Create a Tempo instance').click(); + +// Option 3: ARIA label +cy.byAriaLabel('Select Tempo instance').click(); +``` + +### Menu Item Selection +```typescript +// Current (nested and fragile): +cy.get(':nth-child(2) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); + +// Improved: +// Option 1: Custom data attributes +cy.byCy('tempo-stack-option').click(); + +// Option 2: PatternFly helper +cy.pfMenuItem('Create a TempoStack instance').click(); + +// Option 3: Text-based (least preferred, but better than CSS) +cy.byText('Create a TempoStack instance').click(); +``` + +### Button Interactions +```typescript +// Current: +cy.contains('.pf-v6-c-button', 'View documentation') + .should('be.visible') + .and('have.text', 'View documentation'); + +// Improved: +// Option 1: Custom data attribute +cy.byCy('documentation-button') + .should('have.text', 'View documentation'); + +// Option 2: PatternFly helper +cy.pfButton('View documentation').should('be.visible'); + +// Option 3: Semantic role +cy.byRole('button', 'View documentation').should('be.visible'); +``` + +### Complex Toolbar Navigation +```typescript +// Current (extremely fragile): +cy.get('.pf-m-toggle-group > .pf-v6-c-toolbar__group > :nth-child(2) > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button').click(); + +// Improved: +// Option 1: Custom data attributes (recommended) +cy.byCy('service-filter-dropdown').click(); + +// Option 2: Combine PatternFly helpers +cy.pfToolbarItem(1).within(() => { + cy.pfMenuToggle().click(); +}); + +// Option 3: Label-based for form fields +cy.byLabelText('Service Name').click(); +``` + +### Checkbox Selection in Menus +```typescript +// Current: +cy.contains('.pf-v6-c-menu__item-text', 'http') + .closest('.pf-v6-c-menu__item') + .find('input[type="checkbox"]') + .check(); + +// Improved: +// Option 1: Custom data attributes +cy.byCy('service-type-http').check(); + +// Option 2: Semantic approach +cy.byLabelText('http').check(); +cy.byRole('checkbox', 'http').check(); + +// Option 3: Enhanced PatternFly helper +cy.pfMenuItem('http').within(() => { + cy.get('input[type="checkbox"]').check(); +}); +``` + +## Benefits + +- **Stability**: Tests survive UI refactoring +- **Maintainability**: Clear intent and easy updates +- **Accessibility**: Encourages proper ARIA usage +- **Performance**: Faster element location +- **Readability**: Self-documenting test code + +## Migration Checklist + +- [ ] Audit existing selectors for brittleness +- [ ] Add data-cy attributes to critical UI elements +- [ ] Replace CSS selectors with custom commands +- [ ] Update test documentation +- [ ] Train team on new practices + +Remember: **The best selector is one that survives code changes and clearly expresses test intent.** \ No newline at end of file diff --git a/tests/cypress.config.ts b/tests/cypress.config.ts index c8c6c36..0301e5e 100644 --- a/tests/cypress.config.ts +++ b/tests/cypress.config.ts @@ -28,7 +28,6 @@ export default defineConfig({ openMode: 0, }, e2e: { - browser: "chrome", viewportWidth: 1920, viewportHeight: 1080, setupNodeEvents(on, config) { @@ -97,7 +96,7 @@ export default defineConfig({ return config; }, supportFile: './cypress/support/e2e.js', - specPattern: 'tests/**/*.cy.{js,jsx,ts,tsx}', + specPattern: 'e2e/**/*.cy.{js,jsx,ts,tsx}', numTestsKeptInMemory: 1, testIsolation: false, experimentalModifyObstructiveThirdPartyCode: true, diff --git a/tests/cypress/support/commands.ts b/tests/cypress/support/commands.ts index dbcd791..1949021 100644 --- a/tests/cypress/support/commands.ts +++ b/tests/cypress/support/commands.ts @@ -7,42 +7,59 @@ import { guidedTour } from '../../views/tour'; export {}; declare global { - interface Chainable { - byTestID( - selector: string, - options?: Partial, - ): Chainable; - byTestActionID(selector: string): Chainable>; - byLegacyTestID( - selector: string, - options?: Partial, - ): Chainable>; - byButtonText(selector: string): Chainable>; - byDataID(selector: string): Chainable>; - byTestSelector( - selector: string, - options?: Partial, - ): Chainable>; - byTestDropDownMenu(selector: string): Chainable>; - byTestOperatorRow( - selector: string, - options?: Partial, - ): Chainable>; - byTestSectionHeading(selector: string): Chainable>; - byTestOperandLink(selector: string): Chainable>; + namespace Cypress { + interface Chainable { + byTestID( + selector: string, + options?: Partial, + ): Chainable; + byTestActionID(selector: string): Chainable>; + byLegacyTestID( + selector: string, + options?: Partial, + ): Chainable>; + byButtonText(selector: string): Chainable>; + byDataID(selector: string): Chainable>; + byTestSelector( + selector: string, + options?: Partial, + ): Chainable>; + byTestDropDownMenu(selector: string): Chainable>; + byTestOperatorRow( + selector: string, + options?: Partial, + ): Chainable>; + byTestSectionHeading(selector: string): Chainable>; + byTestOperandLink(selector: string): Chainable>; + // Best practice selector commands + byCy(selector: string, options?: Partial): Chainable>; + byAriaLabel(selector: string, options?: Partial): Chainable>; + byRole(role: string, name?: string, options?: Partial): Chainable>; + byText(text: string, options?: Partial): Chainable>; + findByTestId(selector: string): Chainable>; + // PatternFly-specific commands + byLabelText(text: string, options?: Partial): Chainable>; + pfMenuToggle(text?: string, options?: Partial): Chainable>; + pfMenuItem(text: string, options?: Partial): Chainable>; + pfButton(text: string, options?: Partial): Chainable>; + pfEmptyState(options?: Partial): Chainable>; + pfToolbarItem(index?: number, options?: Partial): Chainable>; + } } } declare global { - interface Chainable { - switchPerspective(perspective: string); - uiLogin(provider: string, username: string, password: string); - uiLogout(); - cliLogin(username?, password?, hostapi?); - cliLogout(); - adminCLI(command: string, options?); - login(provider?: string, username?: string, password?: string): Chainable; - executeAndDelete(command: string); + namespace Cypress { + interface Chainable { + switchPerspective(perspective: string): Chainable; + uiLogin(provider: string, username: string, password: string): Chainable; + uiLogout(): Chainable; + cliLogin(username?: string, password?: string, hostapi?: string): Chainable; + cliLogout(): Chainable; + adminCLI(command: string, options?: any): Chainable; + login(provider?: string, username?: string, password?: string): Chainable; + executeAndDelete(command: string): Chainable; + } } } @@ -98,69 +115,59 @@ Cypress.Commands.add('byTestOperandLink', (selector: string) => { cy.get(`[data-test-operand-link="${selector}"]`); }); +// Simplified login command that handles OAuth redirect properly Cypress.Commands.add( 'login', ( - provider: string = Cypress.env('LOGIN_IDP'), - username: string = Cypress.env('LOGIN_USERNAME'), + provider: string = Cypress.env('LOGIN_IDP') || 'kube:admin', + username: string = Cypress.env('LOGIN_USERNAME') || 'kubeadmin', password: string = Cypress.env('LOGIN_PASSWORD'), - oauthurl: string, ) => { cy.session( [provider, username], () => { cy.visit(Cypress.config('baseUrl')); - cy.window().then( - ( - win: any, // eslint-disable-line @typescript-eslint/no-explicit-any - ) => { - // Check if auth is disabled (for a local development environment) - if (win.SERVER_FLAGS?.authDisabled) { - cy.task('log', ' skipping login, console is running with auth disabled'); - return; - } - cy.exec( - `oc get node --selector=hypershift.openshift.io/managed --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ).then((result) => { - cy.log(result.stdout); - cy.task('log', result.stdout); - if (result.stdout.includes('Ready')) { - cy.log(`Attempting login via cy.origin to: ${oauthurl}`); - cy.task('log', `Attempting login via cy.origin to: ${oauthurl}`); - cy.origin( - oauthurl, - { args: { username, password } }, - ({ username, password }) => { - cy.get('#inputUsername').type(username); - cy.get('#inputPassword').type(password); - cy.get('button[type=submit]').click(); - }, - ); - } else { - cy.task('log', ` Logging in as ${username} using fallback on ${oauthurl}`); - cy.origin( - oauthurl, - { args: { provider, username, password } }, - ({ provider, username, password }) => { - cy.get('[data-test-id="login"]').should('be.visible'); - cy.get('body').then(($body) => { - if ($body.text().includes(provider)) { - cy.contains(provider).should('be.visible').click(); - } - }); - cy.get('#inputUsername').type(username); - cy.get('#inputPassword').type(password); - cy.get('button[type=submit]').click(); + cy.window().then((win: any) => { + // Check if auth is disabled (for a local development environment) + if (win.SERVER_FLAGS?.authDisabled) { + cy.task('log', ' skipping login, console is running with auth disabled'); + return; + } + + cy.task('log', ` Logging in as ${username}`); + + // Get the current URL to determine if we've been redirected to OAuth + cy.url().then((currentUrl) => { + const url = new URL(currentUrl); + const oauthOrigin = `${url.protocol}//${url.hostname.replace('console-openshift-console', 'oauth-openshift')}`; + + cy.task('log', `OAuth origin: ${oauthOrigin}`); + + // Use cy.origin to handle the OAuth login on different domain + cy.origin( + oauthOrigin, + { args: { provider, username, password } }, + ({ provider, username, password }) => { + cy.get('[data-test-id="login"]').should('be.visible'); + cy.get('body').then(($body) => { + if ($body.text().includes(provider)) { + cy.contains(provider).should('be.visible').click(); } - ); + }); + cy.get('#inputUsername').type(username); + cy.get('#inputPassword').type(password); + cy.get('button[type=submit]').click(); } - }); - }, - ); + ); + }); + }); }, { cacheAcrossSpecs: true, validate() { + cy.visit(Cypress.config('baseUrl')); + // Wait for any OAuth redirects to complete and ensure we're on console domain + cy.url({ timeout: 30000 }).should('contain', 'console-openshift-console'); cy.byTestID("username", {timeout: 120000}).should('be.visible'); guidedTour.close(); }, @@ -178,8 +185,10 @@ Cypress.Commands.add('switchPerspective', (perspective: string) => { cy.get('#nav-toggle').click(); } }); - nav.sidenav.switcher.changePerspectiveTo(perspective); - nav.sidenav.switcher.shouldHaveText(perspective); + // Note: nav object would need to be imported or defined elsewhere + // For now, using direct DOM selectors + cy.get('[data-test="perspective-switcher-toggle"]').click(); + cy.contains('[data-test="perspective-switcher-menu-option"]', perspective).click(); }); // To avoid influence from upstream login change @@ -263,4 +272,111 @@ Cypress.Commands.add('executeAndDelete', (command: string) => { cy.task('log', `Command "${command}" executed successfully`); } }); -}); \ No newline at end of file +}); + +// Best practice selector commands following accessibility and stability guidelines + +Cypress.Commands.add( + 'byCy', + (selector: string, options?: Partial) => { + cy.get(`[data-cy="${selector}"]`, options); + }, +); + +Cypress.Commands.add( + 'byAriaLabel', + (selector: string, options?: Partial) => { + cy.get(`[aria-label="${selector}"]`, options); + }, +); + +Cypress.Commands.add( + 'byRole', + (role: string, name?: string, options?: Partial) => { + const selector = name ? `[role="${role}"][aria-label="${name}"], [role="${role}"][aria-labelledby*="${name}"]` : `[role="${role}"]`; + cy.get(selector, options); + }, +); + +Cypress.Commands.add( + 'byText', + (text: string, options?: Partial) => { + cy.contains(text, options); + }, +); + +Cypress.Commands.add('findByTestId', (selector: string) => { + return cy.get(`[data-testid="${selector}"]`); +}); + +// PatternFly-specific commands for common component patterns + +Cypress.Commands.add( + 'byLabelText', + (text: string, options?: Partial) => { + // Find form elements by their associated label - with error handling + cy.get('body').then(($body) => { + if ($body.find(`label:contains("${text}")`).length > 0) { + cy.get(`label:contains("${text}")`, options).then(($label) => { + const forAttr = $label.attr('for'); + if (forAttr) { + cy.get(`#${forAttr}`, options); + } else { + cy.wrap($label).find('input, select, textarea', options); + } + }); + } else { + cy.log(`Label with text "${text}" not found`); + } + }); + }, +); + +Cypress.Commands.add( + 'pfMenuToggle', + (text?: string, options?: Partial) => { + const defaultOptions = { timeout: 10000, ...options }; + if (text) { + cy.get('.pf-v6-c-menu-toggle, .pf-v5-c-menu-toggle', defaultOptions).contains(text); + } else { + cy.get('.pf-v6-c-menu-toggle, .pf-v5-c-menu-toggle', defaultOptions); + } + }, +); + +Cypress.Commands.add( + 'pfMenuItem', + (text: string, options?: Partial) => { + const defaultOptions = { timeout: 10000, ...options }; + cy.get('.pf-v6-c-menu__item, .pf-v5-c-menu__item', defaultOptions).contains(text); + }, +); + +Cypress.Commands.add( + 'pfButton', + (text: string, options?: Partial) => { + const defaultOptions = { timeout: 10000, ...options }; + cy.contains('.pf-v6-c-button, .pf-v5-c-button', text, defaultOptions); + }, +); + +Cypress.Commands.add( + 'pfEmptyState', + (options?: Partial) => { + const defaultOptions = { timeout: 10000, ...options }; + cy.get('.pf-v6-c-empty-state, .pf-v5-c-empty-state', defaultOptions); + }, +); + +Cypress.Commands.add( + 'pfToolbarItem', + (index?: number, options?: Partial) => { + const selector = '.pf-v6-c-toolbar__item, .pf-v5-c-toolbar__item'; + const defaultOptions = { timeout: 10000, ...options }; + if (typeof index === 'number') { + cy.get(selector, defaultOptions).eq(index); + } else { + cy.get(selector, defaultOptions); + } + }, +); \ No newline at end of file diff --git a/tests/cypress/support/e2e.js b/tests/cypress/support/e2e.js index 06b7d84..51dd1c0 100644 --- a/tests/cypress/support/e2e.js +++ b/tests/cypress/support/e2e.js @@ -2,3 +2,18 @@ import './commands'; import registerCypressGrep from '@cypress/grep'; registerCypressGrep(); + +// Handle uncaught exceptions from the application +Cypress.on('uncaught:exception', (err, runnable) => { + // Return false to prevent the test from failing on uncaught exceptions + // that might be caused by the application's React state management + if (err.message.includes('e is not a function') || + err.message.includes('Cannot read prop') || + err.message.includes('undefined is not a function') || + err.message.includes('Cannot read properties of undefined')) { + console.log('Caught application error:', err.message); + return false; + } + // Let other exceptions fail the test + return true; +}); diff --git a/tests/e2e/dt-plugin-tests.cy.ts b/tests/e2e/dt-plugin-tests.cy.ts new file mode 100644 index 0000000..00cac9c --- /dev/null +++ b/tests/e2e/dt-plugin-tests.cy.ts @@ -0,0 +1,497 @@ +import { operatorHubPage } from '../views/operator-hub-page'; + +// Set constants for the operators that need to be installed for tests. +const DTP = { + namespace: 'openshift-cluster-observability-operator', + packageName: 'cluster-observability-operator', + operatorName: 'Cluster Observability Operator', + config: { + kind: 'UIPlugin', + name: 'distributed-tracing', + }, +}; + +const OTEL = { + namespace: 'openshift-opentelemetry-operator', + packageName: 'opentelemetry-product', + operatorName: 'Red Hat build of OpenTelemetry', +}; + +const TEMPO = { + namespace: 'openshift-tempo-operator', + packageName: 'tempo-product', + operatorName: 'Tempo Operator', +}; + +describe('OpenShift Distributed Tracing UI Plugin tests', () => { + before(() => { + // Cleanup any existing resources from interrupted tests + cy.log('Cleanup any existing resources from previous interrupted tests'); + if (Cypress.env('SKIP_COO_INSTALL')) { + cy.log('Delete Distributed Tracing UI Plugin instance if exists.'); + cy.executeAndDelete( + `oc delete ${DTP.config.kind} ${DTP.config.name} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + + cy.log('Delete Chainsaw namespaces if they exist.'); + cy.exec( + `for ns in $(oc get projects -o name --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} | grep "chainsaw-" | sed 's|project.project.openshift.io/||'); do oc get opentelemetrycollectors.opentelemetry.io,tempostacks.tempo.grafana.com,tempomonolithics.tempo.grafana.com,pvc -n $ns -o name --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} 2>/dev/null | xargs --no-run-if-empty -I {} oc patch {} -n $ns --type merge -p '{"metadata":{"finalizers":[]}}' --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} 2>/dev/null || true; oc delete project $ns --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} || true; done`, + { + timeout: 180000, + failOnNonZeroExit: false + } + ); + + // Only remove cluster-admin role if provider is not kube:admin + if (Cypress.env('LOGIN_IDP') !== 'kube:admin') { + cy.log('Remove cluster-admin role from user if exists.'); + cy.executeAndDelete( + `oc adm policy remove-cluster-role-from-user cluster-admin ${Cypress.env('LOGIN_USERNAME')} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + } + } else { + cy.log('Delete Distributed Tracing UI Plugin instance if exists.'); + cy.executeAndDelete( + `oc delete ${DTP.config.kind} ${DTP.config.name} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + + cy.log('Delete Chainsaw namespaces if they exist.'); + cy.exec( + `for ns in $(oc get projects -o name --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} | grep "chainsaw-" | sed 's|project.project.openshift.io/||'); do oc get opentelemetrycollectors.opentelemetry.io,tempostacks.tempo.grafana.com,tempomonolithics.tempo.grafana.com,pvc -n $ns -o name --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} 2>/dev/null | xargs --no-run-if-empty -I {} oc patch {} -n $ns --type merge -p '{"metadata":{"finalizers":[]}}' --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} 2>/dev/null || true; oc delete project $ns --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} || true; done`, + { + timeout: 180000, + failOnNonZeroExit: false + } + ); + + cy.log('Remove Cluster Observability Operator if exists'); + cy.executeAndDelete(`oc delete namespace ${DTP.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`); + + cy.log('Remove OpenTelemetry Operator if exists'); + cy.executeAndDelete(`oc delete namespace ${OTEL.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`); + + cy.log('Remove Tempo Operator if exists'); + cy.executeAndDelete(`oc delete namespace ${TEMPO.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`); + + // Only remove cluster-admin role if provider is not kube:admin + if (Cypress.env('LOGIN_IDP') !== 'kube:admin') { + cy.log('Remove cluster-admin role from user if exists.'); + cy.executeAndDelete( + `oc adm policy remove-cluster-role-from-user cluster-admin ${Cypress.env('LOGIN_USERNAME')} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + } + } + // Only add cluster-admin role if provider is not kube:admin + if (Cypress.env('LOGIN_IDP') !== 'kube:admin') { + cy.adminCLI( + `oc adm policy add-cluster-role-to-user cluster-admin ${Cypress.env('LOGIN_USERNAME')}`, + ); + } + // Simplified login without OAuth URL complexity + cy.login( + Cypress.env('LOGIN_IDP'), + Cypress.env('LOGIN_USERNAME'), + Cypress.env('LOGIN_PASSWORD'), + ); + + if (Cypress.env('SKIP_COO_INSTALL')) { + cy.log('SKIP_COO_INSTALL is set. Skipping Cluster Observability Operator installation.'); + } else if (Cypress.env('COO_UI_INSTALL')) { + cy.log('COO_UI_INSTALL is set. COO, Tempo and OpenTelemetry operators will be installed from redhat-operators catalog source'); + cy.log('Install Cluster Observability Operator'); + operatorHubPage.installOperator(DTP.packageName, 'redhat-operators'); + cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should(($el) => { + const text = $el.text(); + expect(text).to.satisfy((t) => + t.includes('ready for use') || t.includes('Operator installed successfully') + ); + }); + cy.log('Install OpenTelemetry Operator'); + operatorHubPage.installOperator(OTEL.packageName, 'redhat-operators'); + cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should(($el) => { + const text = $el.text(); + expect(text).to.satisfy((t) => + t.includes('ready for use') || t.includes('Operator installed successfully') + ); + }); + cy.log('Install Tempo Operator'); + operatorHubPage.installOperator(TEMPO.packageName, 'redhat-operators'); + cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should(($el) => { + const text = $el.text(); + expect(text).to.satisfy((t) => + t.includes('ready for use') || t.includes('Operator installed successfully') + ); + }); + } else if (Cypress.env('KONFLUX_COO_BUNDLE_IMAGE')) { + cy.log('KONFLUX_COO_BUNDLE_IMAGE is set. COO operator will be installed from Konflux bundle. Tempo and OpenTelemetry operators will be installed from redhat-operators catalog source'); + cy.log('Install Cluster Observability Operator'); + cy.exec( + `oc --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} apply -f ./fixtures/coo-imagecontentsourcepolicy.yaml` , + ); + cy.exec( + `oc create namespace ${DTP.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + cy.exec( + `oc label namespaces ${DTP.namespace} openshift.io/cluster-monitoring=true --overwrite=true --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + cy.exec( + `operator-sdk run bundle --timeout=10m --namespace ${DTP.namespace} ${Cypress.env('KONFLUX_COO_BUNDLE_IMAGE')} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} --verbose `, + { timeout: 6 * 60 * 1000 }, + ); + cy.log('Install OpenTelemetry Operator'); + operatorHubPage.installOperator(OTEL.packageName, 'redhat-operators'); + cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should(($el) => { + const text = $el.text(); + expect(text).to.satisfy((t) => + t.includes('ready for use') || t.includes('Operator installed successfully') + ); + }); + cy.log('Install Tempo Operator'); + operatorHubPage.installOperator(TEMPO.packageName, 'redhat-operators'); + cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should(($el) => { + const text = $el.text(); + expect(text).to.satisfy((t) => + t.includes('ready for use') || t.includes('Operator installed successfully') + ); + }); + } else if (Cypress.env('CUSTOM_COO_BUNDLE_IMAGE')) { + cy.log('CUSTOM_COO_BUNDLE_IMAGE is set. COO operator will be installed from custom built bundle. Tempo and OpenTelemetry operators will be installed from redhat-operators catalog source'); + cy.log('Install Cluster Observability Operator'); + cy.exec( + `oc --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} apply -f ./fixtures/coo-imagecontentsourcepolicy.yaml` , + ); + cy.exec( + `oc create namespace ${DTP.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + cy.exec( + `oc label namespaces ${DTP.namespace} openshift.io/cluster-monitoring=true --overwrite=true --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + cy.exec( + `operator-sdk run bundle --timeout=10m --namespace ${DTP.namespace} ${Cypress.env('CUSTOM_COO_BUNDLE_IMAGE')} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} --verbose `, + { timeout: 6 * 60 * 1000 }, + ); + cy.log('Install OpenTelemetry Operator'); + operatorHubPage.installOperator(OTEL.packageName, 'redhat-operators'); + cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should(($el) => { + const text = $el.text(); + expect(text).to.satisfy((t) => + t.includes('ready for use') || t.includes('Operator installed successfully') + ); + }); + cy.log('Install Tempo Operator'); + operatorHubPage.installOperator(TEMPO.packageName, 'redhat-operators'); + cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should(($el) => { + const text = $el.text(); + expect(text).to.satisfy((t) => + t.includes('ready for use') || t.includes('Operator installed successfully') + ); + }); + } else { + throw new Error('No CYPRESS env set for operator installation, check the README for more details.'); + } + + cy.log('Set Distributed Tracing Console Plugin image in operator CSV'); + if (Cypress.env('DT_CONSOLE_IMAGE')) { + cy.log('DT_CONSOLE_IMAGE is set. the image will be patched in COO operator CSV'); + cy.exec( + './fixtures/update-plugin-image.sh', + { + env: { + DT_CONSOLE_IMAGE: Cypress.env('DT_CONSOLE_IMAGE'), + KUBECONFIG: Cypress.env('KUBECONFIG_PATH'), + DTP_NAMESPACE: `${DTP.namespace}` + }, + timeout: 120000, + failOnNonZeroExit: true + } + ) .then((result) => { + expect(result.code).to.eq(0); + cy.log(`COO CSV updated successfully with Distributed Tracing Console Plugin image: ${result.stdout}`); + }); + } else { + cy.log('DT_CONSOLE_IMAGE is NOT set. Skipping patching the image in COO operator CSV.'); + } + + cy.log('Create Distributed Tracing UI Plugin instance.'); + cy.exec(`oc apply -f ./fixtures/tracing-ui-plugin.yaml --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`); + cy.exec( + `sleep 15 && oc wait --for=condition=Ready pods --selector=app.kubernetes.io/instance=distributed-tracing -n ${DTP.namespace} --timeout=60s --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + { + timeout: 80000, + failOnNonZeroExit: true + } + ).then((result) => { + expect(result.code).to.eq(0); + cy.log(`Distributed Tracing Console plugin pod is now running in namespace: ${DTP.namespace}`); + }); + // Check for web console update alert, if not found, go to traces page + cy.get('body').then(($body) => { + if ($body.find('.pf-v5-c-alert, .pf-v6-c-alert').length > 0 && + $body.text().includes('Web console update is available')) { + cy.get('.pf-v5-c-alert, .pf-v6-c-alert') + .contains('Web console update is available') + .should('exist'); + } else { + cy.visit('/observe/traces'); + cy.url().should('include', '/observe/traces'); + } + }); + + }); + + after(() => { + if (Cypress.env('SKIP_COO_INSTALL')) { + cy.log('Delete Distributed Tracing UI Plugin instance.'); + cy.executeAndDelete( + `oc delete ${DTP.config.kind} ${DTP.config.name} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + + cy.log('Delete Chainsaw namespaces.'); + cy.exec( + `for ns in $(oc get projects -o name --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} | grep "chainsaw-" | sed 's|project.project.openshift.io/||'); do oc get opentelemetrycollectors.opentelemetry.io,tempostacks.tempo.grafana.com,tempomonolithics.tempo.grafana.com,pvc -n $ns -o name --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} 2>/dev/null | xargs --no-run-if-empty -I {} oc patch {} -n $ns --type merge -p '{"metadata":{"finalizers":[]}}' --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} 2>/dev/null || true; oc delete project $ns --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} || true; done`, + { + timeout: 300000, + failOnNonZeroExit: false + } + ); + + // Only remove cluster-admin role if provider is not kube:admin + if (Cypress.env('LOGIN_IDP') !== 'kube:admin') { + cy.log('Remove cluster-admin role from user.'); + cy.executeAndDelete( + `oc adm policy remove-cluster-role-from-user cluster-admin ${Cypress.env('LOGIN_USERNAME')} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + } + } else { + cy.log('Delete Distributed Tracing UI Plugin instance.'); + cy.executeAndDelete( + `oc delete ${DTP.config.kind} ${DTP.config.name} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + + cy.log('Delete Chainsaw namespaces.'); + cy.exec( + `for ns in $(oc get projects -o name --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} | grep "chainsaw-" | sed 's|project.project.openshift.io/||'); do oc get opentelemetrycollectors.opentelemetry.io,tempostacks.tempo.grafana.com,tempomonolithics.tempo.grafana.com,pvc -n $ns -o name --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} 2>/dev/null | xargs --no-run-if-empty -I {} oc patch {} -n $ns --type merge -p '{"metadata":{"finalizers":[]}}' --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} 2>/dev/null || true; oc delete project $ns --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} || true; done`, + { + timeout: 300000, + failOnNonZeroExit: false + } + ); + + cy.log('Remove Cluster Observability Operator'); + cy.executeAndDelete(`oc delete namespace ${DTP.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`); + + cy.log('Remove OpenTelemetry Operator'); + cy.executeAndDelete(`oc delete namespace ${OTEL.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`); + + cy.log('Remove Tempo Operator'); + cy.executeAndDelete(`oc delete namespace ${TEMPO.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`); + + // Only remove cluster-admin role if provider is not kube:admin + if (Cypress.env('LOGIN_IDP') !== 'kube:admin') { + cy.log('Remove cluster-admin role from user.'); + cy.executeAndDelete( + `oc adm policy remove-cluster-role-from-user cluster-admin ${Cypress.env('LOGIN_USERNAME')} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ); + } + } + }); + + // Tests start from here. + + it('Test Distributed Tracing UI plugin page without any Tempo instances', () => { + cy.log('Navigate to the observe/traces page'); + cy.visit('/observe/traces'); + + cy.log('Assert that the Traces page shows the empty state.'); + // Using PatternFly empty state helper with heading selector + cy.pfEmptyState().within(() => { + cy.get('h1, h2, h3, h4, h5, h6').should('contain.text', 'No Tempo instances yet'); + }); + + cy.log('Assert that the View documentation button is visible.'); + // Using PatternFly button helper + cy.pfButton('View documentation') + .should('be.visible') + .and('have.text', 'View documentation'); + + cy.log('Assert create a tempo instance toggle visibility and text.'); + // Using PatternFly menu toggle helper + cy.pfMenuToggle('Create a Tempo instance').should('be.visible'); + + cy.log('Click the toggle to show creation options.'); + cy.pfMenuToggle('Create a Tempo instance').click(); + + cy.log('Assert dropdown items for Tempo instance creation are visible.'); + // Using PatternFly menu item helpers + cy.pfMenuItem('Create a TempoStack instance') + .should('be.visible') + .and('have.text', 'Create a TempoStack instance'); + + cy.pfMenuItem('Create a TempoMonolithic instance') + .should('be.visible') + .and('have.text', 'Create a TempoMonolithic instance'); + }); + + it('Test Distributed Tracing UI plugin with Tempo instances and verify traces using user having cluster-admin role', function () { + cy.log('Create TempoStack and TempoMonolithic instances'); + cy.exec( + 'chainsaw test --config ./fixtures/.chainsaw.yaml --skip-delete ./fixtures/chainsaw-tests', + { + env: { + KUBECONFIG: Cypress.env('KUBECONFIG_PATH'), + }, + timeout: 1800000, + failOnNonZeroExit: true + } + ) .then((result) => { + expect(result.code).to.eq(0); + cy.log(`Chainsaw test ran successfully: ${result.stdout}`); + }); + cy.log('Navigate to the observe/traces page'); + cy.visit('/observe/traces'); + + cy.log('Assert traces in TempoStack instance.'); + cy.get(':nth-child(1) > .pf-v6-c-toolbar__item > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button > .pf-v6-c-menu-toggle__controls > .pf-v6-c-menu-toggle__toggle-icon').click(); + cy.get(':nth-child(2) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); + cy.get(':nth-child(1) > :nth-child(2) > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button').click(); + cy.get(':nth-child(2) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); + cy.get(':nth-child(2) > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__controls > .pf-v6-c-menu-toggle__toggle-icon').click(); + cy.get(':nth-child(1) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); + cy.get(':nth-child(1) > :nth-child(2) > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button > .pf-v6-c-menu-toggle__controls > .pf-v6-c-menu-toggle__toggle-icon').click(); + cy.get(':nth-child(1) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); + cy.get('.pf-v6-c-toolbar__group > :nth-child(1) > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__controls > .pf-v6-c-menu-toggle__toggle-icon').click(); + cy.get(':nth-child(1) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); + cy.get('.pf-m-toggle-group > .pf-v6-c-toolbar__group > :nth-child(2) > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button').click(); + cy.contains('.pf-v6-c-menu__item-text', 'http-rbac-1') + .closest('.pf-v6-c-menu__item') + .find('input[type="checkbox"]') + .check(); + cy.contains('.pf-v6-c-menu__item-text', 'http-rbac-2') + .closest('.pf-v6-c-menu__item') + .find('input[type="checkbox"]') + .check(); + cy.contains('.pf-v6-c-menu__item-text', 'grpc-rbac-1') + .closest('.pf-v6-c-menu__item') + .find('input[type="checkbox"]') + .check(); + cy.contains('.pf-v6-c-menu__item-text', 'grpc-rbac-2') + .closest('.pf-v6-c-menu__item') + .find('input[type="checkbox"]') + .check(); + cy.get('.MuiDataGrid-row--firstVisible > [data-field="name"] > .MuiBox-root > .MuiTypography-root').click(); + cy.contains('div', 'okey-dokey').click({ force: true }); + cy.get('.css-1bmckj4').then(($el) => { + cy.log(`Actual text in .css-1bmckj4 (TempoStack): ${$el.text()}`); + expect($el.text()).to.satisfy((text) => + text === 'http-rbac-1' || + text === 'http-rbac-2' || + text === 'grpc-rbac-1' || + text === 'grpc-rbac-2' + ); + }); + cy.get('.MuiTypography-h2').should('have.text', 'okey-dokey'); + cy.get('.MuiTabs-list > .MuiButtonBase-root').should('be.visible'); + + // Check for net.peer.ip + cy.contains('.MuiTypography-h5', 'net.peer.ip').next('.MuiTypography-body1').should('have.text', '1.2.3.4'); + + // Check for peer.service + cy.contains('.MuiTypography-h5', 'peer.service').next('.MuiTypography-body1').should('have.text', 'telemetrygen-client'); + + // Check for k8s.container.name if present + cy.get('body').then(($body) => { + if ($body.find('.MuiTypography-h5:contains("k8s.container.name")').length > 0) { + cy.contains('.MuiTypography-h5', 'k8s.container.name').next('.MuiTypography-body1').should('have.text', 'telemetrygen'); + } + }); + + // Check for k8s.namespace.name if present + cy.get('body').then(($body) => { + if ($body.find('.MuiTypography-h5:contains("k8s.namespace.name")').length > 0) { + cy.contains('.MuiTypography-h5', 'k8s.namespace.name').next('.MuiTypography-body1').then(($el) => { + cy.log(`Actual text in k8s.namespace.name (TempoStack): ${$el.text()}`); + expect($el.text()).to.satisfy((text) => + text === 'chainsaw-test-rbac-1' || + text === 'chainsaw-test-rbac-2' || + text === 'chainsaw-mono-rbac-1' || + text === 'chainsaw-mono-rbac-2' + ); + }); + } + }); + + // Check for service.name + cy.contains('.MuiTypography-h5', 'service.name').next('.MuiTypography-body1').then(($el) => { + cy.log(`Actual text in service.name (TempoStack): ${$el.text()}`); + expect($el.text()).to.satisfy((text) => + text === 'http-rbac-1' || + text === 'http-rbac-2' || + text === 'grpc-rbac-1' || + text === 'grpc-rbac-2' + ); + }); + cy.get('.pf-v6-c-breadcrumb__list > :nth-child(1) > a').click(); + + cy.log('Assert traces in TempoMonolithic instance.'); + cy.get(':nth-child(1) > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button').click(); + cy.get(':nth-child(1) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); + cy.get(':nth-child(2) > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle').click(); + cy.get(':nth-child(3) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); + cy.get('.pf-v6-c-form__group-control > .pf-v6-c-button > .pf-v6-c-button__text').click(); + cy.get('.pf-m-toggle-group > .pf-m-action-group > .pf-v6-c-toolbar__item > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-button > .pf-v6-c-button__text').click(); + cy.get('.MuiDataGrid-row--firstVisible > [data-field="name"] > .MuiBox-root > .MuiTypography-root').click(); + cy.contains('div', 'okey-dokey').click({ force: true }); + cy.get('.css-1bmckj4').then(($el) => { + cy.log(`Actual text in .css-1bmckj4 (TempoMonolithic): ${$el.text()}`); + expect($el.text()).to.satisfy((text) => + text === 'http-rbac-1' || + text === 'http-rbac-2' || + text === 'grpc-rbac-1' || + text === 'grpc-rbac-2' + ); + }); + cy.get('.MuiTypography-h2').should('have.text', 'okey-dokey'); + cy.get('.MuiTabs-list > .MuiButtonBase-root').should('be.visible'); + // Check for span details with more flexible approach + // Check for net.peer.ip + cy.contains('.MuiTypography-h5', 'net.peer.ip').next('.MuiTypography-body1').should('have.text', '1.2.3.4'); + + // Check for peer.service + cy.contains('.MuiTypography-h5', 'peer.service').next('.MuiTypography-body1').should('have.text', 'telemetrygen-client'); + + // Check for k8s.container.name if present + cy.get('body').then(($body) => { + if ($body.find('.MuiTypography-h5:contains("k8s.container.name")').length > 0) { + cy.contains('.MuiTypography-h5', 'k8s.container.name').next('.MuiTypography-body1').should('have.text', 'telemetrygen'); + } + }); + + // Check for k8s.namespace.name if present + cy.get('body').then(($body) => { + if ($body.find('.MuiTypography-h5:contains("k8s.namespace.name")').length > 0) { + cy.contains('.MuiTypography-h5', 'k8s.namespace.name').next('.MuiTypography-body1').then(($el) => { + cy.log(`Actual text in k8s.namespace.name (TempoMonolithic): ${$el.text()}`); + expect($el.text()).to.satisfy((text) => + text === 'chainsaw-test-rbac-1' || + text === 'chainsaw-test-rbac-2' || + text === 'chainsaw-mono-rbac-1' || + text === 'chainsaw-mono-rbac-2' + ); + }); + } + }); + + // Check for service.name + cy.contains('.MuiTypography-h5', 'service.name').next('.MuiTypography-body1').then(($el) => { + cy.log(`Actual text in service.name (TempoMonolithic): ${$el.text()}`); + expect($el.text()).to.satisfy((text) => + text === 'http-rbac-1' || + text === 'http-rbac-2' || + text === 'grpc-rbac-1' || + text === 'grpc-rbac-2' + ); + }); + cy.get('.pf-v6-c-breadcrumb__list > :nth-child(1) > a').click(); + }); + +}); diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/01-assert.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/01-assert.yaml deleted file mode 100644 index 5e8f07d..0000000 --- a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/01-assert.yaml +++ /dev/null @@ -1,94 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: tempo-mmo -status: - readyReplicas: 1 - ---- -apiVersion: v1 -kind: Pod -metadata: - name: tempo-mmo-0 -status: - containerStatuses: - - name: jaeger-query - ready: true - started: true - - name: tempo - ready: true - started: true - - name: tempo-gateway - ready: true - started: true - - name: tempo-gateway-opa - ready: true - started: true - - name: tempo-query - ready: true - started: true - phase: Running - ---- -apiVersion: v1 -kind: Service -metadata: - name: tempo-mmo-gateway -spec: - ports: - - name: public - port: 8080 - protocol: TCP - targetPort: public - - name: internal - port: 8081 - protocol: TCP - targetPort: internal - - name: otlp-grpc - port: 4317 - protocol: TCP - targetPort: grpc-public - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/component: gateway - app.kubernetes.io/instance: mmo - app.kubernetes.io/managed-by: tempo-operator - app.kubernetes.io/name: tempo-monolithic - app.kubernetes.io/namespace: chainsaw-monolithic-multitenancy - name: tempo-mmo-gateway-chainsaw-monolithic-multitenancy -rules: -- apiGroups: - - authentication.k8s.io - resources: - - tokenreviews - verbs: - - create -- apiGroups: - - authorization.k8s.io - resources: - - subjectaccessreviews - verbs: - - create ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/component: gateway - app.kubernetes.io/instance: mmo - app.kubernetes.io/managed-by: tempo-operator - app.kubernetes.io/name: tempo-monolithic - app.kubernetes.io/namespace: chainsaw-monolithic-multitenancy - name: tempo-mmo-gateway-chainsaw-monolithic-multitenancy -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: tempo-mmo-gateway-chainsaw-monolithic-multitenancy -subjects: -- kind: ServiceAccount - name: tempo-mmo - namespace: chainsaw-monolithic-multitenancy diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/01-install-tempo.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/01-install-tempo.yaml deleted file mode 100644 index 4e1f0be..0000000 --- a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/01-install-tempo.yaml +++ /dev/null @@ -1,83 +0,0 @@ -apiVersion: tempo.grafana.com/v1alpha1 -kind: TempoMonolithic -metadata: - name: mmo -spec: - jaegerui: - enabled: true - route: - enabled: true - multitenancy: - enabled: true - mode: openshift - authentication: - - tenantName: dev - tenantId: "1610b0c3-c509-4592-a256-a1871353dbfa" - - tenantName: prod - tenantId: "1610b0c3-c509-4592-a256-a1871353dbfb" ---- - -# Grant the dev-collector Service Account permission to write traces to the 'dev' tenant -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: allow-write-traces-dev-tenant -rules: -- apiGroups: [tempo.grafana.com] - resources: [dev] # tenantName - resourceNames: [traces] - verbs: [create] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: allow-write-traces-dev-tenant -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: allow-write-traces-dev-tenant -subjects: -- kind: ServiceAccount - name: dev-collector - namespace: chainsaw-monolithic-multitenancy ---- - -# Grant the default Service Account (used by the verify-traces pod) permission to read traces of the 'dev' tenant -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: allow-read-traces-dev-tenant -rules: -- apiGroups: [tempo.grafana.com] - resources: [dev] # tenantName - resourceNames: [traces] - verbs: [get] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: allow-read-traces-dev-tenant -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: allow-read-traces-dev-tenant -subjects: -- kind: ServiceAccount - name: default - namespace: chainsaw-monolithic-multitenancy ---- -# Grant the default ServiceAccount (used by the verify-traces pod) view permissions of the chainsaw-monolithic-multitenancy namespace. -# If the ServiceAccount cannot access any namespaces, every 'get' request will be denied: -# https://github.com/observatorium/opa-openshift/pull/18/files -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: view -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: view -subjects: -- kind: ServiceAccount - name: default - namespace: chainsaw-monolithic-multitenancy diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/02-install-otelcol.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/02-install-otelcol.yaml deleted file mode 100644 index 6442bfc..0000000 --- a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/02-install-otelcol.yaml +++ /dev/null @@ -1,50 +0,0 @@ -apiVersion: opentelemetry.io/v1alpha1 -kind: OpenTelemetryCollector -metadata: - name: dev -spec: - config: | - extensions: - bearertokenauth: - filename: /var/run/secrets/kubernetes.io/serviceaccount/token - - receivers: - otlp/grpc: - protocols: - grpc: - otlp/http: - protocols: - http: - - exporters: - otlp: - endpoint: tempo-mmo-gateway.chainsaw-monolithic-multitenancy.svc.cluster.local:4317 - tls: - ca_file: /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt - auth: - authenticator: bearertokenauth - headers: - X-Scope-OrgID: dev # tenantName - otlphttp: - endpoint: https://tempo-mmo-gateway.chainsaw-monolithic-multitenancy.svc.cluster.local:8080/api/traces/v1/dev - tls: - ca_file: /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt - auth: - authenticator: bearertokenauth - headers: - X-Scope-OrgID: dev # tenantName - - service: - telemetry: - logs: - level: "DEBUG" - development: true - encoding: "json" - extensions: [bearertokenauth] - pipelines: - traces/grpc: - receivers: [otlp/grpc] - exporters: [otlp] - traces/http: - receivers: [otlp/http] - exporters: [otlphttp] diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/03-assert.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/03-assert.yaml deleted file mode 100644 index 1fafdf1..0000000 --- a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/03-assert.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: generate-traces-grpc -status: - succeeded: 1 ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: generate-traces-http -status: - succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/03-generate-traces.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/03-generate-traces.yaml deleted file mode 100644 index 15d57ce..0000000 --- a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/03-generate-traces.yaml +++ /dev/null @@ -1,36 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: generate-traces-grpc -spec: - template: - spec: - containers: - - name: telemetrygen - image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 - args: - - traces - - --otlp-endpoint=dev-collector:4317 - - --service=grpc - - --otlp-insecure - - --traces=10 - restartPolicy: Never ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: generate-traces-http -spec: - template: - spec: - containers: - - name: telemetrygen - image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 - args: - - traces - - --otlp-endpoint=dev-collector:4318 - - --otlp-http - - --otlp-insecure - - --service=http - - --traces=10 - restartPolicy: Never \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/04-assert.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/04-assert.yaml deleted file mode 100644 index ff19437..0000000 --- a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/04-assert.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-jaegerui-grpc -status: - succeeded: 1 ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-traceql-grpc -status: - succeeded: 1 ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-jaegerui-http -status: - succeeded: 1 ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-traceql-http -status: - succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/04-verify-traces.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/04-verify-traces.yaml deleted file mode 100644 index 40e0b4d..0000000 --- a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/04-verify-traces.yaml +++ /dev/null @@ -1,107 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-jaegerui-grpc -spec: - template: - spec: - containers: - - name: verify-traces - image: ghcr.io/grafana/tempo-operator/test-utils:main - command: ["/bin/bash", "-eux", "-c"] - args: - - | - curl -vG \ - --header "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ - --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ - https://tempo-mmo-gateway.chainsaw-monolithic-multitenancy.svc:8080/api/traces/v1/dev/api/traces \ - --data-urlencode "service=grpc" \ - | tee /tmp/jaeger.out - - num_traces=$(jq ".data | length" /tmp/jaeger.out) - if [[ "$num_traces" != "10" ]]; then - echo && echo "The Jaeger API returned $num_traces instead of 10 traces." - exit 1 - fi - restartPolicy: Never ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-traceql-grpc -spec: - template: - spec: - containers: - - name: verify-traces - image: ghcr.io/grafana/tempo-operator/test-utils:main - command: ["/bin/bash", "-eux", "-c"] - args: - - | - curl -sS -G \ - --header "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ - --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ - --data-urlencode 'q={ resource.service.name="grpc" }' \ - https://tempo-mmo-gateway.chainsaw-monolithic-multitenancy.svc:8080/api/traces/v1/dev/tempo/api/search \ - | tee /tmp/tempo.out - - num_traces=$(jq ".traces | length" /tmp/tempo.out) - if [[ "$num_traces" != "10" ]]; then - echo && echo "The Tempo API returned $num_traces instead of 10 traces." - exit 1 - fi - restartPolicy: Never ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-jaegerui-http -spec: - template: - spec: - containers: - - name: verify-traces - image: ghcr.io/grafana/tempo-operator/test-utils:main - command: ["/bin/bash", "-eux", "-c"] - args: - - | - curl -vG \ - --header "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ - --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ - https://tempo-mmo-gateway.chainsaw-monolithic-multitenancy.svc:8080/api/traces/v1/dev/api/traces \ - --data-urlencode "service=http" \ - | tee /tmp/jaeger.out - - num_traces=$(jq ".data | length" /tmp/jaeger.out) - if [[ "$num_traces" != "10" ]]; then - echo && echo "The Jaeger API returned $num_traces instead of 10 traces." - exit 1 - fi - restartPolicy: Never ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-traceql-http -spec: - template: - spec: - containers: - - name: verify-traces - image: ghcr.io/grafana/tempo-operator/test-utils:main - command: ["/bin/bash", "-eux", "-c"] - args: - - | - curl -sS -G \ - --header "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ - --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ - --data-urlencode 'q={ resource.service.name="http" }' \ - https://tempo-mmo-gateway.chainsaw-monolithic-multitenancy.svc:8080/api/traces/v1/dev/tempo/api/search \ - | tee /tmp/tempo.out - - num_traces=$(jq ".traces | length" /tmp/tempo.out) - if [[ "$num_traces" != "10" ]]; then - echo && echo "The Tempo API returned $num_traces instead of 10 traces." - exit 1 - fi - restartPolicy: Never diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/chainsaw-test.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/chainsaw-test.yaml deleted file mode 100644 index 1de146c..0000000 --- a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/chainsaw-test.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json -apiVersion: chainsaw.kyverno.io/v1alpha1 -kind: Test -metadata: - name: monolithic-multitenancy-openshift -spec: - # this test must use a known namespace because of the CN field of the TLS certificate and the ClusterRoleBinding - namespace: chainsaw-monolithic-multitenancy - steps: - - name: step-01 - try: - - apply: - file: 01-install-tempo.yaml - - assert: - file: 01-assert.yaml - - name: step-02 - try: - - apply: - file: 02-install-otelcol.yaml - - assert: - file: 02-assert.yaml - - name: step-03 - try: - - apply: - file: 03-generate-traces.yaml - - assert: - file: 03-assert.yaml - - name: step-04 - try: - - apply: - file: 04-verify-traces.yaml - - assert: - file: 04-assert.yaml - catch: - - events: {} - - podLogs: - selector: job-name=verify-traces-jaegerui - tail: 50 - - podLogs: - selector: job-name=verify-traces-traceql - tail: 50 - - podLogs: - selector: app.kubernetes.io/name=tempo-monolithic diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/01-assert.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/01-assert.yaml new file mode 100644 index 0000000..3cbeb36 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/01-assert.yaml @@ -0,0 +1,44 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: tempo-mmo-rbac +status: + readyReplicas: 1 + +--- +apiVersion: v1 +kind: Pod +metadata: + name: tempo-mmo-rbac-0 +status: + containerStatuses: + - name: tempo + ready: true + started: true + - name: tempo-gateway + ready: true + started: true + - name: tempo-gateway-opa + ready: true + started: true + phase: Running + +--- +apiVersion: v1 +kind: Service +metadata: + name: tempo-mmo-rbac-gateway +spec: + ports: + - name: public + port: 8080 + protocol: TCP + targetPort: public + - name: internal + port: 8081 + protocol: TCP + targetPort: internal + - name: otlp-grpc + port: 4317 + protocol: TCP + targetPort: grpc-public diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/01-install-tempo.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/01-install-tempo.yaml new file mode 100644 index 0000000..88925f5 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/01-install-tempo.yaml @@ -0,0 +1,52 @@ +apiVersion: tempo.grafana.com/v1alpha1 +kind: TempoMonolithic +metadata: + name: mmo-rbac +spec: + query: + rbac: + enabled: true + multitenancy: + enabled: true + mode: openshift + authentication: + - tenantName: dev + tenantId: "1610b0c3-c509-4592-a256-a1871353dbfa" + - tenantName: prod + tenantId: "1610b0c3-c509-4592-a256-a1871353dbfb" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: allow-read-traces-dev-tenant-rbac +rules: +- apiGroups: [tempo.grafana.com] + resources: [dev] + resourceNames: [traces] + verbs: [get] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: allow-read-traces-dev-tenant-rbac +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: allow-read-traces-dev-tenant-rbac +subjects: + - kind: Group + apiGroup: rbac.authorization.k8s.io + name: system:authenticated +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: view +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: view +subjects: +- kind: ServiceAccount + name: default + namespace: chainsaw-mmo-rbac diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/02-assert.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/02-assert.yaml similarity index 82% rename from tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/02-assert.yaml rename to tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/02-assert.yaml index 8e4e81a..4062bd9 100644 --- a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-openshift/02-assert.yaml +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/02-assert.yaml @@ -11,7 +11,7 @@ apiVersion: v1 kind: Service metadata: name: dev-collector - namespace: chainsaw-monolithic-multitenancy + namespace: chainsaw-mmo-rbac spec: ports: - appProtocol: grpc @@ -26,6 +26,6 @@ spec: targetPort: 4318 selector: app.kubernetes.io/component: opentelemetry-collector - app.kubernetes.io/instance: chainsaw-monolithic-multitenancy.dev + app.kubernetes.io/instance: chainsaw-mmo-rbac.dev app.kubernetes.io/managed-by: opentelemetry-operator app.kubernetes.io/part-of: opentelemetry \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/02-install-otelcol.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/02-install-otelcol.yaml new file mode 100644 index 0000000..0ce5aed --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/02-install-otelcol.yaml @@ -0,0 +1,132 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: otel-collector-deployment + namespace: chainsaw-mmo-rbac + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: chainsaw-mono-rbac-clusterrole +rules: +- apiGroups: [""] + resources: ["pods", "namespaces", "nodes"] + verbs: ["get", "watch", "list"] +- apiGroups: ["apps"] + resources: ["replicasets"] + verbs: ["get", "list", "watch"] +- apiGroups: ["extensions"] + resources: ["replicasets"] + verbs: ["get", "list", "watch"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: chainsaw-mono-rbac-clusterrole-binding +subjects: +- kind: ServiceAccount + name: otel-collector-deployment + namespace: chainsaw-mmo-rbac +roleRef: + kind: ClusterRole + name: chainsaw-mono-rbac-clusterrole + apiGroup: rbac.authorization.k8s.io + +--- +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: dev +spec: + serviceAccount: otel-collector-deployment + config: | + extensions: + bearertokenauth: + filename: /var/run/secrets/kubernetes.io/serviceaccount/token + + receivers: + otlp/grpc: + protocols: + grpc: + otlp/http: + protocols: + http: + + processors: + k8sattributes: + extract: + metadata: + - k8s.pod.name + - k8s.pod.uid + - k8s.deployment.name + - k8s.namespace.name + - k8s.node.name + pod_association: + - sources: + - from: resource_attribute + name: k8s.pod.ip + - sources: + - from: connection + + exporters: + debug: + verbosity: detailed + otlp: + endpoint: tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc.cluster.local:4317 + tls: + ca_file: /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt + auth: + authenticator: bearertokenauth + headers: + X-Scope-OrgID: dev # tenantName + otlphttp: + endpoint: https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc.cluster.local:8080/api/traces/v1/dev + tls: + ca_file: /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt + auth: + authenticator: bearertokenauth + headers: + X-Scope-OrgID: dev # tenantName + + service: + telemetry: + logs: + level: "DEBUG" + development: true + extensions: [bearertokenauth] + pipelines: + traces/grpc: + receivers: [otlp/grpc] + processors: [k8sattributes] + exporters: [otlp,debug] + traces/http: + receivers: [otlp/http] + processors: [k8sattributes] + exporters: [otlphttp,debug] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: allow-write-traces-dev-tenant-rbac +rules: +- apiGroups: [tempo.grafana.com] + resources: [dev] + resourceNames: [traces] + verbs: [create] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: allow-write-traces-dev-tenant-rbac +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: allow-write-traces-dev-tenant-rbac +subjects: +- kind: ServiceAccount + name: otel-collector-deployment + namespace: chainsaw-mmo-rbac diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-create-sas.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-create-sas.yaml new file mode 100644 index 0000000..28ac05b --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-create-sas.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tempo-rbac-sa-1 + namespace: chainsaw-mono-rbac-1 + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tempo-rbac-sa-2 + namespace: chainsaw-mono-rbac-2 + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: chainsaw-mono-rbac-1-admin + namespace: chainsaw-mono-rbac-1 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin +subjects: +- kind: ServiceAccount + name: tempo-rbac-sa-1 + namespace: chainsaw-mono-rbac-1 + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: chainsaw-mono-rbac-2-admin + namespace: chainsaw-mono-rbac-2 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin +subjects: +- kind: ServiceAccount + name: tempo-rbac-sa-2 + namespace: chainsaw-mono-rbac-2 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-kubeadmin-traces-verify.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-kubeadmin-traces-verify.yaml new file mode 100644 index 0000000..080b35a --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-kubeadmin-traces-verify.yaml @@ -0,0 +1,15 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-kubeadmin-grpc + namespace: chainsaw-mmo-rbac +status: + succeeded: 1 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-kubeadmin-http + namespace: chainsaw-mmo-rbac +status: + succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-1-traces-gen.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-1-traces-gen.yaml new file mode 100644 index 0000000..06d42a2 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-1-traces-gen.yaml @@ -0,0 +1,15 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-grpc-sa-1 + namespace: chainsaw-mono-rbac-1 +status: + succeeded: 1 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-http-sa-1 + namespace: chainsaw-mono-rbac-1 +status: + succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-1-traces-verify.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-1-traces-verify.yaml new file mode 100644 index 0000000..0ec3a1e --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-1-traces-verify.yaml @@ -0,0 +1,15 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-traceql-grpc-sa-1 + namespace: chainsaw-mono-rbac-1 +status: + succeeded: 1 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-traceql-http-sa-1 + namespace: chainsaw-mono-rbac-1 +status: + succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-2-traces-gen.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-2-traces-gen.yaml new file mode 100644 index 0000000..8079cc8 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/assert-tempo-rbac-sa-2-traces-gen.yaml @@ -0,0 +1,15 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-grpc-sa-2 + namespace: chainsaw-mono-rbac-2 +status: + succeeded: 1 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-http-sa-2 + namespace: chainsaw-mono-rbac-2 +status: + succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/chainsaw-test.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/chainsaw-test.yaml new file mode 100644 index 0000000..5fcc0ac --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/chainsaw-test.yaml @@ -0,0 +1,49 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: monolithic-multitenancy-rbac +spec: + namespace: chainsaw-mmo-rbac + steps: + - name: step-01 + try: + - apply: + file: 01-install-tempo.yaml + - assert: + file: 01-assert.yaml + - name: step-02 + try: + - apply: + file: 02-install-otelcol.yaml + - assert: + file: 02-assert.yaml + - name: Create non-admin SAs with namespace level access + try: + - apply: + file: create-SAs-with-namespace-access.yaml + - assert: + file: assert-create-sas.yaml + - name: Generate traces from namespace chainsaw-mono-rbac-1 + try: + - apply: + file: tempo-rbac-sa-1-traces-gen.yaml + - assert: + file: assert-tempo-rbac-sa-1-traces-gen.yaml + - name: Generate traces from namespace chainsaw-mono-rbac-2 + try: + - apply: + file: tempo-rbac-sa-2-traces-gen.yaml + - assert: + file: assert-tempo-rbac-sa-2-traces-gen.yaml + - name: Assert tracess using RBAC + try: + - apply: + file: tempo-rbac-sa-1-traces-verify.yaml + - assert: + file: assert-tempo-rbac-sa-1-traces-verify.yaml + - name: Verify kubeadmin can view traces from all projects + try: + - apply: + file: kubeadmin-traces-verify.yaml + - assert: + file: assert-kubeadmin-traces-verify.yaml diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/create-SAs-with-namespace-access.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/create-SAs-with-namespace-access.yaml new file mode 100644 index 0000000..ecd013d --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/create-SAs-with-namespace-access.yaml @@ -0,0 +1,91 @@ +apiVersion: project.openshift.io/v1 +kind: Project +metadata: + name: chainsaw-mono-rbac-1 +spec: {} + +--- +apiVersion: project.openshift.io/v1 +kind: Project +metadata: + name: chainsaw-mono-rbac-2 +spec: {} + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tempo-rbac-sa-1 + namespace: chainsaw-mono-rbac-1 + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tempo-rbac-sa-2 + namespace: chainsaw-mono-rbac-2 + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tempo-rbac-cluster-admin + namespace: chainsaw-mmo-rbac + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: chainsaw-mono-rbac-1-admin + namespace: chainsaw-mono-rbac-1 +subjects: + - kind: ServiceAccount + name: tempo-rbac-sa-1 + namespace: chainsaw-mono-rbac-1 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: chainsaw-mono-rbac-2-admin + namespace: chainsaw-mono-rbac-2 +subjects: + - kind: ServiceAccount + name: tempo-rbac-sa-2 + namespace: chainsaw-mono-rbac-2 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin + +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: tempo-rbac-cluster-admin-binding-monolithic +subjects: + - kind: ServiceAccount + name: tempo-rbac-cluster-admin + namespace: chainsaw-mmo-rbac +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: chainsaw-test-rbac-1-testuser + namespace: chainsaw-mono-rbac-1 +subjects: + - kind: User + name: testuser-1 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/kubeadmin-traces-verify.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/kubeadmin-traces-verify.yaml new file mode 100644 index 0000000..a5d31da --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/kubeadmin-traces-verify.yaml @@ -0,0 +1,201 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-kubeadmin-grpc + namespace: chainsaw-mmo-rbac +spec: + template: + spec: + serviceAccountName: tempo-rbac-cluster-admin + containers: + - name: verify-traces + image: ghcr.io/grafana/tempo-operator/test-utils:main + command: + - /bin/bash + - -eux + - -c + args: + - | + # Get the cluster-admin service account token + token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + + # First, verify traces from chainsaw-mono-rbac-1 (grpc-rbac-1 service) + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-1" }' \ + | tee /tmp/jaeger-rbac-1.out + num_traces=$(jq ".traces | length" /tmp/jaeger-rbac-1.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces for grpc-rbac-1." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-1" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output - cluster-admin should see complete traces" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Cluster-admin: Trace output for service grpc-rbac-1 contains: $searchString" + else + echo "Cluster-admin: Trace output for service grpc-rbac-1 does not contain: $searchString" + exit 1 + fi + done + + # Now verify traces from chainsaw-mono-rbac-2 (grpc-rbac-2 service) + # cluster-admin should be able to see complete traces from this project too + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-2" }' \ + | tee /tmp/jaeger-rbac-2.out + num_traces=$(jq ".traces | length" /tmp/jaeger-rbac-2.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces for grpc-rbac-2." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-2" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output - cluster-admin should see complete traces" + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Cluster-admin: Trace output for service grpc-rbac-2 contains: $searchString" + else + echo "Cluster-admin: Trace output for service grpc-rbac-2 does not contain: $searchString" + exit 1 + fi + done + restartPolicy: Never +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-kubeadmin-http + namespace: chainsaw-mmo-rbac +spec: + template: + spec: + serviceAccountName: tempo-rbac-cluster-admin + containers: + - name: verify-traces + image: ghcr.io/grafana/tempo-operator/test-utils:main + command: + - /bin/bash + - -eux + - -c + args: + - | + # Get the cluster-admin service account token + token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + + # First, verify traces from chainsaw-mono-rbac-1 (http-rbac-1 service) + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-1" }' \ + | tee /tmp/jaeger-rbac-1.out + num_traces=$(jq ".traces | length" /tmp/jaeger-rbac-1.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces for http-rbac-1." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-1" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output - cluster-admin should see complete traces" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Cluster-admin: Trace output for service http-rbac-1 contains: $searchString" + else + echo "Cluster-admin: Trace output for service http-rbac-1 does not contain: $searchString" + exit 1 + fi + done + + # Now verify traces from chainsaw-mono-rbac-2 (http-rbac-2 service) + # cluster-admin should be able to see complete traces from this project too + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-2" }' \ + | tee /tmp/jaeger-rbac-2.out + num_traces=$(jq ".traces | length" /tmp/jaeger-rbac-2.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces for http-rbac-2." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-2" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output - cluster-admin should see complete traces" + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Cluster-admin: Trace output for service http-rbac-2 contains: $searchString" + else + echo "Cluster-admin: Trace output for service http-rbac-2 does not contain: $searchString" + exit 1 + fi + done + restartPolicy: Never \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-1-traces-gen.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-1-traces-gen.yaml new file mode 100644 index 0000000..f8bb17f --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-1-traces-gen.yaml @@ -0,0 +1,42 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-grpc-sa-1 + namespace: chainsaw-mono-rbac-1 +spec: + template: + spec: + containers: + - name: telemetrygen + image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 + args: + - traces + - --otlp-endpoint=dev-collector.chainsaw-mmo-rbac.svc:4317 + - --service=grpc-rbac-1 + - --otlp-insecure + - --traces=2 + - --otlp-attributes=k8s.container.name="telemetrygen" + - --otlp-attributes=k8s.namespace.name="chainsaw-mono-rbac-1" + restartPolicy: Never +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-http-sa-1 + namespace: chainsaw-mono-rbac-1 +spec: + template: + spec: + containers: + - name: telemetrygen + image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 + args: + - traces + - --otlp-endpoint=dev-collector.chainsaw-mmo-rbac.svc:4318 + - --otlp-http + - --otlp-insecure + - --service=http-rbac-1 + - --traces=2 + - --otlp-attributes=k8s.container.name="telemetrygen" + - --otlp-attributes=k8s.namespace.name="chainsaw-mono-rbac-1" + restartPolicy: Never diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-1-traces-verify.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-1-traces-verify.yaml new file mode 100644 index 0000000..f8fb3dd --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-1-traces-verify.yaml @@ -0,0 +1,207 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-traceql-grpc-sa-1 + namespace: chainsaw-mono-rbac-1 +spec: + template: + spec: + serviceAccountName: tempo-rbac-sa-1 + containers: + - name: verify-traces + image: ghcr.io/grafana/tempo-operator/test-utils:main + command: + - /bin/bash + - -eux + - -c + args: + - | + token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + curl \ + -v -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-1" }' \ + | tee /tmp/jaeger.out + num_traces=$(jq ".traces | length" /tmp/jaeger.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-1" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Trace output for service grpc-rbac-1 contains: $searchString" + else + echo "Trace output for service grpc-rbac-1 does not contain: $searchString" + exit 1 + fi + done + + curl \ + -v -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-2" }' \ + | tee /tmp/jaeger.out + num_traces=$(jq ".traces | length" /tmp/jaeger.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-2" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Trace output for service grpc-rbac-2 contains: $searchString" + exit 1 + else + echo "Trace output for service grpc-rbac-2 does not contain: $searchString" + fi + done + restartPolicy: Never +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-traceql-http-sa-1 + namespace: chainsaw-mono-rbac-1 +spec: + template: + spec: + serviceAccountName: tempo-rbac-sa-1 + containers: + - name: verify-traces + image: ghcr.io/grafana/tempo-operator/test-utils:main + command: + - /bin/bash + - -eux + - -c + args: + - | + token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + curl \ + -v -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-1" }' \ + | tee /tmp/jaeger.out + num_traces=$(jq ".traces | length" /tmp/jaeger.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-1" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Trace output for service http-rbac-1 contains: $searchString" + else + echo "Trace output for service http-rbac-1 does not contain: $searchString" + exit 1 + fi + done + + curl \ + -v -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-2" }' \ + | tee /tmp/jaeger.out + num_traces=$(jq ".traces | length" /tmp/jaeger.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-2" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-mmo-rbac-gateway.chainsaw-mmo-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Trace output for service http-rbac-2 contains: $searchString" + exit 1 + else + echo "Trace output for service http-rbac-2 does not contain: $searchString" + fi + done + restartPolicy: Never diff --git a/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-2-traces-gen.yaml b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-2-traces-gen.yaml new file mode 100644 index 0000000..85f5a4c --- /dev/null +++ b/tests/fixtures/chainsaw-tests/monolithic-multitenancy-rbac/tempo-rbac-sa-2-traces-gen.yaml @@ -0,0 +1,42 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-grpc-sa-2 + namespace: chainsaw-mono-rbac-2 +spec: + template: + spec: + containers: + - name: telemetrygen + image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 + args: + - traces + - --otlp-endpoint=dev-collector.chainsaw-mmo-rbac.svc:4317 + - --service=grpc-rbac-2 + - --otlp-insecure + - --traces=2 + - --otlp-attributes=k8s.container.name="telemetrygen" + - --otlp-attributes=k8s.namespace.name="chainsaw-mono-rbac-2" + restartPolicy: Never +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-http-sa-2 + namespace: chainsaw-mono-rbac-2 +spec: + template: + spec: + containers: + - name: telemetrygen + image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 + args: + - traces + - --otlp-endpoint=dev-collector.chainsaw-mmo-rbac.svc:4318 + - --otlp-http + - --otlp-insecure + - --service=http-rbac-2 + - --traces=2 + - --otlp-attributes=k8s.container.name="telemetrygen" + - --otlp-attributes=k8s.namespace.name="chainsaw-mono-rbac-2" + restartPolicy: Never diff --git a/tests/fixtures/chainsaw-tests/multitenancy/00-assert.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/00-assert.yaml similarity index 71% rename from tests/fixtures/chainsaw-tests/multitenancy/00-assert.yaml rename to tests/fixtures/chainsaw-tests/multitenancy-rbac/00-assert.yaml index 4854234..c4aa69f 100644 --- a/tests/fixtures/chainsaw-tests/multitenancy/00-assert.yaml +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/00-assert.yaml @@ -2,6 +2,6 @@ apiVersion: apps/v1 kind: Deployment metadata: name: minio - namespace: chainsaw-multitenancy + namespace: chainsaw-rbac status: readyReplicas: 1 diff --git a/tests/fixtures/chainsaw-tests/multitenancy/00-install-storage.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/00-install-storage.yaml similarity index 86% rename from tests/fixtures/chainsaw-tests/multitenancy/00-install-storage.yaml rename to tests/fixtures/chainsaw-tests/multitenancy-rbac/00-install-storage.yaml index 8dcbb1f..a48ae90 100644 --- a/tests/fixtures/chainsaw-tests/multitenancy/00-install-storage.yaml +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/00-install-storage.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Namespace metadata: - name: chainsaw-multitenancy + name: chainsaw-rbac --- apiVersion: v1 kind: PersistentVolumeClaim @@ -10,19 +10,19 @@ metadata: labels: app.kubernetes.io/name: minio name: minio - namespace: chainsaw-multitenancy + namespace: chainsaw-rbac spec: accessModes: - ReadWriteOnce resources: requests: - storage: 2Gi + storage: 1Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: minio - namespace: chainsaw-multitenancy + namespace: chainsaw-rbac spec: selector: matchLabels: @@ -46,7 +46,7 @@ spec: value: tempo - name: MINIO_SECRET_KEY value: supersecret - image: quay.io/minio/minio:latest + image: quay.io/minio/minio:RELEASE.2024-10-02T17-50-41Z name: minio ports: - containerPort: 9000 @@ -62,7 +62,7 @@ apiVersion: v1 kind: Service metadata: name: minio - namespace: chainsaw-multitenancy + namespace: chainsaw-rbac spec: ports: - port: 9000 @@ -76,7 +76,7 @@ apiVersion: v1 kind: Secret metadata: name: minio - namespace: chainsaw-multitenancy + namespace: chainsaw-rbac stringData: endpoint: http://minio:9000 bucket: tempo diff --git a/tests/fixtures/chainsaw-tests/multitenancy/01-assert.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/01-assert.yaml similarity index 54% rename from tests/fixtures/chainsaw-tests/multitenancy/01-assert.yaml rename to tests/fixtures/chainsaw-tests/multitenancy-rbac/01-assert.yaml index 2666b66..ac896e4 100644 --- a/tests/fixtures/chainsaw-tests/multitenancy/01-assert.yaml +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/01-assert.yaml @@ -6,17 +6,17 @@ kind: Secret metadata: labels: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo - name: tempo-simplest-gateway - namespace: chainsaw-multitenancy + name: tempo-simplst-gateway + namespace: chainsaw-rbac ownerReferences: - apiVersion: tempo.grafana.com/v1alpha1 blockOwnerDeletion: true controller: true kind: TempoStack - name: simplest + name: simplst type: Opaque --- apiVersion: v1 @@ -26,68 +26,67 @@ kind: ConfigMap metadata: labels: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo - name: tempo-simplest-gateway - namespace: chainsaw-multitenancy + name: tempo-simplst-gateway + namespace: chainsaw-rbac ownerReferences: - apiVersion: tempo.grafana.com/v1alpha1 blockOwnerDeletion: true controller: true kind: TempoStack - name: simplest + name: simplst --- apiVersion: v1 kind: ConfigMap metadata: labels: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo annotations: service.beta.openshift.io/inject-cabundle: "true" - name: tempo-simplest-gateway-cabundle - namespace: chainsaw-multitenancy + name: tempo-simplst-gateway-cabundle + namespace: chainsaw-rbac ownerReferences: - apiVersion: tempo.grafana.com/v1alpha1 blockOwnerDeletion: true controller: true kind: TempoStack - name: simplest + name: simplst --- apiVersion: v1 automountServiceAccountToken: true kind: ServiceAccount metadata: annotations: - serviceaccounts.openshift.io/oauth-redirectreference.dev: '{"kind":"OAuthRedirectReference","apiVersion":"v1","reference":{"kind":"Route","name":"tempo-simplest-gateway"}}' - serviceaccounts.openshift.io/oauth-redirectreference.prod: '{"kind":"OAuthRedirectReference","apiVersion":"v1","reference":{"kind":"Route","name":"tempo-simplest-gateway"}}' + serviceaccounts.openshift.io/oauth-redirectreference.dev: '{"kind":"OAuthRedirectReference","apiVersion":"v1","reference":{"kind":"Route","name":"tempo-simplst-gateway"}}' + serviceaccounts.openshift.io/oauth-redirectreference.prod: '{"kind":"OAuthRedirectReference","apiVersion":"v1","reference":{"kind":"Route","name":"tempo-simplst-gateway"}}' labels: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo - name: tempo-simplest-gateway - namespace: chainsaw-multitenancy + name: tempo-simplst-gateway + namespace: chainsaw-rbac ownerReferences: - apiVersion: tempo.grafana.com/v1alpha1 blockOwnerDeletion: true controller: true kind: TempoStack - name: simplest + name: simplst --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo - app.kubernetes.io/namespace: chainsaw-multitenancy - name: tempo-simplest-gateway-chainsaw-multitenancy + name: tempo-simplst-gateway-chainsaw-rbac rules: - apiGroups: - authentication.k8s.io @@ -107,43 +106,40 @@ kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo - app.kubernetes.io/namespace: chainsaw-multitenancy - name: tempo-simplest-gateway-chainsaw-multitenancy + name: tempo-simplst-gateway-chainsaw-rbac roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: tempo-simplest-gateway-chainsaw-multitenancy + name: tempo-simplst-gateway-chainsaw-rbac subjects: - kind: ServiceAccount - name: tempo-simplest-gateway - namespace: chainsaw-multitenancy + name: tempo-simplst-gateway --- apiVersion: apps/v1 kind: Deployment metadata: labels: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo - name: tempo-simplest-gateway - namespace: chainsaw-multitenancy + name: tempo-simplst-gateway + namespace: chainsaw-rbac spec: - replicas: 1 selector: matchLabels: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo template: metadata: labels: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo spec: @@ -152,10 +148,10 @@ spec: - --traces.tenant-header=x-scope-orgid - --web.listen=0.0.0.0:8080 - --web.internal.listen=0.0.0.0:8081 - - --traces.write.otlpgrpc.endpoint=tempo-simplest-distributor.chainsaw-multitenancy.svc.cluster.local:4317 - - --traces.write.otlphttp.endpoint=https://tempo-simplest-distributor.chainsaw-multitenancy.svc.cluster.local:4318 + - --traces.write.otlpgrpc.endpoint=tempo-simplst-distributor.chainsaw-rbac.svc.cluster.local:4317 + - --traces.write.otlphttp.endpoint=https://tempo-simplst-distributor.chainsaw-rbac.svc.cluster.local:4318 - --traces.write-timeout=30s - - --traces.tempo.endpoint=https://tempo-simplest-query-frontend.chainsaw-multitenancy.svc.cluster.local:3200 + - --traces.tempo.endpoint=https://tempo-simplst-query-frontend.chainsaw-rbac.svc.cluster.local:3200 - --grpc.listen=0.0.0.0:8090 - --rbac.config=/etc/tempo-gateway/cm/rbac.yaml - --tenants.config=/etc/tempo-gateway/secret/tenants.yaml @@ -169,19 +165,10 @@ spec: - --tls.server.cert-file=/etc/tempo-gateway/serving-certs/tls.crt - --tls.server.key-file=/etc/tempo-gateway/serving-certs/tls.key - --tls.healthchecks.server-ca-file=/etc/tempo-gateway/cabundle/service-ca.crt - - --tls.healthchecks.server-name=tempo-simplest-gateway.chainsaw-multitenancy.svc.cluster.local + - --tls.healthchecks.server-name=tempo-simplst-gateway.chainsaw-rbac.svc.cluster.local - --web.healthchecks.url=https://localhost:8080 - --tls.client-auth-type=NoClientCert - - --traces.read.endpoint=https://tempo-simplest-query-frontend.chainsaw-multitenancy.svc.cluster.local:16686 - livenessProbe: - failureThreshold: 10 - httpGet: - path: /live - port: internal - scheme: HTTPS - periodSeconds: 30 - successThreshold: 1 - timeoutSeconds: 2 + - --traces.query-rbac=true name: tempo-gateway ports: - containerPort: 8090 @@ -193,65 +180,15 @@ spec: - containerPort: 8080 name: public protocol: TCP - readinessProbe: - failureThreshold: 12 - httpGet: - path: /ready - port: internal - scheme: HTTPS - periodSeconds: 5 - successThreshold: 1 - timeoutSeconds: 1 - resources: - limits: - cpu: 120m - memory: "161061280" - requests: - cpu: 36m - memory: "48318384" - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - volumeMounts: - - mountPath: /etc/tempo-gateway/cm - name: rbac - readOnly: true - - mountPath: /etc/tempo-gateway/secret/tenants.yaml - name: tenant - readOnly: true - subPath: tenants.yaml - - mountPath: /var/run/ca - name: tempo-simplest-ca-bundle - - mountPath: /var/run/tls/server - name: tempo-simplest-gateway-mtls - - mountPath: /etc/tempo-gateway/serving-certs - name: serving-certs - readOnly: true - - mountPath: /etc/tempo-gateway/cabundle - name: cabundle - readOnly: true - args: - --log.level=warn - --web.listen=:8082 - --web.internal.listen=:8083 - --web.healthchecks.url=http://localhost:8082 - --opa.package=tempostack + - --opa.matcher=kubernetes_namespace_name - --openshift.mappings=dev=tempo.grafana.com - --openshift.mappings=prod=tempo.grafana.com - livenessProbe: - failureThreshold: 10 - httpGet: - path: /live - port: 8083 - scheme: HTTP - periodSeconds: 30 - successThreshold: 1 - timeoutSeconds: 2 name: tempo-gateway-opa ports: - containerPort: 8082 @@ -260,52 +197,6 @@ spec: - containerPort: 8083 name: opa-metrics protocol: TCP - readinessProbe: - failureThreshold: 12 - httpGet: - path: /ready - port: 8083 - scheme: HTTP - periodSeconds: 5 - successThreshold: 1 - timeoutSeconds: 1 - resources: {} - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - serviceAccount: tempo-simplest-gateway - serviceAccountName: tempo-simplest-gateway - terminationGracePeriodSeconds: 30 - volumes: - - configMap: - defaultMode: 420 - items: - - key: rbac.yaml - path: rbac.yaml - name: tempo-simplest-gateway - name: rbac - - name: tenant - secret: - defaultMode: 420 - items: - - key: tenants.yaml - path: tenants.yaml - secretName: tempo-simplest-gateway - - configMap: - defaultMode: 420 - name: tempo-simplest-ca-bundle - name: tempo-simplest-ca-bundle - - name: tempo-simplest-gateway-mtls - secret: - defaultMode: 420 - secretName: tempo-simplest-gateway-mtls - - name: serving-certs - secret: - defaultMode: 420 - secretName: tempo-simplest-gateway-tls - - configMap: - defaultMode: 420 - name: tempo-simplest-gateway-cabundle - name: cabundle status: availableReplicas: 1 readyReplicas: 1 @@ -316,17 +207,17 @@ kind: Route metadata: labels: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo - name: tempo-simplest-gateway - namespace: chainsaw-multitenancy + name: tempo-simplst-gateway + namespace: chainsaw-rbac ownerReferences: - apiVersion: tempo.grafana.com/v1alpha1 blockOwnerDeletion: true controller: true kind: TempoStack - name: simplest + name: simplst spec: port: targetPort: public @@ -334,7 +225,7 @@ spec: termination: reencrypt to: kind: Service - name: tempo-simplest-gateway + name: tempo-simplst-gateway weight: 100 wildcardPolicy: None --- @@ -342,20 +233,20 @@ apiVersion: v1 kind: Service metadata: annotations: - service.beta.openshift.io/serving-cert-secret-name: tempo-simplest-gateway-tls + service.beta.openshift.io/serving-cert-secret-name: tempo-simplst-gateway-tls labels: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo - name: tempo-simplest-gateway - namespace: chainsaw-multitenancy + name: tempo-simplst-gateway + namespace: chainsaw-rbac ownerReferences: - apiVersion: tempo.grafana.com/v1alpha1 blockOwnerDeletion: true controller: true kind: TempoStack - name: simplest + name: simplst spec: ports: - name: grpc-public @@ -372,7 +263,7 @@ spec: targetPort: public selector: app.kubernetes.io/component: gateway - app.kubernetes.io/instance: simplest + app.kubernetes.io/instance: simplst app.kubernetes.io/managed-by: tempo-operator app.kubernetes.io/name: tempo type: ClusterIP @@ -380,39 +271,39 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: tempo-simplest-compactor - namespace: chainsaw-multitenancy + name: tempo-simplst-compactor + namespace: chainsaw-rbac status: readyReplicas: 1 --- apiVersion: apps/v1 kind: Deployment metadata: - name: tempo-simplest-distributor - namespace: chainsaw-multitenancy + name: tempo-simplst-distributor + namespace: chainsaw-rbac status: readyReplicas: 1 --- apiVersion: apps/v1 kind: Deployment metadata: - name: tempo-simplest-querier - namespace: chainsaw-multitenancy + name: tempo-simplst-querier + namespace: chainsaw-rbac status: readyReplicas: 1 --- apiVersion: apps/v1 kind: Deployment metadata: - name: tempo-simplest-query-frontend - namespace: chainsaw-multitenancy + name: tempo-simplst-query-frontend + namespace: chainsaw-rbac status: readyReplicas: 1 --- apiVersion: apps/v1 kind: StatefulSet metadata: - name: tempo-simplest-ingester - namespace: chainsaw-multitenancy + name: tempo-simplst-ingester + namespace: chainsaw-rbac status: readyReplicas: 1 diff --git a/tests/fixtures/chainsaw-tests/multitenancy/01-install-tempo.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/01-install-tempo.yaml similarity index 61% rename from tests/fixtures/chainsaw-tests/multitenancy/01-install-tempo.yaml rename to tests/fixtures/chainsaw-tests/multitenancy-rbac/01-install-tempo.yaml index 2ce4748..85b9184 100644 --- a/tests/fixtures/chainsaw-tests/multitenancy/01-install-tempo.yaml +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/01-install-tempo.yaml @@ -1,23 +1,9 @@ -# based on config/samples/openshift/tempo_v1alpha1_multitenancy.yaml apiVersion: tempo.grafana.com/v1alpha1 kind: TempoStack metadata: - name: simplest - namespace: chainsaw-multitenancy + name: simplst + namespace: chainsaw-rbac spec: - retention: - global: - traces: 20h - perTenant: - dev: - traces: 10h - limits: - perTenant: - dev: - ingestion: - ingestionBurstSizeBytes: 1000000 - query: - maxSearchDuration: 1h storage: secret: name: minio @@ -26,7 +12,7 @@ spec: resources: total: limits: - memory: 3Gi + memory: 4Gi cpu: 2000m tenants: mode: openshift @@ -38,14 +24,13 @@ spec: template: gateway: enabled: true - queryFrontend: - jaegerQuery: + rbac: enabled: true --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: tempostack-traces-reader + name: tempostack-traces-reader-rbac rules: - apiGroups: - 'tempo.grafana.com' @@ -59,23 +44,21 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: tempostack-traces-reader + name: tempostack-traces-reader-rbac roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: tempostack-traces-reader + name: tempostack-traces-reader-rbac subjects: - kind: Group apiGroup: rbac.authorization.k8s.io name: system:authenticated --- -# grant the default serviceaccount in the chainsaw-multitenancy namespace -# access to view resource in chainsaw-multitenancy namespace apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: view - namespace: chainsaw-multitenancy + namespace: chainsaw-rbac roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole @@ -83,4 +66,4 @@ roleRef: subjects: - kind: ServiceAccount name: default - namespace: chainsaw-multitenancy + namespace: chainsaw-rbac diff --git a/tests/fixtures/chainsaw-tests/multitenancy/02-assert.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/02-assert.yaml similarity index 74% rename from tests/fixtures/chainsaw-tests/multitenancy/02-assert.yaml rename to tests/fixtures/chainsaw-tests/multitenancy-rbac/02-assert.yaml index ac725d5..6115475 100644 --- a/tests/fixtures/chainsaw-tests/multitenancy/02-assert.yaml +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/02-assert.yaml @@ -3,6 +3,6 @@ apiVersion: apps/v1 kind: Deployment metadata: name: dev-collector - namespace: chainsaw-multitenancy + namespace: chainsaw-rbac status: readyReplicas: 1 diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/02-install-otelcol.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/02-install-otelcol.yaml new file mode 100644 index 0000000..496526d --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/02-install-otelcol.yaml @@ -0,0 +1,139 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: otel-collector-deployment + namespace: chainsaw-rbac + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: chainsaw-rbac-clusterrole +rules: +- apiGroups: [""] + resources: ["pods", "namespaces", "nodes"] + verbs: ["get", "watch", "list"] +- apiGroups: ["apps"] + resources: ["replicasets"] + verbs: ["get", "list", "watch"] +- apiGroups: ["extensions"] + resources: ["replicasets"] + verbs: ["get", "list", "watch"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: chainsaw-rbac-clusterrole-binding +subjects: +- kind: ServiceAccount + name: otel-collector-deployment + namespace: chainsaw-rbac +roleRef: + kind: ClusterRole + name: chainsaw-rbac-clusterrole + apiGroup: rbac.authorization.k8s.io + +--- +apiVersion: opentelemetry.io/v1alpha1 +kind: OpenTelemetryCollector +metadata: + name: dev + namespace: chainsaw-rbac +spec: + serviceAccount: otel-collector-deployment + config: | + extensions: + bearertokenauth: + filename: "/var/run/secrets/kubernetes.io/serviceaccount/token" + + receivers: + otlp/grpc: + protocols: + grpc: + otlp/http: + protocols: + http: + + processors: + k8sattributes: + extract: + metadata: + - k8s.pod.name + - k8s.pod.uid + - k8s.deployment.name + - k8s.namespace.name + - k8s.node.name + pod_association: + - sources: + - from: resource_attribute + name: k8s.pod.ip + - sources: + - from: connection + + exporters: + debug: + verbosity: detailed + otlp: + endpoint: tempo-simplst-gateway.chainsaw-rbac.svc.cluster.local:8090 + tls: + insecure: false + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt" + auth: + authenticator: bearertokenauth + headers: + X-Scope-OrgID: "dev" + otlphttp: + endpoint: https://tempo-simplst-gateway.chainsaw-rbac.svc.cluster.local:8080/api/traces/v1/dev + tls: + insecure: false + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt" + auth: + authenticator: bearertokenauth + headers: + X-Scope-OrgID: "dev" + + service: + telemetry: + logs: + level: "DEBUG" + development: true + extensions: [bearertokenauth] + pipelines: + traces/grpc: + receivers: [otlp/grpc] + processors: [k8sattributes] + exporters: [otlp,debug] + traces/http: + receivers: [otlp/http] + processors: [k8sattributes] + exporters: [otlphttp,debug] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: tempostack-traces-write-rbac +rules: + - apiGroups: + - 'tempo.grafana.com' + # this needs to match tenant name in the CR/tenants.yaml and the tenant has be sent in X-Scope-OrgID + # The API gateway sends the tenantname as resource (res) to OPA sidecar + resources: + - dev + resourceNames: + - traces + verbs: + - 'create' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: tempostack-traces-rbac +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: tempostack-traces-write-rbac +subjects: + - kind: ServiceAccount + name: otel-collector-deployment + namespace: chainsaw-rbac diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-create-sas.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-create-sas.yaml new file mode 100644 index 0000000..c318323 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-create-sas.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tempo-rbac-sa-1 + namespace: chainsaw-test-rbac-1 + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tempo-rbac-sa-2 + namespace: chainsaw-test-rbac-2 + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: chainsaw-test-rbac-1-admin + namespace: chainsaw-test-rbac-1 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin +subjects: +- kind: ServiceAccount + name: tempo-rbac-sa-1 + namespace: chainsaw-test-rbac-1 + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: chainsaw-test-rbac-2-admin + namespace: chainsaw-test-rbac-2 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin +subjects: +- kind: ServiceAccount + name: tempo-rbac-sa-2 + namespace: chainsaw-test-rbac-2 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-kubeadmin-traces-verify.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-kubeadmin-traces-verify.yaml new file mode 100644 index 0000000..ffc45b3 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-kubeadmin-traces-verify.yaml @@ -0,0 +1,15 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-kubeadmin-grpc + namespace: chainsaw-rbac +status: + succeeded: 1 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-kubeadmin-http + namespace: chainsaw-rbac +status: + succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-1-traces-gen.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-1-traces-gen.yaml new file mode 100644 index 0000000..2bcaff2 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-1-traces-gen.yaml @@ -0,0 +1,15 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-grpc-sa-1 + namespace: chainsaw-test-rbac-1 +status: + succeeded: 1 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-http-sa-1 + namespace: chainsaw-test-rbac-1 +status: + succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-1-traces-verify.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-1-traces-verify.yaml new file mode 100644 index 0000000..fca3fa4 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-1-traces-verify.yaml @@ -0,0 +1,15 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-traceql-grpc-sa-1 + namespace: chainsaw-test-rbac-1 +status: + succeeded: 1 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-traceql-http-sa-1 + namespace: chainsaw-test-rbac-1 +status: + succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-2-traces-gen.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-2-traces-gen.yaml new file mode 100644 index 0000000..e7a0d51 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/assert-tempo-rbac-sa-2-traces-gen.yaml @@ -0,0 +1,15 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-grpc-sa-2 + namespace: chainsaw-test-rbac-2 +status: + succeeded: 1 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-http-sa-2 + namespace: chainsaw-test-rbac-2 +status: + succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/chainsaw-test.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/chainsaw-test.yaml new file mode 100755 index 0000000..5a0b118 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/chainsaw-test.yaml @@ -0,0 +1,55 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: multitenancy-rbac +spec: + namespace: chainsaw-rbac + steps: + - name: step-00 + try: + - apply: + file: 00-install-storage.yaml + - assert: + file: 00-assert.yaml + - name: step-01 + try: + - apply: + file: 01-install-tempo.yaml + - assert: + file: 01-assert.yaml + - name: step-02 + try: + - apply: + file: 02-install-otelcol.yaml + - assert: + file: 02-assert.yaml + - name: Create non-admin SAs with namespace level access + try: + - apply: + file: create-SAs-with-namespace-access.yaml + - assert: + file: assert-create-sas.yaml + - name: Generate traces from namespace chainsaw-test-rbac-1 + try: + - apply: + file: tempo-rbac-sa-1-traces-gen.yaml + - assert: + file: assert-tempo-rbac-sa-1-traces-gen.yaml + - name: Generate traces from namespace chainsaw-test-rbac-2 + try: + - apply: + file: tempo-rbac-sa-2-traces-gen.yaml + - assert: + file: assert-tempo-rbac-sa-2-traces-gen.yaml + - name: Assert tracess using RBAC + try: + - apply: + file: tempo-rbac-sa-1-traces-verify.yaml + - assert: + file: assert-tempo-rbac-sa-1-traces-verify.yaml + - name: Verify kubeadmin can view traces from all projects + try: + - apply: + file: kubeadmin-traces-verify.yaml + - assert: + file: assert-kubeadmin-traces-verify.yaml \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/create-SAs-with-namespace-access.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/create-SAs-with-namespace-access.yaml new file mode 100644 index 0000000..20ce77a --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/create-SAs-with-namespace-access.yaml @@ -0,0 +1,91 @@ +apiVersion: project.openshift.io/v1 +kind: Project +metadata: + name: chainsaw-test-rbac-1 +spec: {} + +--- +apiVersion: project.openshift.io/v1 +kind: Project +metadata: + name: chainsaw-test-rbac-2 +spec: {} + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tempo-rbac-sa-1 + namespace: chainsaw-test-rbac-1 + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tempo-rbac-sa-2 + namespace: chainsaw-test-rbac-2 + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tempo-rbac-cluster-admin + namespace: chainsaw-rbac + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: chainsaw-test-rbac-1-admin + namespace: chainsaw-test-rbac-1 +subjects: + - kind: ServiceAccount + name: tempo-rbac-sa-1 + namespace: chainsaw-test-rbac-1 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: chainsaw-test-rbac-2-admin + namespace: chainsaw-test-rbac-2 +subjects: + - kind: ServiceAccount + name: tempo-rbac-sa-2 + namespace: chainsaw-test-rbac-2 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin + +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: tempo-rbac-cluster-admin-binding +subjects: + - kind: ServiceAccount + name: tempo-rbac-cluster-admin + namespace: chainsaw-rbac +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: chainsaw-test-rbac-1-testuser + namespace: chainsaw-test-rbac-1 +subjects: + - kind: User + name: testuser-1 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/kubeadmin-traces-verify.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/kubeadmin-traces-verify.yaml new file mode 100644 index 0000000..57a62f6 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/kubeadmin-traces-verify.yaml @@ -0,0 +1,201 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-kubeadmin-grpc + namespace: chainsaw-rbac +spec: + template: + spec: + serviceAccountName: tempo-rbac-cluster-admin + containers: + - name: verify-traces + image: ghcr.io/grafana/tempo-operator/test-utils:main + command: + - /bin/bash + - -eux + - -c + args: + - | + # Get the cluster-admin service account token + token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + + # First, verify traces from chainsaw-test-rbac-1 (grpc-rbac-1 service) + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-1" }' \ + | tee /tmp/jaeger-rbac-1.out + num_traces=$(jq ".traces | length" /tmp/jaeger-rbac-1.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces for grpc-rbac-1." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-1" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output - cluster-admin should see complete traces" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Cluster-admin: Trace output for service grpc-rbac-1 contains: $searchString" + else + echo "Cluster-admin: Trace output for service grpc-rbac-1 does not contain: $searchString" + exit 1 + fi + done + + # Now verify traces from chainsaw-test-rbac-2 (grpc-rbac-2 service) + # cluster-admin should be able to see complete traces from this project too + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-2" }' \ + | tee /tmp/jaeger-rbac-2.out + num_traces=$(jq ".traces | length" /tmp/jaeger-rbac-2.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces for grpc-rbac-2." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-2" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output - cluster-admin should see complete traces" + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Cluster-admin: Trace output for service grpc-rbac-2 contains: $searchString" + else + echo "Cluster-admin: Trace output for service grpc-rbac-2 does not contain: $searchString" + exit 1 + fi + done + restartPolicy: Never +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-kubeadmin-http + namespace: chainsaw-rbac +spec: + template: + spec: + serviceAccountName: tempo-rbac-cluster-admin + containers: + - name: verify-traces + image: ghcr.io/grafana/tempo-operator/test-utils:main + command: + - /bin/bash + - -eux + - -c + args: + - | + # Get the cluster-admin service account token + token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + + # First, verify traces from chainsaw-test-rbac-1 (http-rbac-1 service) + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-1" }' \ + | tee /tmp/jaeger-rbac-1.out + num_traces=$(jq ".traces | length" /tmp/jaeger-rbac-1.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces for http-rbac-1." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-1" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output - cluster-admin should see complete traces" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Cluster-admin: Trace output for service http-rbac-1 contains: $searchString" + else + echo "Cluster-admin: Trace output for service http-rbac-1 does not contain: $searchString" + exit 1 + fi + done + + # Now verify traces from chainsaw-test-rbac-2 (http-rbac-2 service) + # cluster-admin should be able to see complete traces from this project too + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-2" }' \ + | tee /tmp/jaeger-rbac-2.out + num_traces=$(jq ".traces | length" /tmp/jaeger-rbac-2.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces for http-rbac-2." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-2" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output - cluster-admin should see complete traces" + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Cluster-admin: Trace output for service http-rbac-2 contains: $searchString" + else + echo "Cluster-admin: Trace output for service http-rbac-2 does not contain: $searchString" + exit 1 + fi + done + restartPolicy: Never \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-1-traces-gen.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-1-traces-gen.yaml new file mode 100644 index 0000000..d64e939 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-1-traces-gen.yaml @@ -0,0 +1,42 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-grpc-sa-1 + namespace: chainsaw-test-rbac-1 +spec: + template: + spec: + containers: + - name: telemetrygen + image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 + args: + - traces + - --otlp-endpoint=dev-collector.chainsaw-rbac.svc:4317 + - --service=grpc-rbac-1 + - --otlp-insecure + - --traces=2 + - --otlp-attributes=k8s.container.name="telemetrygen" + - --otlp-attributes=k8s.namespace.name="chainsaw-test-rbac-1" + restartPolicy: Never +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-http-sa-1 + namespace: chainsaw-test-rbac-1 +spec: + template: + spec: + containers: + - name: telemetrygen + image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 + args: + - traces + - --otlp-endpoint=dev-collector.chainsaw-rbac.svc:4318 + - --otlp-http + - --otlp-insecure + - --service=http-rbac-1 + - --traces=2 + - --otlp-attributes=k8s.container.name="telemetrygen" + - --otlp-attributes=k8s.namespace.name="chainsaw-test-rbac-1" + restartPolicy: Never diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-1-traces-verify.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-1-traces-verify.yaml new file mode 100644 index 0000000..bc0ed2f --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-1-traces-verify.yaml @@ -0,0 +1,208 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-traceql-grpc-sa-1 + namespace: chainsaw-test-rbac-1 +spec: + template: + spec: + serviceAccountName: tempo-rbac-sa-1 + containers: + - name: verify-traces + image: ghcr.io/grafana/tempo-operator/test-utils:main + command: + - /bin/bash + - -eux + - -c + args: + - | + token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-1" }' \ + | tee /tmp/jaeger.out + num_traces=$(jq ".traces | length" /tmp/jaeger.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-1" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Trace output for service grpc-rbac-1 contains: $searchString" + else + echo "Trace output for service grpc-rbac-1 does not contain: $searchString" + exit 1 + fi + done + + + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-2" }' \ + | tee /tmp/jaeger.out + num_traces=$(jq ".traces | length" /tmp/jaeger.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="grpc-rbac-2" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Trace output for service grpc-rbac-2 contains: $searchString" + exit 1 + else + echo "Trace output for service grpc-rbac-2 does not contain: $searchString" + fi + done + restartPolicy: Never +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: verify-traces-traceql-http-sa-1 + namespace: chainsaw-test-rbac-1 +spec: + template: + spec: + serviceAccountName: tempo-rbac-sa-1 + containers: + - name: verify-traces + image: ghcr.io/grafana/tempo-operator/test-utils:main + command: + - /bin/bash + - -eux + - -c + args: + - | + token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-1" }' \ + | tee /tmp/jaeger.out + num_traces=$(jq ".traces | length" /tmp/jaeger.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-1" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Trace output for service http-rbac-1 contains: $searchString" + else + echo "Trace output for service http-rbac-1 does not contain: $searchString" + exit 1 + fi + done + + curl \ + -G \ + --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-2" }' \ + | tee /tmp/jaeger.out + num_traces=$(jq ".traces | length" /tmp/jaeger.out) + if [[ "$num_traces" != "2" ]]; then + echo && echo "The Jaeger API returned $num_traces instead of 2 traces." + exit 1 + fi + + echo "Fetch the first trace ID and store it in a variable" + traceID=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/search \ + --data-urlencode 'q={ resource.service.name="http-rbac-2" }' | jq -r '.traces[0].traceID') + + echo "Use the trace ID to fetch the complete trace" + traceOutput=$(curl -G --header "Authorization: Bearer $token" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ + https://tempo-simplst-gateway.chainsaw-rbac.svc:8080/api/traces/v1/dev/tempo/api/traces/$traceID) + + echo "Check for the strings in the trace output" + stringsToSearch=( + "\"key\":\"net.peer.ip\"" + "\"stringValue\":\"1.2.3.4\"" + "\"key\":\"peer.service\"" + "\"stringValue\":\"telemetrygen-client\"" + "\"key\":\"k8s.pod.ip\"" + "\"key\":\"k8s.container.name\"" + ) + for searchString in "${stringsToSearch[@]}"; do + if echo "$traceOutput" | grep -q "$searchString"; then + echo "Trace output for service http-rbac-2 contains: $searchString" + exit 1 + else + echo "Trace output for service http-rbac-2 does not contain: $searchString" + fi + done + restartPolicy: Never diff --git a/tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-2-traces-gen.yaml b/tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-2-traces-gen.yaml new file mode 100644 index 0000000..3633d50 --- /dev/null +++ b/tests/fixtures/chainsaw-tests/multitenancy-rbac/tempo-rbac-sa-2-traces-gen.yaml @@ -0,0 +1,42 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-grpc-sa-2 + namespace: chainsaw-test-rbac-2 +spec: + template: + spec: + containers: + - name: telemetrygen + image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 + args: + - traces + - --otlp-endpoint=dev-collector.chainsaw-rbac.svc:4317 + - --service=grpc-rbac-2 + - --otlp-insecure + - --traces=2 + - --otlp-attributes=k8s.container.name="telemetrygen" + - --otlp-attributes=k8s.namespace.name="chainsaw-test-rbac-2" + restartPolicy: Never +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: generate-traces-http-sa-2 + namespace: chainsaw-test-rbac-2 +spec: + template: + spec: + containers: + - name: telemetrygen + image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 + args: + - traces + - --otlp-endpoint=dev-collector.chainsaw-rbac.svc:4318 + - --otlp-http + - --otlp-insecure + - --service=http-rbac-2 + - --traces=2 + - --otlp-attributes=k8s.container.name="telemetrygen" + - --otlp-attributes=k8s.namespace.name="chainsaw-test-rbac-2" + restartPolicy: Never diff --git a/tests/fixtures/chainsaw-tests/multitenancy/02-install-otelcol.yaml b/tests/fixtures/chainsaw-tests/multitenancy/02-install-otelcol.yaml deleted file mode 100644 index b555512..0000000 --- a/tests/fixtures/chainsaw-tests/multitenancy/02-install-otelcol.yaml +++ /dev/null @@ -1,86 +0,0 @@ -# based on config/samples/otelcol_v1alpha1_openshift.yaml ---- -apiVersion: opentelemetry.io/v1alpha1 -kind: OpenTelemetryCollector -metadata: - name: dev - namespace: chainsaw-multitenancy -spec: - config: | - extensions: - bearertokenauth: - filename: "/var/run/secrets/kubernetes.io/serviceaccount/token" - - receivers: - otlp/grpc: - protocols: - grpc: - otlp/http: - protocols: - http: - - processors: - - exporters: - otlp: - endpoint: tempo-simplest-gateway.chainsaw-multitenancy.svc.cluster.local:8090 - tls: - insecure: false - ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt" - auth: - authenticator: bearertokenauth - headers: - X-Scope-OrgID: "dev" - otlphttp: - endpoint: https://tempo-simplest-gateway.chainsaw-multitenancy.svc.cluster.local:8080/api/traces/v1/dev - tls: - insecure: false - ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt" - auth: - authenticator: bearertokenauth - headers: - X-Scope-OrgID: "dev" - - service: - telemetry: - logs: - level: "DEBUG" - development: true - encoding: "json" - extensions: [bearertokenauth] - pipelines: - traces/grpc: - receivers: [otlp/grpc] - exporters: [otlp] - traces/http: - receivers: [otlp/http] - exporters: [otlphttp] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: tempostack-traces-write -rules: - - apiGroups: - - 'tempo.grafana.com' - # this needs to match tenant name in the CR/tenants.yaml and the tenant has be sent in X-Scope-OrgID - # The API gateway sends the tenantname as resource (res) to OPA sidecar - resources: - - dev - resourceNames: - - traces - verbs: - - 'create' ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: tempostack-traces -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: tempostack-traces-write -subjects: - - kind: ServiceAccount - name: dev-collector - namespace: chainsaw-multitenancy diff --git a/tests/fixtures/chainsaw-tests/multitenancy/03-assert.yaml b/tests/fixtures/chainsaw-tests/multitenancy/03-assert.yaml deleted file mode 100644 index b851c15..0000000 --- a/tests/fixtures/chainsaw-tests/multitenancy/03-assert.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: generate-traces-grpc - namespace: chainsaw-multitenancy -status: - succeeded: 1 ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: generate-traces-http - namespace: chainsaw-multitenancy -status: - succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/multitenancy/03-generate-traces.yaml b/tests/fixtures/chainsaw-tests/multitenancy/03-generate-traces.yaml deleted file mode 100644 index 26804d6..0000000 --- a/tests/fixtures/chainsaw-tests/multitenancy/03-generate-traces.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: generate-traces-grpc - namespace: chainsaw-multitenancy -spec: - template: - spec: - containers: - - name: telemetrygen - image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 - args: - - traces - - --otlp-endpoint=dev-collector:4317 - - --service=grpc - - --otlp-insecure - - --traces=10 - restartPolicy: Never ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: generate-traces-http - namespace: chainsaw-multitenancy -spec: - template: - spec: - containers: - - name: telemetrygen - image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.92.0 - args: - - traces - - --otlp-endpoint=dev-collector:4318 - - --otlp-http - - --otlp-insecure - - --service=http - - --traces=10 - restartPolicy: Never diff --git a/tests/fixtures/chainsaw-tests/multitenancy/04-assert.yaml b/tests/fixtures/chainsaw-tests/multitenancy/04-assert.yaml deleted file mode 100644 index aaadeeb..0000000 --- a/tests/fixtures/chainsaw-tests/multitenancy/04-assert.yaml +++ /dev/null @@ -1,31 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-grpc - namespace: chainsaw-multitenancy -status: - succeeded: 1 ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-traceql-grpc - namespace: chainsaw-multitenancy -status: - succeeded: 1 ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-http - namespace: chainsaw-multitenancy -status: - succeeded: 1 ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-traceql-http - namespace: chainsaw-multitenancy -status: - succeeded: 1 \ No newline at end of file diff --git a/tests/fixtures/chainsaw-tests/multitenancy/04-verify-traces.yaml b/tests/fixtures/chainsaw-tests/multitenancy/04-verify-traces.yaml deleted file mode 100644 index 85711af..0000000 --- a/tests/fixtures/chainsaw-tests/multitenancy/04-verify-traces.yaml +++ /dev/null @@ -1,129 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-grpc - namespace: chainsaw-multitenancy -spec: - template: - spec: - containers: - - name: verify-traces - image: ghcr.io/grafana/tempo-operator/test-utils:main - command: - - /bin/bash - - -eux - - -c - args: - - | - token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) - curl \ - -v -G \ - --header "Authorization: Bearer $token" \ - --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ - https://tempo-simplest-gateway.chainsaw-multitenancy.svc:8080/api/traces/v1/dev/api/traces \ - --data-urlencode "service=grpc" \ - | tee /tmp/jaeger.out - - num_traces=$(jq ".data | length" /tmp/jaeger.out) - if [[ "$num_traces" != "10" ]]; then - echo && echo "The Jaeger API returned $num_traces instead of 10 traces." - exit 1 - fi - restartPolicy: Never ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-traceql-grpc - namespace: chainsaw-multitenancy -spec: - template: - spec: - containers: - - name: verify-traces - image: ghcr.io/grafana/tempo-operator/test-utils:main - command: - - /bin/bash - - -eux - - -c - args: - - | - token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) - curl \ - -v -G \ - --header "Authorization: Bearer $token" \ - --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ - https://tempo-simplest-gateway.chainsaw-multitenancy.svc:8080/api/traces/v1/dev/tempo/api/search \ - --data-urlencode 'q={ resource.service.name="grpc" }' \ - | tee /tmp/jaeger.out - num_traces=$(jq ".traces | length" /tmp/jaeger.out) - if [[ "$num_traces" != "10" ]]; then - echo && echo "The Jaeger API returned $num_traces instead of 10 traces." - exit 1 - fi - restartPolicy: Never ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-http - namespace: chainsaw-multitenancy -spec: - template: - spec: - containers: - - name: verify-traces - image: ghcr.io/grafana/tempo-operator/test-utils:main - command: - - /bin/bash - - -eux - - -c - args: - - | - token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) - curl \ - -v -G \ - --header "Authorization: Bearer $token" \ - --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ - https://tempo-simplest-gateway.chainsaw-multitenancy.svc:8080/api/traces/v1/dev/api/traces \ - --data-urlencode "service=http" \ - | tee /tmp/jaeger.out - - num_traces=$(jq ".data | length" /tmp/jaeger.out) - if [[ "$num_traces" != "10" ]]; then - echo && echo "The Jaeger API returned $num_traces instead of 10 traces." - exit 1 - fi - restartPolicy: Never ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: verify-traces-traceql-http - namespace: chainsaw-multitenancy -spec: - template: - spec: - containers: - - name: verify-traces - image: ghcr.io/grafana/tempo-operator/test-utils:main - command: - - /bin/bash - - -eux - - -c - args: - - | - token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) - curl \ - -v -G \ - --header "Authorization: Bearer $token" \ - --cacert /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt \ - https://tempo-simplest-gateway.chainsaw-multitenancy.svc:8080/api/traces/v1/dev/tempo/api/search \ - --data-urlencode 'q={ resource.service.name="http" }' \ - | tee /tmp/jaeger.out - num_traces=$(jq ".traces | length" /tmp/jaeger.out) - if [[ "$num_traces" != "10" ]]; then - echo && echo "The Jaeger API returned $num_traces instead of 10 traces." - exit 1 - fi - restartPolicy: Never diff --git a/tests/fixtures/chainsaw-tests/multitenancy/chainsaw-test.yaml b/tests/fixtures/chainsaw-tests/multitenancy/chainsaw-test.yaml deleted file mode 100755 index 2565e94..0000000 --- a/tests/fixtures/chainsaw-tests/multitenancy/chainsaw-test.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json -apiVersion: chainsaw.kyverno.io/v1alpha1 -kind: Test -metadata: - creationTimestamp: null - name: multitenancy -spec: - namespace: chainsaw-multitenancy - steps: - - name: step-00 - try: - - apply: - file: 00-install-storage.yaml - - assert: - file: 00-assert.yaml - - name: step-01 - try: - - apply: - file: 01-install-tempo.yaml - - assert: - file: 01-assert.yaml - - name: step-02 - try: - - apply: - file: 02-install-otelcol.yaml - - assert: - file: 02-assert.yaml - - name: step-03 - try: - - apply: - file: 03-generate-traces.yaml - - assert: - file: 03-assert.yaml - - name: step-04 - try: - - apply: - file: 04-verify-traces.yaml - - assert: - file: 04-assert.yaml diff --git a/tests/package-lock.json b/tests/package-lock.json index 20bae2d..1943a6d 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -2231,9 +2231,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -2303,9 +2303,9 @@ "dev": true }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -4456,9 +4456,9 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dependencies": { "balanced-match": "^1.0.0" } @@ -6614,9 +6614,9 @@ } }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -6696,9 +6696,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -9825,9 +9825,9 @@ } }, "node_modules/matcher-collection/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -15336,9 +15336,9 @@ } }, "node_modules/walk-sync/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", diff --git a/tests/tests/dt-plugin-tests.cy.ts b/tests/tests/dt-plugin-tests.cy.ts deleted file mode 100644 index f800196..0000000 --- a/tests/tests/dt-plugin-tests.cy.ts +++ /dev/null @@ -1,348 +0,0 @@ -import { operatorHubPage } from '../views/operator-hub-page'; -import { Pages } from '../views/pages'; -import { searchPage } from '../views/search'; - -// Set constants for the operators that need to be installed for tests. -const DTP = { - namespace: 'openshift-cluster-observability-operator', - packageName: 'cluster-observability-operator', - operatorName: 'Cluster Observability Operator', - config: { - kind: 'UIPlugin', - name: 'distributed-tracing', - }, -}; - -const OTEL = { - namespace: 'openshift-opentelemetry-operator', - packageName: 'opentelemetry-product', - operatorName: 'Red Hat build of OpenTelemetry', -}; - -const TEMPO = { - namespace: 'openshift-tempo-operator', - packageName: 'tempo-product', - operatorName: 'Tempo Operator', -}; - -describe('OpenShift Distributed Tracing UI Plugin tests', () => { - before(() => { - cy.adminCLI( - `oc adm policy add-cluster-role-to-user cluster-admin ${Cypress.env('LOGIN_USERNAME')}`, - ); - // Getting the oauth url for hypershift cluster login - cy.exec( - `oc get oauthclient openshift-browser-client -o go-template --template="{{index .redirectURIs 0}}" --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ).then((result) => { - if (expect(result.stderr).to.be.empty) { - const oauth = result.stdout; - // Trimming the origin part of the url - const oauthurl = new URL(oauth); - const oauthorigin = oauthurl.origin; - cy.log(oauthorigin); - cy.wrap(oauthorigin).as('oauthorigin'); - } else { - throw new Error(`Execution of oc get oauthclient failed - Exit code: ${result.code} - Stdout:\n${result.stdout} - Stderr:\n${result.stderr}`); - } - }); - cy.get('@oauthorigin').then((oauthorigin) => { - cy.login( - Cypress.env('LOGIN_IDP'), - Cypress.env('LOGIN_USERNAME'), - Cypress.env('LOGIN_PASSWORD'), - oauthorigin, - ); - }); - - if (Cypress.env('SKIP_COO_INSTALL')) { - cy.log('SKIP_COO_INSTALL is set. Skipping Cluster Observability Operator installation.'); - } else if (Cypress.env('COO_UI_INSTALL')) { - cy.log('COO_UI_INSTALL is set. COO, Tempo and OpenTelemetry operators will be installed from redhat-operators catalog source'); - cy.log('Install Cluster Observability Operator'); - operatorHubPage.installOperator(DTP.packageName, 'redhat-operators'); - cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should( - 'include.text', - 'ready for use', - ); - cy.log('Install OpenTelemetry Operator'); - operatorHubPage.installOperator(OTEL.packageName, 'redhat-operators'); - cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should( - 'include.text', - 'ready for use', - ); - cy.log('Install Tempo Operator'); - operatorHubPage.installOperator(TEMPO.packageName, 'redhat-operators'); - cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should( - 'include.text', - 'ready for use', - ); - } else if (Cypress.env('KONFLUX_COO_BUNDLE_IMAGE')) { - cy.log('KONFLUX_COO_BUNDLE_IMAGE is set. COO operator will be installed from Konflux bundle. Tempo and OpenTelemetry operators will be installed from redhat-operators catalog source'); - cy.log('Install Cluster Observability Operator'); - cy.exec( - `oc --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} apply -f ./fixtures/coo-imagecontentsourcepolicy.yaml` , - ); - cy.exec( - `oc create namespace ${DTP.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ); - cy.exec( - `oc label namespaces ${DTP.namespace} openshift.io/cluster-monitoring=true --overwrite=true --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ); - cy.exec( - `operator-sdk run bundle --timeout=10m --namespace ${DTP.namespace} ${Cypress.env('KONFLUX_COO_BUNDLE_IMAGE')} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} --verbose `, - { timeout: 6 * 60 * 1000 }, - ); - cy.log('Install OpenTelemetry Operator'); - operatorHubPage.installOperator(OTEL.packageName, 'redhat-operators'); - cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should( - 'include.text', - 'ready for use', - ); - cy.log('Install Tempo Operator'); - operatorHubPage.installOperator(TEMPO.packageName, 'redhat-operators'); - cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should( - 'include.text', - 'ready for use', - ); - } else if (Cypress.env('CUSTOM_COO_BUNDLE_IMAGE')) { - cy.log('CUSTOM_COO_BUNDLE_IMAGE is set. COO operator will be installed from custom built bundle. Tempo and OpenTelemetry operators will be installed from redhat-operators catalog source'); - cy.log('Install Cluster Observability Operator'); - cy.exec( - `oc --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} apply -f ./fixtures/coo-imagecontentsourcepolicy.yaml` , - ); - cy.exec( - `oc create namespace ${DTP.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ); - cy.exec( - `oc label namespaces ${DTP.namespace} openshift.io/cluster-monitoring=true --overwrite=true --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ); - cy.exec( - `operator-sdk run bundle --timeout=10m --namespace ${DTP.namespace} ${Cypress.env('CUSTOM_COO_BUNDLE_IMAGE')} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')} --verbose `, - { timeout: 6 * 60 * 1000 }, - ); - cy.log('Install OpenTelemetry Operator'); - operatorHubPage.installOperator(OTEL.packageName, 'redhat-operators'); - cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should( - 'include.text', - 'ready for use', - ); - cy.log('Install Tempo Operator'); - operatorHubPage.installOperator(TEMPO.packageName, 'redhat-operators'); - cy.get('.co-clusterserviceversion-install__heading', { timeout: 5 * 60 * 1000 }).should( - 'include.text', - 'ready for use', - ); - } else { - throw new Error('No CYPRESS env set for operator installation, check the README for more details.'); - } - - cy.log('Set Distributed Tracing Console Plugin image in operator CSV'); - if (Cypress.env('DT_CONSOLE_IMAGE')) { - cy.log('DT_CONSOLE_IMAGE is set. the image will be patched in COO operator CSV'); - cy.exec( - './fixtures/update-plugin-image.sh', - { - env: { - DT_CONSOLE_IMAGE: Cypress.env('DT_CONSOLE_IMAGE'), - KUBECONFIG: Cypress.env('KUBECONFIG_PATH'), - DTP_NAMESPACE: `${DTP.namespace}` - }, - timeout: 120000, - failOnNonZeroExit: true - } - ) .then((result) => { - expect(result.code).to.eq(0); - cy.log(`COO CSV updated successfully with Distributed Tracing Console Plugin image: ${result.stdout}`); - }); - } else { - cy.log('DT_CONSOLE_IMAGE is NOT set. Skipping patching the image in COO operator CSV.'); - } - - cy.log('Create Distributed Tracing UI Plugin instance.'); - cy.exec(`oc apply -f ./fixtures/tracing-ui-plugin.yaml --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`); - cy.exec( - `sleep 15 && oc wait --for=condition=Ready pods --selector=app.kubernetes.io/instance=distributed-tracing -n ${DTP.namespace} --timeout=60s --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - { - timeout: 80000, - failOnNonZeroExit: true - } - ).then((result) => { - expect(result.code).to.eq(0); - cy.log(`Distributed Tracing Console plugin pod is now running in namespace: ${DTP.namespace}`); - }); - cy.get('.pf-v5-c-alert, .pf-v6-c-alert', { timeout: 120000 }) - .contains('Web console update is available') - .then(($alert) => { - // If the alert is found, assert that it exists - expect($alert).to.exist; - }, () => { - // If the alert is not found within the timeout, visit and assert the /observe/traces page - cy.visit('/observe/traces'); - cy.url().should('include', '/observe/traces'); - }); - - }); - - after(() => { - if (Cypress.env('SKIP_COO_INSTALL')) { - cy.log('Delete Distributed Tracing UI Plugin instance.'); - cy.executeAndDelete( - `oc delete ${DTP.config.kind} ${DTP.config.name} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ); - - cy.log('Delete Chainsaw namespaces.'); - cy.executeAndDelete( - `oc delete project chainsaw-multitenancy chainsaw-monolithic-multitenancy --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ); - - cy.log('Remove cluster-admin role from user.'); - cy.executeAndDelete( - `oc adm policy remove-cluster-role-from-user cluster-admin ${Cypress.env('LOGIN_USERNAME')} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ); - } else { - cy.log('Delete Distributed Tracing UI Plugin instance.'); - cy.executeAndDelete( - `oc delete ${DTP.config.kind} ${DTP.config.name} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ); - - cy.log('Delete Chainsaw namespaces.'); - cy.executeAndDelete( - `oc delete project chainsaw-multitenancy chainsaw-monolithic-multitenancy --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ); - - cy.log('Remove Cluster Observability Operator'); - cy.executeAndDelete(`oc delete namespace ${DTP.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`); - - cy.log('Remove OpenTelemetry Operator'); - cy.executeAndDelete(`oc delete namespace ${OTEL.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`); - - cy.log('Remove Tempo Operator'); - cy.executeAndDelete(`oc delete namespace ${TEMPO.namespace} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`); - - cy.log('Remove cluster-admin role from user.'); - cy.executeAndDelete( - `oc adm policy remove-cluster-role-from-user cluster-admin ${Cypress.env('LOGIN_USERNAME')} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, - ); - } - }); - - // Tests start from here. - it('Test Distributed Tracing UI plugin page without any Tempo instances', () => { - cy.log('Navigate to the observe/traces page'); - cy.visit('/observe/traces'); - - cy.log('Assert that the Traces page shows the empty state.'); - cy.get('.pf-v6-c-empty-state__title-text') - .should('be.visible') - .and('have.text', 'No Tempo instances yet'); - - cy.log('Assert that the View documentation button is visible.'); - cy.contains('.pf-v6-c-button', 'View documentation') - .should('be.visible') - .and('have.text', 'View documentation'); - - cy.log('Assert create a tempo instance toggle visibility and text.'); - const createTempoToggle = cy.contains('.pf-v6-c-menu-toggle', 'Create a Tempo instance'); - createTempoToggle.should('be.visible'); - - cy.log('Click the toggle to show creation options.'); - createTempoToggle.click(); - - cy.log('Assert dropdown items for Tempo instance creation are visible.'); - cy.contains('.pf-v6-c-menu__item-text', 'Create a TempoStack instance') - .should('be.visible') - .and('have.text', 'Create a TempoStack instance'); - - cy.contains('.pf-v6-c-menu__item-text', 'Create a TempoMonolithic instance') - .should('be.visible') - .and('have.text', 'Create a TempoMonolithic instance'); - }); - - it('(Test Distributed Tracing UI plugin with Tempo instances and verify traces)', function () { - cy.log('Create TempoStack and TempoMonolithic instances'); - cy.exec( - 'chainsaw test --config ./fixtures/.chainsaw.yaml --skip-delete ./fixtures/chainsaw-tests/multitenancy ./fixtures/chainsaw-tests/monolithic-multitenancy-openshift', - { - env: { - KUBECONFIG: Cypress.env('KUBECONFIG_PATH'), - }, - timeout: 1800000, - failOnNonZeroExit: true - } - ) .then((result) => { - expect(result.code).to.eq(0); - cy.log(`Chainsaw test ran successfully: ${result.stdout}`); - }); - cy.log('Navigate to the observe/traces page'); - cy.visit('/observe/traces'); - - cy.log('Assert traces in TempoStack instance.'); - cy.get(':nth-child(1) > .pf-v6-c-toolbar__item > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button > .pf-v6-c-menu-toggle__controls > .pf-v6-c-menu-toggle__toggle-icon').click(); - cy.get(':nth-child(2) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); - cy.get(':nth-child(1) > :nth-child(2) > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button').click(); - cy.get(':nth-child(2) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); - cy.get(':nth-child(2) > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__controls > .pf-v6-c-menu-toggle__toggle-icon').click(); - cy.get(':nth-child(1) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); - cy.get(':nth-child(1) > :nth-child(2) > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button > .pf-v6-c-menu-toggle__controls > .pf-v6-c-menu-toggle__toggle-icon').click(); - cy.get(':nth-child(1) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); - cy.get('.pf-v6-c-toolbar__group > :nth-child(1) > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__controls > .pf-v6-c-menu-toggle__toggle-icon').click(); - cy.get(':nth-child(1) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); - cy.get('.pf-m-toggle-group > .pf-v6-c-toolbar__group > :nth-child(2) > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button').click(); - cy.contains('.pf-v6-c-menu__item-text', 'http') - .closest('.pf-v6-c-menu__item') - .find('input[type="checkbox"]') - .check(); - cy.contains('.pf-v6-c-menu__item-text', 'grpc') - .closest('.pf-v6-c-menu__item') - .find('input[type="checkbox"]') - .check(); - cy.get('.MuiDataGrid-row--firstVisible > [data-field="name"] > .MuiBox-root > .MuiTypography-root').click(); - cy.contains('div', 'okey-dokey').click({ force: true }); - cy.get('.css-1bmckj4').then(($el) => { - cy.log(`Actual text in .css-1bmckj4 (TempoMonolithic): ${$el.text()}`); - expect($el.text()).to.satisfy((text) => text === 'http' || text === 'grpc'); - }); - cy.get('.MuiTypography-h2').should('have.text', 'okey-dokey'); - cy.get('.MuiTabs-list > .MuiButtonBase-root').should('be.visible'); - cy.get(':nth-child(5) > :nth-child(1) > .MuiListItemText-root > .MuiTypography-h5').should('have.text', 'net.peer.ip'); - cy.get(':nth-child(5) > :nth-child(1) > .MuiListItemText-root > .MuiTypography-body1').should('have.text', '1.2.3.4'); - cy.get(':nth-child(2) > .MuiListItemText-root > .MuiTypography-h5').should('have.text', 'peer.service'); - cy.get(':nth-child(2) > .MuiListItemText-root > .MuiTypography-body1').should('have.text', 'telemetrygen-client'); - cy.get(':nth-child(7) > .MuiListItem-root > .MuiListItemText-root > .MuiTypography-h5').should('have.text', 'service.name'); - cy.get(':nth-child(7) > .MuiListItem-root > .MuiListItemText-root > .MuiTypography-body1').then(($el) => { - cy.log(`Actual text in service.name (TempoStack): ${$el.text()}`); - expect($el.text()).to.satisfy((text) => text === 'http' || text === 'grpc'); - }); - cy.get('.pf-v6-c-breadcrumb__list > :nth-child(1) > a').click(); - - cy.log('Assert traces in TempoMonolithic instance.'); - cy.get(':nth-child(1) > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle > .pf-v6-c-menu-toggle__button').click(); - cy.get(':nth-child(1) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); - cy.get(':nth-child(2) > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-menu-toggle').click(); - cy.get(':nth-child(3) > .pf-v6-c-menu__item > .pf-v6-c-menu__item-main > .pf-v6-c-menu__item-text').click(); - cy.get('.pf-v6-c-form__group-control > .pf-v6-c-button > .pf-v6-c-button__text').click(); - cy.get('.pf-m-toggle-group > .pf-m-action-group > .pf-v6-c-toolbar__item > .pf-v6-c-form > .pf-v6-c-form__group > .pf-v6-c-form__group-control > .pf-v6-c-button > .pf-v6-c-button__text').click(); - cy.get('.MuiDataGrid-row--firstVisible > [data-field="name"] > .MuiBox-root > .MuiTypography-root').click(); - cy.contains('div', 'okey-dokey').click({ force: true }); - cy.get('.css-1bmckj4').then(($el) => { - cy.log(`Actual text in .css-1bmckj4 (TempoMonolithic): ${$el.text()}`); - expect($el.text()).to.satisfy((text) => text === 'http' || text === 'grpc'); - }); - cy.get('.MuiTypography-h2').should('have.text', 'okey-dokey'); - cy.get('.MuiTabs-list > .MuiButtonBase-root').should('be.visible'); - cy.get(':nth-child(5) > :nth-child(1) > .MuiListItemText-root > .MuiTypography-h5').should('have.text', 'net.peer.ip'); - cy.get(':nth-child(5) > :nth-child(1) > .MuiListItemText-root > .MuiTypography-body1').should('have.text', '1.2.3.4'); - cy.get(':nth-child(2) > .MuiListItemText-root > .MuiTypography-h5').should('have.text', 'peer.service'); - cy.get(':nth-child(2) > .MuiListItemText-root > .MuiTypography-body1').should('have.text', 'telemetrygen-client'); - cy.get(':nth-child(7) > .MuiListItem-root > .MuiListItemText-root > .MuiTypography-h5').should('have.text', 'service.name'); - cy.get(':nth-child(7) > .MuiListItem-root > .MuiListItemText-root > .MuiTypography-body1').then(($el) => { - cy.log(`Actual text in service.name (TempoMonolithic): ${$el.text()}`); - expect($el.text()).to.satisfy((text) => text === 'http' || text === 'grpc'); - }); - cy.get('.pf-v6-c-breadcrumb__list > :nth-child(1) > a').click(); - }); - -});