diff --git a/web/cypress/fixtures/flowcollector/fc_TLSTracking.yaml b/web/cypress/fixtures/flowcollector/fc_TLSTracking.yaml new file mode 100644 index 000000000..819ee8525 --- /dev/null +++ b/web/cypress/fixtures/flowcollector/fc_TLSTracking.yaml @@ -0,0 +1,21 @@ +kind: FlowCollector +apiVersion: flows.netobserv.io/v1beta2 +metadata: + name: cluster +spec: + agent: + ebpf: + sampling: 1 + features: + - TLSTracking + type: eBPF + loki: + enable: true + mode: Monolithic + namespace: netobserv + monolithic: + installDemoLoki: true + processor: + metrics: + additionalIncludeList: + - workload_tls_flows_total diff --git a/web/cypress/fixtures/test-server-client.yaml b/web/cypress/fixtures/test-server-client.yaml index 040e4e9cb..1c95ae7e6 100644 --- a/web/cypress/fixtures/test-server-client.yaml +++ b/web/cypress/fixtures/test-server-client.yaml @@ -1,19 +1,25 @@ -apiVersion: template.openshift.io/v1 -kind: Template +--- +apiVersion: v1 +kind: Namespace metadata: - name: netobserv-test-client-server -objects: - - kind: Namespace - apiVersion: v1 - metadata: - name: ${SERVER_NS} - labels: - name: ${SERVER_NS} - - apiVersion: apps/v1 - kind: Deployment + name: test-server-56222 + labels: + name: test-server-56222 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + namespace: test-server-56222 + labels: + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: metadata: - name: nginx - namespace: ${SERVER_NS} labels: app: nginx spec: @@ -21,74 +27,61 @@ objects: runAsNonRoot: true seccompProfile: type: RuntimeDefault - replicas: 1 - selector: - matchLabels: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: ["ALL"] - privileged: false - image: quay.io/openshifttest/nginx-alpine:1.2.3 - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8080 - - apiVersion: v1 - kind: Service - metadata: - namespace: ${SERVER_NS} - name: nginx-service - spec: - selector: - app: nginx - type: NodePort - ports: - - protocol: TCP - port: 80 - targetPort: 8080 - - kind: Namespace - apiVersion: v1 - metadata: - name: ${CLIENT_NS} - labels: - name: ${CLIENT_NS} - - apiVersion: v1 - kind: Pod - metadata: - creationTimestamp: null - labels: - run: client - name: client - namespace: ${CLIENT_NS} - spec: containers: - - command: - - sh - - -c - - " - \ while : ; do\n - \ curl nginx-service.${SERVER_NS}.svc:80/data/100K 2>&1 > /dev/null ; sleep 5 \n - \ done" - image: quay.io/openshifttest/hello-openshift:1.2.0 + - name: nginx + image: quay.io/openshifttest/nginx-alpine:1.2.3 + imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] - privileged: false - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - name: client -parameters: - - name: SERVER_NS - value: test-server - - name: CLIENT_NS - value: test-client + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: test-server-56222 + name: nginx-service +spec: + selector: + app: nginx + type: NodePort + ports: + - protocol: TCP + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: Namespace +metadata: + name: test-client-56222 + labels: + name: test-client-56222 +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + run: client + name: client + namespace: test-client-56222 +spec: + containers: + - name: client + image: quay.io/openshifttest/hello-openshift:1.2.0 + command: + - sh + - -c + - | + while : ; do + curl nginx-service.test-server-56222.svc:80/data/100K 2>&1 > /dev/null + sleep 5 + done + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + capabilities: + drop: ["ALL"] + seccompProfile: + type: RuntimeDefault diff --git a/web/cypress/integration-tests/dns_tracking.cy.ts b/web/cypress/integration-tests/dns_tracking.cy.ts index ecaf3dea3..5762360c4 100644 --- a/web/cypress/integration-tests/dns_tracking.cy.ts +++ b/web/cypress/integration-tests/dns_tracking.cy.ts @@ -63,23 +63,14 @@ describe('(OCP-67087) DNSTracking test', { tags: ['Network_Observability'] }, fu cy.get(filterSelectors.filterInput).type("dst_namespace=" + project + '{enter}') cy.get(filterSelectors.filterInput).type("dns_name=" + dns_name + '{enter}') - // select DNS Id and DNS Error columns - cy.openColumnsModal().then(col => { - cy.get(colSelectors.columnsModal).should('be.visible') - cy.get('#DNSId').check() - cy.get('#DNSErrNo').check() - cy.get('#DNSName').check() - cy.byTestID(colSelectors.save).click() - }) - - // verify they are visible in table view - cy.byTestID('table-composable').should('exist').within(() => { - cy.get(colSelectors.dnsId).should('exist') - cy.get(colSelectors.dnsError).should('exist') - cy.get(colSelectors.dnsName).should('exist') - }) - - // Verify DNSName column for all rows + // select DNS Id, DNS Error and DNS Name columns + cy.selectAndVerifyColumns([ + colSelectors.dnsId, + colSelectors.dnsError, + colSelectors.dnsName + ]) + + // Verify DNSName value for all rows cy.get('[data-test-td-column-id="DNSName"]').each((td) => { expect(td).to.contain(`${dns_name}`) }) diff --git a/web/cypress/integration-tests/netflow_cluster_admin_group.cy.ts b/web/cypress/integration-tests/netflow_cluster_admin_group.cy.ts index a7e714c3b..15c0df38c 100644 --- a/web/cypress/integration-tests/netflow_cluster_admin_group.cy.ts +++ b/web/cypress/integration-tests/netflow_cluster_admin_group.cy.ts @@ -27,15 +27,15 @@ describe.skip('(OCP-67617) User in group with cluster-admin role', { tags: ['Net cy.visit('/netflow-traffic') // validate user is not able to access netflow traffic page // overview shows no panels - cy.get('li.overviewTabButton').should('exist').click() + cy.get('#tabs-container').contains('Overview').click() cy.get("#overview-flex").should('not.exist') // table view shows no grid - cy.get('li.tableTabButton').should('exist').click() + cy.get('#tabs-container').contains('Traffic flows').click() cy.byTestID("table-composable").should('not.exist') // topology view shows no view - cy.get('li.topologyTabButton').should('exist').click() + cy.get('#tabs-container').contains('Topology').click() cy.byTestID("error-state").should('exist') }) diff --git a/web/cypress/integration-tests/netflow_external_subnet.cy.ts b/web/cypress/integration-tests/netflow_external_subnet.cy.ts index df2eb1689..c348d48bb 100644 --- a/web/cypress/integration-tests/netflow_external_subnet.cy.ts +++ b/web/cypress/integration-tests/netflow_external_subnet.cy.ts @@ -21,12 +21,10 @@ describe('(OCP-67615, OCP-72874) Return external traffic and custom subnet label cy.byTestID("table-composable").should('exist') // enable SrcSubnetLabel and DstSubnetLabel columns - cy.openColumnsModal().then(col => { - cy.get(colSelectors.columnsModal).should('be.visible') - cy.get('#SrcSubnetLabel').check() - cy.get('#DstSubnetLabel').check() - cy.byTestID(colSelectors.save).click() - }) + cy.selectAndVerifyColumns([ + colSelectors.srcSubnetLabel, + colSelectors.dstSubnetLabel + ]) // filter on SrcSubnetLabel Pods and DstIP 52.200.142.250 cy.get(filterSelectors.filterInput).type("src_subnet_label=Pods" + '{enter}') diff --git a/web/cypress/integration-tests/netflow_table.cy.ts b/web/cypress/integration-tests/netflow_table.cy.ts index e4a965c16..1a00c6027 100644 --- a/web/cypress/integration-tests/netflow_table.cy.ts +++ b/web/cypress/integration-tests/netflow_table.cy.ts @@ -57,21 +57,21 @@ describe('(OCP-50532, OCP-50531, OCP-50530, OCP-59408) Netflow Table view tests' netflowPage.stopAutoRefresh() cy.openColumnsModal().then(col => { cy.get(colSelectors.columnsModal).should('be.visible') - cy.get('#K8S_OwnerObject').check() - cy.get('#AddrPort').check() + cy.get(colSelectors.k8sOwner).check() + cy.get(colSelectors.ipPort).check() - cy.get('#Mac').should('exist').check() - cy.get('#FlowDirection').should('exist').check() + cy.get(colSelectors.mac).should('exist').check() + cy.get(colSelectors.direction).should('exist').check() // ICMP related columns - cy.get('#IcmpType').should('exist').check() - cy.get('#IcmpCode').should('exist').check() + cy.get(colSelectors.icmpType).should('exist').check() + cy.get(colSelectors.icmpCode).should('exist').check() // source columns - cy.get('#SrcK8S_HostIP').check() + cy.get(colSelectors.srcNodeIP).check() cy.get('#SrcK8S_Namespace[type="checkbox"]').uncheck() // dest columns - cy.get('#DstK8S_HostIP').check() + cy.get(colSelectors.dstNodeIP).check() cy.byTestID(colSelectors.save).click() }) @@ -170,11 +170,7 @@ describe('(OCP-50532, OCP-50531, OCP-50530, OCP-59408) Netflow Table view tests' cy.contains('Display options').should('exist').click() cy.byTestID('size-s').click() cy.contains('Display options').should('exist').click() - cy.openColumnsModal().then(col => { - cy.get(colSelectors.columnsModal).should('be.visible') - cy.get('#StartTime').check() - cy.byTestID(colSelectors.save).click() - }) + cy.selectAndVerifyColumns([colSelectors.startTime]) cy.byTestID("show-view-options-button").should('exist').click() }) @@ -267,7 +263,7 @@ describe('(OCP-50532, OCP-50531, OCP-50530, OCP-59408) Netflow Table view tests' cy.byTestID("show-histogram-button").should('exist').click().then(() => { cy.get('#time-range-dropdown-dropdown').should('exist').click() cy.get("#5m").should("exist").click() - cy.byTestID("refresh-dropdown-dropdown").should('exist').should('not.be.disabled') + cy.byTestID(genSelectors.refreshDrop).should('exist').should('not.be.disabled') }) }) diff --git a/web/cypress/integration-tests/netflow_zone_multiCluster.cy.ts b/web/cypress/integration-tests/netflow_zone_multiCluster.cy.ts index 59a1ee6b7..4b5ae5773 100644 --- a/web/cypress/integration-tests/netflow_zone_multiCluster.cy.ts +++ b/web/cypress/integration-tests/netflow_zone_multiCluster.cy.ts @@ -20,25 +20,12 @@ describe('Netflow Zone and multiCluster test', { tags: ['Network_Observability'] cy.get('#tabs-container').contains('Traffic flows').click() cy.byTestID("table-composable").should('exist') - cy.openColumnsModal().then(col => { - cy.get(colSelectors.columnsModal).should('be.visible') - // Check zone columns - cy.get('#SrcZone').check() - cy.get('#DstZone').check() - - // Check multiCluster column - cy.get('#ClusterName').check() - cy.byTestID(colSelectors.save).click() - }) - - cy.byTestID('table-composable').should('exist').within(() => { - // Verify zone column - cy.get(colSelectors.srcZone).should('exist') - cy.get(colSelectors.dstZone).should('exist') - - // Verify multiCluster column - cy.get(colSelectors.clusterName).should('exist') - }) + // Check zone and multiCluster columns + cy.selectAndVerifyColumns([ + colSelectors.srcZone, + colSelectors.dstZone, + colSelectors.clusterName + ]) }) it("(OCP-71524, aramesha) should verify zone/cluster scope topology", function () { diff --git a/web/cypress/integration-tests/quickFilters.cy.ts b/web/cypress/integration-tests/quickFilters.cy.ts index 9ac3d9b34..7728dddca 100644 --- a/web/cypress/integration-tests/quickFilters.cy.ts +++ b/web/cypress/integration-tests/quickFilters.cy.ts @@ -17,20 +17,26 @@ var patch = [{ } ] }] -var templateParams = `-p SERVER_NS=${SERVER_NS} CLIENT_NS=${CLIENT_NS}` -var templateProcessCmd = `oc process -f cypress/fixtures/test-server-client.yaml ${templateParams} --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}` describe('(OCP-56222) Quick Filters test', { tags: ['Network_Observability'] }, function () { before('any test', function () { cy.adminCLI(`oc adm policy add-cluster-role-to-user cluster-admin ${Cypress.env('LOGIN_USERNAME')}`) cy.uiLogin(Cypress.env('LOGIN_IDP'), Cypress.env('LOGIN_USERNAME'), Cypress.env('LOGIN_PASSWORD')) - // create test server and client pods - cy.adminCLI(`${templateProcessCmd}| oc apply -f -`) Operator.install() cy.checkStorageClass(this) Operator.createFlowcollector() + + // create test server and client pods + cy.adminCLI('oc apply -f cypress/fixtures/test-server-client.yaml') + + // Wait for pods to be created + cy.wait(10000) + + // Wait for pods to be ready + cy.adminCLI('oc wait --for=condition=Ready pod -l app=nginx -n test-server-56222 --timeout=120s') + cy.adminCLI('oc wait --for=condition=Ready pod -n test-client-56222 client --timeout=120s') }) beforeEach('any netflow table test', function () { @@ -88,7 +94,7 @@ describe('(OCP-56222) Quick Filters test', { tags: ['Network_Observability'] }, }) after("all tests", function () { - cy.adminCLI(`${templateProcessCmd} | oc delete -f -`) + cy.adminCLI('oc delete -f cypress/fixtures/test-server-client.yaml --ignore-not-found') cy.adminCLI(`oc adm policy remove-cluster-role-from-user cluster-admin ${Cypress.env('LOGIN_USERNAME')}`) }) }) diff --git a/web/cypress/integration-tests/table_queryopts.cy.ts b/web/cypress/integration-tests/table_queryopts.cy.ts index 32b4cd43f..53b72de31 100644 --- a/web/cypress/integration-tests/table_queryopts.cy.ts +++ b/web/cypress/integration-tests/table_queryopts.cy.ts @@ -84,16 +84,7 @@ describe('(OCP-50532, OCP-50531, OCP-50530, OCP-59408) Netflow Table Query Optio it("(OCP-68125, aramesha) should verify DSCP column", function () { netflowPage.stopAutoRefresh() - cy.openColumnsModal().then(col => { - cy.get(colSelectors.columnsModal).should('be.visible') - cy.get('#Dscp').check() - cy.byTestID(colSelectors.save).click() - }) - cy.reload() - - cy.byTestID('table-composable').should('exist').within(() => { - cy.get(colSelectors.dscp).should('exist') - }) + cy.selectAndVerifyColumns([colSelectors.dscp]) // filter on DSCP values cy.get(filterSelectors.filterInput).type("dscp=0" + '{enter}').click() diff --git a/web/cypress/integration-tests/tls_dashboards.cy.ts b/web/cypress/integration-tests/tls_dashboards.cy.ts new file mode 100644 index 000000000..0d0e0c17a --- /dev/null +++ b/web/cypress/integration-tests/tls_dashboards.cy.ts @@ -0,0 +1,39 @@ +import { Operator } from "@views/netobserv" +import { dashboard } from "@views/dashboards-page" + +const TLSPanels = [ + "flows-rate-per-tls-version-chart", + "flows-rate-per-tls-group-chart", +] + +describe('(OCP-88966) TLSTracking test', { tags: ['Network_Observability'] }, function () { + + before('any test', function () { + cy.adminCLI(`oc adm policy add-cluster-role-to-user cluster-admin ${Cypress.env('LOGIN_USERNAME')}`) + cy.uiLogin(Cypress.env('LOGIN_IDP'), Cypress.env('LOGIN_USERNAME'), Cypress.env('LOGIN_PASSWORD')) + + Operator.install() + cy.checkStorageClass(this) + Operator.createFlowcollector("TLSTracking") + }) + + // TODO: TLS in topology is still Dev InProgress. Will implement test here once its complete + + it("(OCP-88966, aramesha) Validate TLSTracking dashboards", function () { + // navigate to 'NetObserv / Main' Dashboard page + dashboard.visit() + dashboard.visitDashboard("netobserv-main") + + // verify 'TLS Traffic' panel + cy.checkDashboards(['tls-traffic-chart']) + + cy.get('#content-scrollable').scrollTo('bottom') + + cy.checkDashboards(TLSPanels) + }) + + after("all tests", function () { + Operator.deleteFlowCollector() + cy.adminCLI(`oc adm policy remove-cluster-role-from-user cluster-admin ${Cypress.env('LOGIN_USERNAME')}`) + }) +}) diff --git a/web/cypress/integration-tests/tls_tracking.cy.ts b/web/cypress/integration-tests/tls_tracking.cy.ts new file mode 100644 index 000000000..9e43f1571 --- /dev/null +++ b/web/cypress/integration-tests/tls_tracking.cy.ts @@ -0,0 +1,92 @@ +import { colSelectors, filterSelectors, netflowPage, overviewSelectors } from "@views/netflow-page" +import { Operator } from "@views/netobserv" + +describe('(OCP-88966) TLSTracking test', { tags: ['Network_Observability'] }, function () { + + before('any test', function () { + cy.adminCLI(`oc adm policy add-cluster-role-to-user cluster-admin ${Cypress.env('LOGIN_USERNAME')}`) + cy.uiLogin(Cypress.env('LOGIN_IDP'), Cypress.env('LOGIN_USERNAME'), Cypress.env('LOGIN_PASSWORD')) + + Operator.install() + cy.checkStorageClass(this) + Operator.createFlowcollector("TLSTracking") + }) + + beforeEach('any TLSTracking test', function () { + netflowPage.visit() + }) + + it("(OCP-88966, aramesha) Verify TLSTracking panels", function () { + // verify default TLSTracking panels are visible + cy.checkPanel(overviewSelectors.defaultTLSTrackingPanels) + cy.checkPanelsNum(4); + + // open panels modal and verify all relevant panels are listed + cy.openPanelsModal() + cy.checkPopItems(overviewSelectors.panelsModal, overviewSelectors.manageTLSTrackingPanelsList); + + // select all panels and verify they are rendered + cy.get(overviewSelectors.panelsModal).contains('Select all').click(); + cy.get(overviewSelectors.panelsModal).contains('Save').click(); + netflowPage.waitForLokiQuery() + cy.checkPanelsNum(8); + cy.checkPanel(overviewSelectors.allTLSTrackingPanels) + + // restore default panels and verify they are visible + cy.openPanelsModal(); + cy.byTestID(overviewSelectors.resetDefault).click().byTestID(overviewSelectors.save).click() + netflowPage.waitForLokiQuery() + cy.checkPanel(overviewSelectors.defaultTLSTrackingPanels) + cy.checkPanelsNum(4); + }) + + it("(OCP-88966, aramesha) Validate TLSTracking columns", function () { + cy.get('#tabs-container').contains('Traffic flows').click() + cy.byTestID("table-composable").should('exist') + netflowPage.stopAutoRefresh() + + // verify default TLS column: TLS Version + cy.byTestID('table-composable').should('exist').within(() => { + cy.get(colSelectors.tlsVersion).should('exist') + }) + + // select TLS Cipher Suite, TLS Group and TLS Types columns + cy.selectAndVerifyColumns([ + colSelectors.tlsCipherSuite, + colSelectors.tlsGroup, + colSelectors.tlsTypes + ]) + + // add filter for tls_version= TLS 1.3 and tls_types = ServerHello + cy.get(filterSelectors.filterInput).type("tls_version=TLS 1.3" + '{enter}') + cy.get(filterSelectors.filterInput).type("tls_types=ServerHello" + '{enter}') + netflowPage.waitForLokiQuery() + + // Verify TLS column data for all rows + cy.get('[data-test-td-column-id="TLSVersion"]') + .should('have.length.greaterThan', 0) + .each((td) => { + expect(td).to.contain('TLS 1.3') + }) + cy.get('[data-test-td-column-id="TLSTypes"]').each((td) => { + expect(td).to.contain('ServerHello') + }) + cy.get('[data-test-td-column-id="TLSGroup"]').each((td) => { + expect(td.text().trim()).to.not.be.empty + }) + cy.get('[data-test-td-column-id="TLSCipherSuite"]').each((td) => { + expect(td.text().trim()).to.not.be.empty + }) + + netflowPage.clearAllFilters() + }) + + afterEach("test", function () { + netflowPage.resetClearFilters() + }) + + after("all tests", function () { + Operator.deleteFlowCollector() + cy.adminCLI(`oc adm policy remove-cluster-role-from-user cluster-admin ${Cypress.env('LOGIN_USERNAME')}`) + }) +}) diff --git a/web/cypress/views/netflow-page.ts b/web/cypress/views/netflow-page.ts index fbd222bcc..6c6811dde 100644 --- a/web/cypress/views/netflow-page.ts +++ b/web/cypress/views/netflow-page.ts @@ -6,6 +6,7 @@ declare global { checkPanel(panelName: string[]): Chainable openPanelsModal(): Chainable openColumnsModal(): Chainable + selectAndVerifyColumns(columnSelectors: string[]): Chainable checkPopItems(id: string, names: string[]): Chainable checkQuerySummary(metric: JQuery): Chainable checkPerformance(page: string, loadTime: number, memoryUsage: number): Chainable @@ -35,8 +36,10 @@ export const netflowPage = { // set the page to auto refresh netflowPage.setAutoRefresh() - cy.byTestID('no-results-found').should('not.exist') - cy.get('#overview-container').should('exist') + netflowPage.waitForLokiQuery() + + cy.byTestID('no-results-found', { timeout: 30000 }).should('not.exist') + cy.get('#overview-container', { timeout: 30000 }).should('exist') }, setAutoRefresh: () => { cy.byTestID(genSelectors.refreshDrop).should('exist').invoke('text').then((text) => { @@ -172,39 +175,43 @@ export namespace genSelectors { export const fullScreen = '[data-test=fullscreen-button]' } -// Helper function to generate table header column selectors -const thCol = (columnId: string): string => `[data-test=th-${columnId}] button`; - export namespace colSelectors { export const columnsModal = '#columns-modal' export const save = 'columns-save-button' export const resetDefault = 'columns-reset-button' - export const mac = thCol('Mac') - export const k8sOwner = thCol('K8S_OwnerObject') - export const ipPort = thCol('AddrPort') - export const protocol = thCol('Proto') - export const icmpType = thCol('IcmpType') - export const icmpCode = thCol('IcmpCode') - export const srcNodeIP = thCol('SrcK8S_HostIP') - export const srcNS = thCol('SrcK8S_Namespace') - export const dstNodeIP = thCol('DstK8S_HostIP') - export const direction = thCol('FlowDirection') - export const bytes = thCol('Bytes') - export const packets = thCol('Packets') - export const recordType = thCol('RecordType') - export const conversationID = thCol('_HashId') - export const flowRTT = thCol('TimeFlowRttMs') - export const dscp = thCol('Dscp') - export const dnsLatency = thCol('DNSLatency') - export const dnsResponseCode = thCol('DNSResponseCode') - export const dnsId = thCol('DNSId') - export const dnsError = thCol('DNSErrNo') - export const dnsName = thCol('DNSName') - export const srcZone = thCol('SrcZone') - export const dstZone = thCol('DstZone') - export const clusterName = thCol('ClusterName') - export const srcNetworkName = thCol('SrcNetworkName') - export const dstNetworkName = thCol('DstNetworkName') + export const mac = '#Mac' + export const k8sOwner = '#K8S_OwnerObject' + export const ipPort = '#AddrPort' + export const protocol = '#Proto' + export const icmpType = '#IcmpType' + export const icmpCode = '#IcmpCode' + export const srcNodeIP = '#SrcK8S_HostIP' + export const srcNS = '#SrcK8S_Namespace' + export const dstNodeIP = '#DstK8S_HostIP' + export const direction = '#FlowDirection' + export const bytes = '#Bytes' + export const packets = '#Packets' + export const recordType = '#RecordType' + export const conversationID = '#_HashId' + export const startTime = '#StartTime' + export const flowRTT = '#TimeFlowRttMs' + export const dscp = '#Dscp' + export const dnsLatency = '#DNSLatency' + export const dnsResponseCode = '#DNSResponseCode' + export const dnsId = '#DNSId' + export const dnsError = '#DNSErrNo' + export const dnsName = '#DNSName' + export const srcZone = '#SrcZone' + export const dstZone = '#DstZone' + export const clusterName = '#ClusterName' + export const srcNetworkName = '#SrcNetworkName' + export const dstNetworkName = '#DstNetworkName' + export const srcSubnetLabel = '#SrcSubnetLabel' + export const dstSubnetLabel = '#DstSubnetLabel' + export const tlsVersion = '#TLSVersion' + export const tlsCipherSuite = '#TLSCipherSuite' + export const tlsGroup = '#TLSGroup' + export const tlsTypes = '#TLSTypes' } export namespace filterSelectors { @@ -268,14 +275,17 @@ export namespace overviewSelectors { export const managePacketDropPanelsList = ['Top X packet dropped state stacked with total (donut or bars and lines)', 'Top X packet dropped cause stacked with total (donut or bars and lines)', 'Top X average dropped bytes rates (donut)', 'Top X dropped bytes rates stacked with total (bars and lines)', 'Top X average dropped packets rates (donut)', 'Top X dropped packets rates stacked with total (bars and lines)'] export const manageDNSTrackingPanelsList = ['Top X DNS response code with total (donut or bars and lines)', 'Top X average DNS latencies with overall (donut or lines)', 'Bottom X minimum DNS latencies with overall (donut or lines)', 'Top X maximum DNS latencies with overall (donut or lines)', 'Top X 90th percentile DNS latencies with overall (donut or lines)'] export const manageFlowRTTPanelsList = ['Top X average TCP smoothed Round Trip Time with overall (donut or lines)', 'Bottom X minimum TCP smoothed Round Trip Time with overall (donut or lines)', 'Top X maximum TCP smoothed Round Trip Time with overall (donut or lines)', 'Top X 90th percentile TCP smoothed Round Trip Time with overall (donut or lines)', 'Top X 99th percentile TCP smoothed Round Trip Time with overall (donut or lines)'] + export const manageTLSTrackingPanelsList = ['TLS usage (donut or lines)', 'TLS usage per version (donut or lines)', 'TLS usage per group (donut or lines)', 'TLS usage per cipher suite (donut or lines)'] export const defaultPanels = ['Top 5 average bytes rates', 'Top 5 bytes rates stacked with total'] export const defaultPacketDropPanels = ['Top 5 packet dropped state stacked with total', 'Top 5 packet dropped cause stacked with total', 'Top 5 average dropped packets rates', 'Top 5 dropped packets rates stacked with total'] export const defaultDNSTrackingPanels = ['Top 5 DNS response code', 'Top 5 average DNS latencies with overall', 'Top 5 90th percentile DNS latencies'] export const defaultFlowRTTPanels = ['Top 5 average TCP smoothed Round Trip Time with overall', 'Bottom 5 minimum TCP smoothed Round Trip Time', 'Top 5 90th percentile TCP smoothed Round Trip Time'] + export const defaultTLSTrackingPanels = ['TLS usage (network flows per second)', 'TLS per version (network flows per second)'] export const allPanels = defaultPanels.concat(['Top 5 average packets rates', 'Top 5 packets rates']) export const allPacketDropPanels = defaultPacketDropPanels.concat(['Top 5 average dropped bytes rates', 'Top 5 dropped bytes rates stacked with total']) export const allDNSTrackingPanels = defaultDNSTrackingPanels.concat(['Bottom 5 minimum DNS latencies', 'Top 5 maximum DNS latencies', 'Top 5 DNS name']) export const allFlowRTTPanels = defaultFlowRTTPanels.concat(['Top 5 maximum TCP smoothed Round Trip Time', 'Top 5 99th percentile TCP smoothed Round Trip Time']) + export const allTLSTrackingPanels = defaultTLSTrackingPanels.concat(['TLS per group (network flows per second)', 'TLS per cipher suite (network flows per second)']) } export const loadTimes = { @@ -329,6 +339,31 @@ Cypress.Commands.add('openColumnsModal', () => { cy.get('#table-column-management').should('exist'); }); +Cypress.Commands.add('selectAndVerifyColumns', (columnSelectors: string[]) => { + // Open the columns modal + cy.openColumnsModal().then(() => { + cy.get(colSelectors.columnsModal).should('be.visible'); + + // Check each column + columnSelectors.forEach(selector => { + cy.get(selector).check(); + }); + + // Save the modal + cy.byTestID(colSelectors.save).click(); + }); + + // Reload to verify column selection persists + cy.reload(); + + // Verify columns appear in table after reload + cy.byTestID('table-composable').should('exist').within(() => { + columnSelectors.forEach(selector => { + cy.get(selector).should('exist'); + }); + }); +}); + Cypress.Commands.add('checkQuerySummary', (metric) => { // parseFloat handles formats: "123 ms", "123+ ms", "1.5k ms", "1.5k+ ms" const num = parseFloat(metric.text()) @@ -354,7 +389,8 @@ Cypress.Commands.add('visitNetflowTrafficTab', (page) => { Cypress.Commands.add('checkNetflowTraffic', (loki = "Enabled") => { // overview panels - cy.get('li.overviewTabButton').should('exist').click({ force: true }) + cy.get('#tabs-container').contains('Overview').click({ force: true }) + netflowPage.waitForLokiQuery() cy.checkPanel(overviewSelectors.defaultPanels) // table view @@ -363,13 +399,13 @@ Cypress.Commands.add('checkNetflowTraffic', (loki = "Enabled") => { cy.get('li.tableTabButton').should('exist').should('have.class', 'pf-m-disabled') } else { - cy.get('li.tableTabButton').should('exist').click() + cy.get('#tabs-container').contains('Traffic flows').click() cy.wait(1000) cy.byTestID("table-composable", { timeout: 60000 }).should('exist') } // topology view - cy.get('li.topologyTabButton').should('exist').click() + cy.get('#tabs-container').contains('Topology').click() cy.wait(2000) cy.get('#drawer', { timeout: 60000 }).should('not.be.empty') }); diff --git a/web/cypress/views/netobserv.ts b/web/cypress/views/netobserv.ts index ca8d51b33..d53ef6fb8 100644 --- a/web/cypress/views/netobserv.ts +++ b/web/cypress/views/netobserv.ts @@ -18,6 +18,7 @@ type FlowCollectorParameter = | 'FlowRTT' | 'DNSTracking' | 'UDNMapping' + | 'TLSTracking' | 'LokiDisabled' | 'LokiWithoutLokiStack' | 'Conversations' @@ -48,6 +49,7 @@ const FIXTURE_PATHS = { dnsTracking: './cypress/fixtures/flowcollector/fc_DNSTracking.yaml', flowRTT: './cypress/fixtures/flowcollector/fc_flowRTT.yaml', udnMapping: './cypress/fixtures/flowcollector/fc_UDN.yaml', + tlsTracking: './cypress/fixtures/flowcollector/fc_TLSTracking.yaml', lokiDisabled: './cypress/fixtures/flowcollector/fc_lokiDisabled.yaml', lokiWithoutLokiStack: './cypress/fixtures/flowcollector/fc_lokiWithoutLokiStack.yaml', conversations: './cypress/fixtures/flowcollector/fc_conversations.yaml', @@ -87,56 +89,50 @@ export const Operator = { return catalogSource }, install: () => { - if (`${Cypress.env('SKIP_NOO_INSTALL')}` == "true") { + if (`${Cypress.env('SKIP_NOO_INSTALL')}` === "true") { return null } - var catalogSource = Operator.install_catalogsource() + // Check operator status via CLI + cy.adminCLI('oc get csv -n openshift-netobserv-operator --no-headers -o custom-columns=":metadata.name" 2>/dev/null || echo "NotFound"') + .then((result: any) => { + const stdout = result.stdout ? result.stdout.trim() : '' + const csvName = stdout.split('\n').find((line: string) => + line.includes('netobserv-operator') || line.includes('network-observability-operator') + ) - cy.visit(`/k8s/ns/openshift-netobserv-operator/operators.coreos.com~v1alpha1~ClusterServiceVersion`); - // if user still does not have admin access - // try few more times - cy.contains("openshift-netobserv-operator").should('be.visible') - cy.get("div.loading-box").should('be.visible').then(() => { - for (let retries = 0; retries <= 15; retries++) { - cy.get("div.loading-box").should('be.visible') - if (Cypress.$('.co-disabled').length == 1) { - cy.log(`user does not have access ${retries}`) - cy.wait(5000) - cy.reload(true) - } - else { - break; - } - } - }) - // don't install operator if its already installed - cy.get("div.loading-box").should('be.visible').then(loading => { - if (Cypress.$('td[role="gridcell"]').length == 0) { - if (catSrc == "upstream") { - // metrics checkbox is not available for upstream operators - operatorHubPage.install("netobserv-operator", catalogSource, false) + if (csvName && !stdout.includes('NotFound') && !stdout.includes('No resources found')) { + // CSV exists, check if it's in Succeeded state + cy.adminCLI(`oc wait csv ${csvName.trim()} -n openshift-netobserv-operator --for=jsonpath='{.status.phase}'=Succeeded --timeout=120s`) + .then(() => { + cy.log('NetObserv Operator already installed') + }) } else { - operatorHubPage.install("netobserv-operator", catalogSource, true) + cy.log('Installing NetObserv Operator') + var catalogSource = Operator.install_catalogsource() + + if (catSrc === "upstream") { + // metrics checkbox is not available for upstream operators + operatorHubPage.install("netobserv-operator", catalogSource, false) + } else { + operatorHubPage.install("netobserv-operator", catalogSource, true) + } } - } }) }, visitFlowcollector: () => { - cy.visit('k8s/ns/openshift-netobserv-operator/operators.coreos.com~v1alpha1~ClusterServiceVersion') - const selector = '[data-test-operator-row="' + Operator.name() + '"]' + cy.adminCLI('oc get csv -n openshift-netobserv-operator --no-headers -o custom-columns=":metadata.name" 2>/dev/null || echo "NotFound"') + .then((result: any) => { + const stdout = result.stdout ? result.stdout.trim() : '' + const csvName = stdout.split('\n').find((line: string) => + line.includes('netobserv-operator') || line.includes('network-observability-operator') + ) - cy.get(selector, { timeout: 30000 }) - .should('exist') - .then($el => { - const href = $el.attr('href') - cy.visit(href as string) - }) - - cy.contains('Flow Collector') - .should('exist') - .then($el => { - const href = $el.attr('href') - cy.visit(href as string) + if (csvName && !stdout.includes('NotFound') && !stdout.includes('No resources found')) { + cy.visit(`/k8s/ns/openshift-netobserv-operator/operators.coreos.com~v1alpha1~ClusterServiceVersion/${csvName.trim()}/flows.netobserv.io~v1beta2~FlowCollector`) + cy.get('div.loading-box__loaded', { timeout: 30000 }).should('exist') + } else { + throw new Error('NetObserv CSV not found') + } }) }, createFlowcollector: (parameters?: FlowCollectorParameter) => { @@ -152,7 +148,7 @@ export const Operator = { }) // don't create flowcollector if already exists cy.get('div.loading-box__loaded', { timeout: 60000 }).should('be.visible').then(() => { - if (Cypress.$('td[role="gridcell"]').length == 0) { + if (Cypress.$('td[role="gridcell"]').length === 0) { cy.log("Deploying flowcollector") switch (parameters) { case "PacketDrop": @@ -167,6 +163,9 @@ export const Operator = { case "UDNMapping": cy.deployFlowcollectorFromFixture(FIXTURE_PATHS.udnMapping) break; + case "TLSTracking": + cy.deployFlowcollectorFromFixture(FIXTURE_PATHS.tlsTracking) + break; case "LokiDisabled": cy.deployFlowcollectorFromFixture(FIXTURE_PATHS.lokiDisabled) break; @@ -191,6 +190,8 @@ export const Operator = { case "StaticPlugin": // Flowcollector deployed with PacketDrop enabled Operator.deployFlowcollectorFromUI() + // Navigate back to FlowCollector list page after UI deployment + Operator.visitFlowcollector() break; case "NetworkAlertHealth": // Flowcollector deployed with DNSTracking enabled @@ -211,15 +212,13 @@ export const Operator = { cy.adminCLI(`oc wait --for=condition=Ready pod -l app=loki -n ${project} --timeout=180s`) } - Operator.visitFlowcollector() - // Wait for page to stabilize after navigation - cy.get('div.loading-box__loaded', { timeout: 30000 }).should('exist') - + // Check FlowCollector status and wait for plugin pod to be Ready if (parameters !== "LokiWithoutLokiStack") { // Check status in the FlowCollector 'cluster' row specifically cy.contains('tr', 'cluster').within(() => { cy.byTestID('status-text', { timeout: 60000 }).should('contain.text', 'Ready') }) + cy.adminCLI(`oc wait --for=condition=Ready pod -l app=netobserv-plugin -n ${project} --timeout=180s`) } } })