--- /dev/null
+../.qa
\ No newline at end of file
--- /dev/null
+../.qa
\ No newline at end of file
--- /dev/null
+.qa/distros/supported/centos_latest.yaml
\ No newline at end of file
--- /dev/null
+roles:
+# 3 osd roles on host.a is required for cephadm task. It checks if the cluster is healthy.
+# More daemons will be deployed on both hosts in e2e tests.
+- - host.a
+ - osd.0
+ - osd.1
+ - osd.2
+ - mon.a
+ - mgr.a
+ - client.0
+- - host.b
+ - client.1
+tasks:
+- install:
+- cephadm:
+- workunit:
+ clients:
+ client.1:
+ - cephadm/create_iscsi_disks.sh
+- workunit:
+ clients:
+ client.0:
+ - cephadm/test_dashboard_e2e.sh
--- /dev/null
+#!/bin/bash -ex
+# Create some file-backed iSCSI targets and attach them locally.
+
+# Exit if it's not CentOS
+if ! grep -q rhel /etc/*-release; then
+ echo "The script only supports CentOS."
+ exit 1
+fi
+
+[ -z "$SUDO" ] && SUDO=sudo
+
+# 15 GB
+DISK_FILE_SIZE="16106127360"
+
+$SUDO yum install -y targetcli iscsi-initiator-utils
+
+TARGET_NAME="iqn.2003-01.org.linux-iscsi.$(hostname).x8664:sn.foobar"
+$SUDO targetcli /iscsi create ${TARGET_NAME}
+$SUDO targetcli /iscsi/${TARGET_NAME}/tpg1/portals delete 0.0.0.0 3260
+$SUDO targetcli /iscsi/${TARGET_NAME}/tpg1/portals create 127.0.0.1 3260
+$SUDO targetcli /iscsi/${TARGET_NAME}/tpg1 set attribute generate_node_acls=1
+$SUDO targetcli /iscsi/${TARGET_NAME}/tpg1 set attribute demo_mode_write_protect=0
+
+for i in $(seq 3); do
+ # Create truncated files, and add them as luns
+ DISK_FILE="/tmp/disk${i}"
+ $SUDO truncate --size ${DISK_FILE_SIZE} ${DISK_FILE}
+
+ $SUDO targetcli /backstores/fileio create "lun${i}" ${DISK_FILE}
+ $SUDO targetcli /iscsi/${TARGET_NAME}/tpg1/luns create "/backstores/fileio/lun${i}"
+done
+
+$SUDO iscsiadm -m discovery -t sendtargets -p 127.0.0.1
+$SUDO iscsiadm -m node -p 127.0.0.1 -T ${TARGET_NAME} -l
--- /dev/null
+#!/bin/bash -ex
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+DASHBOARD_FRONTEND_DIR=${SCRIPT_DIR}/../../../src/pybind/mgr/dashboard/frontend
+
+[ -z "$SUDO" ] && SUDO=sudo
+
+install_common () {
+ if grep -q debian /etc/*-release; then
+ $SUDO apt-get update
+ $SUDO apt-get install -y jq npm
+ elif grep -q rhel /etc/*-release; then
+ $SUDO yum install -y jq npm
+ else
+ echo "Unsupported distribution."
+ exit 1
+ fi
+}
+
+install_chrome () {
+ if grep -q debian /etc/*-release; then
+ $SUDO bash -c 'echo "deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list'
+ curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | $SUDO apt-key add -
+ $SUDO apt-get update
+ $SUDO apt-get install -y google-chrome-stable
+ $SUDO apt-get install -y xvfb
+ $SUDO rm /etc/apt/sources.list.d/google-chrome.list
+ elif grep -q rhel /etc/*-release; then
+ $SUDO dd of=/etc/yum.repos.d/google-chrome.repo status=none <<EOF
+[google-chrome]
+name=google-chrome
+baseurl=https://dl.google.com/linux/chrome/rpm/stable/\$basearch
+enabled=1
+gpgcheck=1
+gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
+EOF
+ $SUDO yum install -y google-chrome-stable
+ $SUDO rm /etc/yum.repos.d/google-chrome.repo
+ # Cypress dependencies
+ $SUDO yum install -y xorg-x11-server-Xvfb gtk2-devel gtk3-devel libnotify-devel GConf2 nss.x86_64 libXScrnSaver alsa-lib
+ else
+ echo "Unsupported distribution."
+ exit 1
+ fi
+}
+
+cypress_run () {
+ local specs="$1"
+ local timeout="$2"
+ local override_config="ignoreTestFiles=*.po.ts,testFiles=${specs}"
+
+ if [ x"$timeout" != "x" ]; then
+ override_config="${override_config},defaultCommandTimeout=${timeout}"
+ fi
+ npx cypress run --browser chrome --headless --config "$override_config"
+}
+
+install_common
+install_chrome
+
+CYPRESS_BASE_URL=$(ceph mgr services | jq -r .dashboard)
+export CYPRESS_BASE_URL
+
+cd $DASHBOARD_FRONTEND_DIR
+
+# This is required for Cypress to understand typescript
+npm ci --unsafe-perm
+npx cypress verify
+npx cypress info
+
+# Remove device_health_metrics pool
+# Low pg count causes OSD removal failure.
+ceph device monitoring off
+ceph tell mon.\* injectargs '--mon-allow-pool-delete=true'
+ceph osd pool rm device_health_metrics device_health_metrics --yes-i-really-really-mean-it
+
+# Take `orch device ls` as ground truth.
+ceph orch device ls --refresh
+sleep 10 # the previous call is asynchronous
+ceph orch device ls --format=json | tee cypress/fixtures/orchestrator/inventory.json
+
+ceph dashboard ac-user-set-password admin admin --force-password
+
+# Run Dashboard e2e tests.
+# These tests are designed with execution order in mind, since orchestrator operations
+# are likely to change cluster state, we can't just run tests in arbitrarily order.
+# See /ceph/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/ folder.
+find cypress # List all specs
+
+cypress_run "orchestrator/01-hosts.e2e-spec.ts"
+
+# Hosts are removed and added in the previous step. Do a refresh again.
+ceph orch device ls --refresh
+sleep 10
+ceph orch device ls --format=json | tee cypress/fixtures/orchestrator/inventory.json
+
+cypress_run "orchestrator/02-hosts-inventory.e2e-spec.ts"
+cypress_run "orchestrator/03-inventory.e2e-spec.ts"
+cypress_run "orchestrator/04-osds.e2e-spec.ts" 300000
{
"baseUrl": "http://localhost:4200/",
"ignoreTestFiles": [
- "*.po.ts"
+ "*.po.ts",
+ "**/orchestrator/**"
],
"supportFile": "cypress/support/index.ts",
"video": false,
--- /dev/null
+[
+ {
+ "addr": "node1",
+ "devices": [
+ {
+ "available": false,
+ "device_id": "",
+ "human_readable_type": "hdd",
+ "lvs": [],
+ "path": "/dev/vda",
+ "rejected_reasons": ["locked"],
+ "sys_api": {
+ "human_readable_size": "42.00 GB",
+ "locked": 1,
+ "model": "",
+ "nr_requests": "256",
+ "partitions": {
+ "vda1": {
+ "holders": [],
+ "human_readable_size": "2.00 MB",
+ "sectors": "4096",
+ "sectorsize": 512,
+ "size": 2097152.0,
+ "start": "2048"
+ },
+ "vda2": {
+ "holders": [],
+ "human_readable_size": "20.00 MB",
+ "sectors": "40960",
+ "sectorsize": 512,
+ "size": 20971520.0,
+ "start": "6144"
+ },
+ "vda3": {
+ "holders": [],
+ "human_readable_size": "41.98 GB",
+ "sectors": "88033247",
+ "sectorsize": 512,
+ "size": 45073022464.0,
+ "start": "47104"
+ }
+ },
+ "path": "/dev/vda",
+ "removable": "0",
+ "rev": "",
+ "ro": "0",
+ "rotational": "1",
+ "sas_address": "",
+ "sas_device_handle": "",
+ "scheduler_mode": "mq-deadline",
+ "sectors": 0,
+ "sectorsize": "512",
+ "size": 45097156608.0,
+ "support_discard": "512",
+ "vendor": "0x1af4"
+ }
+ },
+ {
+ "available": false,
+ "device_id": "641526",
+ "human_readable_type": "hdd",
+ "lvs": [
+ {
+ "block_uuid": "355c2I-e5kg-WWeT-bOsI-0Ez5-sfb7-7TZyE4",
+ "cluster_fsid": "68a32428-e2ab-11ea-9d25-525400ef4c6e",
+ "cluster_name": "ceph",
+ "name": "osd-data-3de18e23-8849-494c-83b0-458d97d32d72",
+ "osd_fsid": "a438ac13-f1bd-412c-9626-e2f063dbbf94",
+ "osd_id": "0",
+ "osdspec_affinity": "dashboard-admin-1597903910143",
+ "type": "block"
+ }
+ ],
+ "path": "/dev/vdb",
+ "rejected_reasons": ["locked", "LVM detected", "Insufficient space (<5GB) on vgs"],
+ "sys_api": {
+ "human_readable_size": "8.00 GB",
+ "locked": 1,
+ "model": "",
+ "nr_requests": "256",
+ "partitions": {},
+ "path": "/dev/vdb",
+ "removable": "0",
+ "rev": "",
+ "ro": "0",
+ "rotational": "1",
+ "sas_address": "",
+ "sas_device_handle": "",
+ "scheduler_mode": "mq-deadline",
+ "sectors": 0,
+ "sectorsize": "512",
+ "size": 8589934592.0,
+ "support_discard": "512",
+ "vendor": "0x1af4"
+ }
+ },
+ {
+ "available": false,
+ "device_id": "467047",
+ "human_readable_type": "hdd",
+ "lvs": [
+ {
+ "block_uuid": "iGC2VU-MSTt-ZP05-kKCP-5EtO-F1Y3-DYAAeb",
+ "cluster_fsid": "68a32428-e2ab-11ea-9d25-525400ef4c6e",
+ "cluster_name": "ceph",
+ "name": "osd-data-2031893c-c83b-4ff0-bfa1-de548044f707",
+ "osd_fsid": "6f544fc4-a3ea-40f9-9c48-69b5ee866709",
+ "osd_id": "1",
+ "osdspec_affinity": "dashboard-admin-1597903910143",
+ "type": "block"
+ }
+ ],
+ "path": "/dev/vdc",
+ "rejected_reasons": ["locked", "LVM detected", "Insufficient space (<5GB) on vgs"],
+ "sys_api": {
+ "human_readable_size": "8.00 GB",
+ "locked": 1,
+ "model": "",
+ "nr_requests": "256",
+ "partitions": {},
+ "path": "/dev/vdc",
+ "removable": "0",
+ "rev": "",
+ "ro": "0",
+ "rotational": "1",
+ "sas_address": "",
+ "sas_device_handle": "",
+ "scheduler_mode": "mq-deadline",
+ "sectors": 0,
+ "sectorsize": "512",
+ "size": 8589934592.0,
+ "support_discard": "512",
+ "vendor": "0x1af4"
+ }
+ },
+ {
+ "available": false,
+ "device_id": "900807",
+ "human_readable_type": "hdd",
+ "lvs": [
+ {
+ "block_uuid": "nO2VSn-IbXr-pxnx-ieXx-kIxk-B4hB-BM6ADc",
+ "cluster_fsid": "68a32428-e2ab-11ea-9d25-525400ef4c6e",
+ "cluster_name": "ceph",
+ "name": "osd-data-537f7b60-5887-440e-80c7-759c028db12d",
+ "osd_fsid": "adeddd37-5cc9-406a-88e5-2add3f81d089",
+ "osd_id": "2",
+ "osdspec_affinity": "dashboard-admin-1597903910143",
+ "type": "block"
+ }
+ ],
+ "path": "/dev/vdd",
+ "rejected_reasons": ["locked", "LVM detected", "Insufficient space (<5GB) on vgs"],
+ "sys_api": {
+ "human_readable_size": "8.00 GB",
+ "locked": 1,
+ "model": "",
+ "nr_requests": "256",
+ "partitions": {},
+ "path": "/dev/vdd",
+ "removable": "0",
+ "rev": "",
+ "ro": "0",
+ "rotational": "1",
+ "sas_address": "",
+ "sas_device_handle": "",
+ "scheduler_mode": "mq-deadline",
+ "sectors": 0,
+ "sectorsize": "512",
+ "size": 8589934592.0,
+ "support_discard": "512",
+ "vendor": "0x1af4"
+ }
+ },
+ {
+ "available": false,
+ "device_id": "757404",
+ "human_readable_type": "hdd",
+ "lvs": [
+ {
+ "block_uuid": "3YSAlw-VMeK-XfUK-rbOB-IKD1-Z9ZI-hUzlDe",
+ "cluster_fsid": "68a32428-e2ab-11ea-9d25-525400ef4c6e",
+ "cluster_name": "ceph",
+ "name": "osd-data-15b39d59-f259-4e93-adc6-bdac7d490d88",
+ "osd_fsid": "840a7138-88e2-4ecb-b88d-6fa2d04d88e7",
+ "osd_id": "3",
+ "osdspec_affinity": "dashboard-admin-1597903910143",
+ "type": "block"
+ }
+ ],
+ "path": "/dev/vde",
+ "rejected_reasons": ["locked", "LVM detected", "Insufficient space (<5GB) on vgs"],
+ "sys_api": {
+ "human_readable_size": "8.00 GB",
+ "locked": 1,
+ "model": "",
+ "nr_requests": "256",
+ "partitions": {},
+ "path": "/dev/vde",
+ "removable": "0",
+ "rev": "",
+ "ro": "0",
+ "rotational": "1",
+ "sas_address": "",
+ "sas_device_handle": "",
+ "scheduler_mode": "mq-deadline",
+ "sectors": 0,
+ "sectorsize": "512",
+ "size": 8589934592.0,
+ "support_discard": "512",
+ "vendor": "0x1af4"
+ }
+ }
+ ],
+ "labels": [],
+ "name": "node1"
+ },
+ {
+ "addr": "node2",
+ "devices": [
+ {
+ "available": true,
+ "device_id": "115432",
+ "human_readable_type": "hdd",
+ "lvs": [],
+ "path": "/dev/vdb",
+ "rejected_reasons": [],
+ "sys_api": {
+ "human_readable_size": "8.00 GB",
+ "locked": 0,
+ "model": "",
+ "nr_requests": "256",
+ "partitions": {},
+ "path": "/dev/vdb",
+ "removable": "0",
+ "rev": "",
+ "ro": "0",
+ "rotational": "1",
+ "sas_address": "",
+ "sas_device_handle": "",
+ "scheduler_mode": "mq-deadline",
+ "sectors": 0,
+ "sectorsize": "512",
+ "size": 8589934592.0,
+ "support_discard": "512",
+ "vendor": "0x1af4"
+ }
+ },
+ {
+ "available": true,
+ "device_id": "937699",
+ "human_readable_type": "hdd",
+ "lvs": [],
+ "path": "/dev/vdc",
+ "rejected_reasons": [],
+ "sys_api": {
+ "human_readable_size": "8.00 GB",
+ "locked": 0,
+ "model": "",
+ "nr_requests": "256",
+ "partitions": {},
+ "path": "/dev/vdc",
+ "removable": "0",
+ "rev": "",
+ "ro": "0",
+ "rotational": "1",
+ "sas_address": "",
+ "sas_device_handle": "",
+ "scheduler_mode": "mq-deadline",
+ "sectors": 0,
+ "sectorsize": "512",
+ "size": 8589934592.0,
+ "support_discard": "512",
+ "vendor": "0x1af4"
+ }
+ },
+ {
+ "available": true,
+ "device_id": "854127",
+ "human_readable_type": "hdd",
+ "lvs": [],
+ "path": "/dev/vdd",
+ "rejected_reasons": [],
+ "sys_api": {
+ "human_readable_size": "8.00 GB",
+ "locked": 0,
+ "model": "",
+ "nr_requests": "256",
+ "partitions": {},
+ "path": "/dev/vdd",
+ "removable": "0",
+ "rev": "",
+ "ro": "0",
+ "rotational": "1",
+ "sas_address": "",
+ "sas_device_handle": "",
+ "scheduler_mode": "mq-deadline",
+ "sectors": 0,
+ "sectorsize": "512",
+ "size": 8589934592.0,
+ "support_discard": "512",
+ "vendor": "0x1af4"
+ }
+ },
+ {
+ "available": true,
+ "device_id": "122615",
+ "human_readable_type": "hdd",
+ "lvs": [],
+ "path": "/dev/vde",
+ "rejected_reasons": [],
+ "sys_api": {
+ "human_readable_size": "8.00 GB",
+ "locked": 0,
+ "model": "",
+ "nr_requests": "256",
+ "partitions": {},
+ "path": "/dev/vde",
+ "removable": "0",
+ "rev": "",
+ "ro": "0",
+ "rotational": "1",
+ "sas_address": "",
+ "sas_device_handle": "",
+ "scheduler_mode": "mq-deadline",
+ "sectors": 0,
+ "sectorsize": "512",
+ "size": 8589934592.0,
+ "support_discard": "512",
+ "vendor": "0x1af4"
+ }
+ },
+ {
+ "available": false,
+ "device_id": "",
+ "human_readable_type": "hdd",
+ "lvs": [],
+ "path": "/dev/vda",
+ "rejected_reasons": ["locked"],
+ "sys_api": {
+ "human_readable_size": "42.00 GB",
+ "locked": 1,
+ "model": "",
+ "nr_requests": "256",
+ "partitions": {
+ "vda1": {
+ "holders": [],
+ "human_readable_size": "2.00 MB",
+ "sectors": "4096",
+ "sectorsize": 512,
+ "size": 2097152.0,
+ "start": "2048"
+ },
+ "vda2": {
+ "holders": [],
+ "human_readable_size": "20.00 MB",
+ "sectors": "40960",
+ "sectorsize": 512,
+ "size": 20971520.0,
+ "start": "6144"
+ },
+ "vda3": {
+ "holders": [],
+ "human_readable_size": "41.98 GB",
+ "sectors": "88033247",
+ "sectorsize": 512,
+ "size": 45073022464.0,
+ "start": "47104"
+ }
+ },
+ "path": "/dev/vda",
+ "removable": "0",
+ "rev": "",
+ "ro": "0",
+ "rotational": "1",
+ "sas_address": "",
+ "sas_device_handle": "",
+ "scheduler_mode": "mq-deadline",
+ "sectors": 0,
+ "sectorsize": "512",
+ "size": 45097156608.0,
+ "support_discard": "512",
+ "vendor": "0x1af4"
+ }
+ }
+ ],
+ "labels": [],
+ "name": "node2"
+ }
+]
});
it('should verify that selected footer increases when an entry is clicked', () => {
- configuration.getTableSelectedCount().should('eq', 1);
+ configuration.getTableCount('selected').should('eq', 1);
});
it('should check that details table opens (w/o tab header)', () => {
it('should show only modified configurations', () => {
configuration.filterTable('Modified', 'yes');
- configuration.getTableFoundCount().should('eq', 1);
+ configuration.getTableCount('found').should('eq', 1);
});
it('should hide all modified configurations', () => {
configuration.filterTable('Modified', 'no');
- configuration.getTableFoundCount().should('gt', 1);
+ configuration.getTableCount('found').should('gt', 1);
});
});
});
import { PageHelper } from '../page-helper.po';
+const pages = {
+ index: { url: '#/hosts', id: 'cd-hosts' },
+ create: { url: '#/hosts/create', id: 'cd-host-form' }
+};
+
export class HostsPageHelper extends PageHelper {
- pages = { index: { url: '#/hosts', id: 'cd-hosts' } };
+ pages = pages;
+
+ columnIndex = {
+ hostname: 2,
+ labels: 4
+ };
check_for_host() {
- this.getTableTotalCount().should('not.be.eq', 0);
+ this.getTableCount('total').should('not.be.eq', 0);
}
// function that checks all services links work for first
expect(links_tested).gt(0);
});
}
+
+ @PageHelper.restrictTo(pages.index.url)
+ clickHostTab(hostname: string, tabName: string) {
+ this.getExpandCollapseElement(hostname).click();
+ cy.get('cd-host-details').within(() => {
+ this.getTab(tabName).click();
+ });
+ }
+
+ @PageHelper.restrictTo(pages.create.url)
+ add(hostname: string, exist?: boolean) {
+ cy.get(`${this.pages.create.id}`).within(() => {
+ cy.get('#hostname').type(hostname);
+ cy.get('cd-submit-button').click();
+ });
+ if (exist) {
+ cy.get('#hostname').should('have.class', 'ng-invalid');
+ } else {
+ // back to host list
+ cy.get(`${this.pages.index.id}`);
+ }
+ }
+
+ @PageHelper.restrictTo(pages.index.url)
+ checkExist(hostname: string, exist: boolean) {
+ this.getTableCell(this.columnIndex.hostname, hostname).should(($elements) => {
+ const hosts = $elements.map((_, el) => el.textContent).get();
+ if (exist) {
+ expect(hosts).to.include(hostname);
+ } else {
+ expect(hosts).to.not.include(hostname);
+ }
+ });
+ }
+
+ @PageHelper.restrictTo(pages.index.url)
+ delete(hostname: string) {
+ super.delete(hostname, this.columnIndex.hostname);
+ }
+
+ // Add or remove labels on a host, then verify labels in the table
+ @PageHelper.restrictTo(pages.index.url)
+ editLabels(hostname: string, labels: string[], add: boolean) {
+ this.getTableCell(this.columnIndex.hostname, hostname).click();
+ this.clickActionButton('edit');
+
+ // add or remove label badges
+ if (add) {
+ cy.get('cd-modal').find('.select-menu-edit').click();
+ for (const label of labels) {
+ cy.contains('cd-modal .badge', new RegExp(`^${label}$`)).should('not.exist');
+ cy.get('.popover-body input').type(`${label}{enter}`);
+ }
+ } else {
+ for (const label of labels) {
+ cy.contains('cd-modal .badge', new RegExp(`^${label}$`))
+ .find('.badge-remove')
+ .click();
+ }
+ }
+ cy.get('cd-modal cd-submit-button').click();
+
+ // Verify labels are added or removed from Labels column
+ // First find row with hostname, then find labels in the row
+ this.getTableCell(this.columnIndex.hostname, hostname)
+ .parent()
+ .find(`datatable-body-cell:nth-child(${this.columnIndex.labels})`)
+ .should(($ele) => {
+ const newLabels = $ele.text().split(', ');
+ for (const label of labels) {
+ if (add) {
+ expect(newLabels).to.include(label);
+ } else {
+ expect(newLabels).to.not.include(label);
+ }
+ }
+ });
+ }
}
--- /dev/null
+import { PageHelper } from '../page-helper.po';
+
+const pages = {
+ index: { url: '#/inventory', id: 'cd-inventory' }
+};
+
+export class InventoryPageHelper extends PageHelper {
+ pages = pages;
+
+ identify() {
+ // Nothing we can do, just verify the form is there
+ this.getFirstTableCell().click();
+ cy.contains('cd-table-actions button', 'Identify').click();
+ cy.get('cd-modal').within(() => {
+ cy.get('#duration').select('15 minutes');
+ cy.get('#duration').select('10 minutes');
+ cy.get('cd-back-button').click();
+ });
+ cy.get('cd-modal').should('not.visible');
+ cy.get(`${this.pages.index.id}`);
+ }
+}
describe('check existence of fields on OSD page', () => {
it('should check that number of rows and count in footer match', () => {
- osds.getTableTotalCount().then((text) => {
+ osds.getTableCount('total').then((text) => {
osds.getTableRows().its('length').should('equal', text);
});
});
});
it('should verify that selected footer increases', () => {
- osds.getTableSelectedCount().should('equal', 1);
+ osds.getTableCount('selected').should('equal', 1);
});
it('should show the correct text for the tab labels', () => {
import { PageHelper } from '../page-helper.po';
+const pages = {
+ index: { url: '#/osd', id: 'cd-osd-list' },
+ create: { url: '#/osd/create', id: 'cd-osd-form' }
+};
+
export class OSDsPageHelper extends PageHelper {
- pages = { index: { url: '#/osd', id: 'cd-osd-list' } };
+ pages = pages;
+
+ columnIndex = {
+ id: 4,
+ status: 5
+ };
+
+ @PageHelper.restrictTo(pages.create.url)
+ create(deviceType: 'hdd' | 'ssd') {
+ // Click Primary devices Add button
+ cy.get('cd-osd-devices-selection-groups[name="Primary"]').as('primaryGroups');
+ cy.get('@primaryGroups').find('button').click();
+
+ // Select all devices with `deviceType`
+ cy.get('cd-osd-devices-selection-modal').within(() => {
+ cy.get('.modal-footer .tc_submitButton').as('addButton').should('be.disabled');
+ this.filterTable('Type', deviceType);
+ cy.get('@addButton').click();
+ });
+
+ cy.get('@primaryGroups').within(() => {
+ this.getTableCount('total').as('newOSDCount');
+ });
+
+ cy.get(`${pages.create.id} .card-footer .tc_submitButton`).click();
+ cy.get(`cd-osd-creation-preview-modal .modal-footer .tc_submitButton`).click();
+ }
+
+ getRowByID(id: number) {
+ return this.getTableCell(this.columnIndex.id, `${id}`).parent();
+ }
+
+ @PageHelper.restrictTo(pages.index.url)
+ checkStatus(id: number, status: string[]) {
+ this.getRowByID(id)
+ .find(`datatable-body-cell:nth-child(${this.columnIndex.status}) .badge`)
+ .should(($ele) => {
+ const allStatus = $ele.toArray().map((v) => v.innerText);
+ for (const s of status) {
+ expect(allStatus).to.include(s);
+ }
+ });
+ }
+
+ @PageHelper.restrictTo(pages.index.url)
+ ensureNoOsd(id: number) {
+ cy.get(`datatable-body-row datatable-body-cell:nth-child(${this.columnIndex.id})`).should(
+ ($ele) => {
+ const osdIds = $ele.toArray().map((v) => v.innerText);
+ expect(osdIds).to.not.include(`${id}`);
+ }
+ );
+ }
+
+ @PageHelper.restrictTo(pages.index.url)
+ deleteByIDs(osdIds: number[], replace?: boolean) {
+ this.getTableRows().each(($el) => {
+ const rowOSD = Number(
+ $el.find('datatable-body-cell .datatable-body-cell-label').get(this.columnIndex.id - 1)
+ .textContent
+ );
+ if (osdIds.includes(rowOSD)) {
+ cy.wrap($el).click();
+ }
+ });
+ this.clickActionButton('delete');
+ if (replace) {
+ cy.get('cd-modal label[for="preserve"]').click();
+ }
+ cy.get('cd-modal label[for="confirmation"]').click();
+ cy.contains('cd-modal button', 'Delete').click();
+ cy.get('cd-modal').should('not.exist');
+ }
}
--- /dev/null
+import { HostsPageHelper } from '../cluster/hosts.po';
+
+describe('Hosts page', () => {
+ const hosts = new HostsPageHelper();
+
+ beforeEach(() => {
+ cy.login();
+ hosts.navigateTo();
+ });
+
+ describe('when Orchestrator is available', () => {
+ beforeEach(function () {
+ cy.fixture('orchestrator/inventory.json').as('hosts');
+ });
+
+ it('should not add an exsiting host', function () {
+ const hostname = Cypress._.sample(this.hosts).name;
+ hosts.navigateTo('create');
+ hosts.add(hostname, true);
+ });
+
+ it('should delete a host and add it back', function () {
+ const host = Cypress._.last(this.hosts)['name'];
+ hosts.delete(host);
+
+ // add it back
+ hosts.navigateTo('create');
+ hosts.add(host);
+ hosts.checkExist(host, true);
+ });
+
+ it('should display inventory', function () {
+ for (const host of this.hosts) {
+ hosts.clickHostTab(host.name, 'Inventory');
+ cy.get('cd-host-details').within(() => {
+ hosts.getTableCount('total').should('be.gte', 0);
+ });
+ }
+ });
+
+ it('should display daemons', function () {
+ for (const host of this.hosts) {
+ hosts.clickHostTab(host.name, 'Daemons');
+ cy.get('cd-host-details').within(() => {
+ hosts.getTableCount('total').should('be.gte', 0);
+ });
+ }
+ });
+
+ it('should edit host labels', function () {
+ const hostname = Cypress._.sample(this.hosts).name;
+ const labels = ['foo', 'bar'];
+ hosts.editLabels(hostname, labels, true);
+ hosts.editLabels(hostname, labels, false);
+ });
+ });
+});
--- /dev/null
+import { HostsPageHelper } from '../cluster/hosts.po';
+
+describe('Hosts page', () => {
+ const hosts = new HostsPageHelper();
+
+ beforeEach(() => {
+ cy.login();
+ hosts.navigateTo();
+ });
+
+ describe('when Orchestrator is available', () => {
+ beforeEach(function () {
+ cy.fixture('orchestrator/inventory.json').as('hosts');
+ });
+
+ it('should display correct inventory', function () {
+ for (const host of this.hosts) {
+ hosts.clickHostTab(host.name, 'Inventory');
+ cy.get('cd-host-details').within(() => {
+ hosts.getTableCount('total').should('be.eq', host.devices.length);
+ });
+ }
+ });
+ });
+});
--- /dev/null
+import { InventoryPageHelper } from '../cluster/inventory.po';
+
+describe('Inventory page', () => {
+ const inventory = new InventoryPageHelper();
+
+ beforeEach(() => {
+ cy.login();
+ inventory.navigateTo();
+ });
+
+ it('should have correct devices', () => {
+ cy.fixture('orchestrator/inventory.json').then((hosts) => {
+ const totalDiskCount = Cypress._.sumBy(hosts, 'devices.length');
+ inventory.getTableCount('total').should('be.eq', totalDiskCount);
+ for (const host of hosts) {
+ inventory.filterTable('Hostname', host['name']);
+ inventory.getTableCount('found').should('be.eq', host.devices.length);
+ }
+ });
+ });
+
+ it('should identify device', () => {
+ inventory.identify();
+ });
+});
--- /dev/null
+import { OSDsPageHelper } from '../cluster/osds.po';
+import { DashboardPageHelper } from '../ui/dashboard.po';
+
+describe('OSDs page', () => {
+ const osds = new OSDsPageHelper();
+ const dashboard = new DashboardPageHelper();
+
+ beforeEach(() => {
+ cy.login();
+ osds.navigateTo();
+ });
+
+ describe('when Orchestrator is available', () => {
+ it('should create and delete OSDs', () => {
+ osds.getTableCount('total').as('initOSDCount');
+ osds.navigateTo('create');
+ osds.create('hdd');
+
+ cy.get('@newOSDCount').then((newCount) => {
+ cy.get('@initOSDCount').then((oldCount) => {
+ const expectedCount = Number(oldCount) + Number(newCount);
+
+ // check total rows
+ osds.expectTableCount('total', expectedCount);
+
+ // landing page is easier to check OSD status
+ dashboard.navigateTo();
+ dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} total`);
+ dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} up`);
+ dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} in`);
+
+ expect(Number(newCount)).to.be.gte(2);
+ // Delete the first OSD we created
+ osds.navigateTo();
+ const deleteOsdId = Number(oldCount);
+ osds.deleteByIDs([deleteOsdId], false);
+ osds.ensureNoOsd(deleteOsdId);
+
+ // Replace the second OSD we created
+ const replaceID = Number(oldCount) + 1;
+ osds.deleteByIDs([replaceID], true);
+ // deleting OSDs doesn't work in cephadm right now, skip checking
+ osds.checkStatus(replaceID, ['destroyed']);
+ });
+ });
+ });
+ });
+});
return cy.get('.nav.nav-tabs li');
}
+ getTab(tabName: string) {
+ return cy.contains('.nav.nav-tabs li', new RegExp(`^${tabName}$`));
+ }
+
getTabText(index: number) {
return this.getTabs().its(index).text();
}
return cy.get('cd-table .dataTables_wrapper');
}
- getTableTotalCount() {
- this.waitDataTableToLoad();
-
- return cy.get('.datatable-footer-inner .page-count span').then(($elem) => {
- const text = $elem
- .filter((_i, e) => e.innerText.includes('total'))
- .first()
- .text();
-
- return Number(text.match(/(\d+)\s+total/)[1]);
- });
+ private getTableCountSpan(spanType: 'selected' | 'found' | 'total') {
+ return cy.contains('.datatable-footer-inner .page-count span', spanType);
}
- getTableSelectedCount() {
+ // Get 'selected', 'found', or 'total' row count of a table.
+ getTableCount(spanType: 'selected' | 'found' | 'total') {
this.waitDataTableToLoad();
-
- return cy.get('.datatable-footer-inner .page-count span').then(($elem) => {
+ return this.getTableCountSpan(spanType).then(($elem) => {
const text = $elem
- .filter((_i, e) => e.innerText.includes('selected'))
+ .filter((_i, e) => e.innerText.includes(spanType))
.first()
.text();
- return Number(text.match(/(\d+)\s+selected/)[1]);
+ return Number(text.match(/(\d+)\s+\w*/)[1]);
});
}
- getTableFoundCount() {
+ // Wait until selected', 'found', or 'total' row count of a table equal to a number.
+ expectTableCount(spanType: 'selected' | 'found' | 'total', count: number) {
this.waitDataTableToLoad();
-
- return cy.get('.datatable-footer-inner .page-count span').then(($elem) => {
- const text = $elem
- .filter((_i, e) => e.innerText.includes('found'))
- .first()
- .text();
-
- return Number(text.match(/(\d+)\s+found/)[1]);
+ this.getTableCountSpan(spanType).should(($elem) => {
+ const text = $elem.first().text();
+ expect(Number(text.match(/(\d+)\s+\w*/)[1])).to.equal(count);
});
}
}
}
+ getTableCell(columnIndex: number, exactContent: string) {
+ this.waitDataTableToLoad();
+ this.seachTable(exactContent);
+ return cy.contains(
+ `datatable-body-row datatable-body-cell:nth-child(${columnIndex})`,
+ new RegExp(`^${exactContent}$`)
+ );
+ }
+
getExpandCollapseElement(content?: string) {
this.waitDataTableToLoad();
cy.contains(`.tc_filter_option .dropdown-item`, option).click();
}
+ setPageSize(size: string) {
+ cy.get('cd-table .dataTables_paginate input').first().clear().type(size);
+ }
+
seachTable(text: string) {
this.waitDataTableToLoad();
- cy.get('cd-table .dataTables_paginate input').first().clear().type('10');
+ this.setPageSize('10');
cy.get('cd-table .search input').first().clear().type(text);
}
return cy.get('cd-table .search button').click();
}
+ // Click the action button
+ clickActionButton(action: string) {
+ cy.get('.table-actions button.dropdown-toggle').first().click(); // open submenu
+ cy.get(`button.${action}`).click(); // click on "action" menu item
+ }
+
/**
* This is a generic method to delete table rows.
* It will select the first row that contains the provided name and delete it.
* After that it will wait until the row is no longer displayed.
+ * @param name The string to search in table cells.
+ * @param columnIndex If provided, search string in columnIndex column.
*/
- delete(name: string) {
+ delete(name: string, columnIndex?: number) {
// Selects row
- this.getFirstTableCell(name).click();
+ const getRow = columnIndex
+ ? this.getTableCell.bind(this, columnIndex)
+ : this.getFirstTableCell.bind(this);
+ getRow(name).click();
// Clicks on table Delete button
- cy.get('.table-actions button.dropdown-toggle').first().click(); // open submenu
- cy.get('button.delete').click(); // click on "delete" menu item
+ this.clickActionButton('delete');
// Confirms deletion
cy.get('cd-modal .custom-control-label').click();
cy.get('cd-modal').should('not.exist');
// Waits for item to be removed from table
- this.getFirstTableCell(name).should('not.exist');
+ getRow(name).should('not.exist');
}
}
}
spec.pageObject.navigateTo();
- spec.pageObject.getTableTotalCount().then((tableCount) => {
+ spec.pageObject.getTableCount('total').then((tableCount) => {
expect(tableCount).to.eq(
dashCount,
`Text of card "${spec.cardName}" and regex "${spec.regexMatcher}" resulted in ${dashCount} ` +
infoCardBodyText(infoCard: string) {
return this.infoCard(infoCard).find('.card-text').text();
}
+
+ infoCardBody(infoCard: string) {
+ return this.infoCard(infoCard).find('.card-text');
+ }
}