|
| 1 | +import { |
| 2 | + inferenceServiceModal, |
| 3 | + modelServingGlobal, |
| 4 | +} from '#~/__tests__/cypress/cypress/pages/modelServing'; |
| 5 | +import { AWS_BUCKETS } from '#~/__tests__/cypress/cypress/utils/s3Buckets'; |
| 6 | +import { |
| 7 | + checkInferenceServiceState, |
| 8 | + provisionProjectForModelServing, |
| 9 | + verifyS3CopyCompleted, |
| 10 | +} from '#~/__tests__/cypress/cypress/utils/oc_commands/modelServing'; |
| 11 | +import { deleteOpenShiftProject } from '#~/__tests__/cypress/cypress/utils/oc_commands/project'; |
| 12 | +import { loadDSPFixture } from '#~/__tests__/cypress/cypress/utils/dataLoader'; |
| 13 | +import { HTPASSWD_CLUSTER_ADMIN_USER } from '#~/__tests__/cypress/cypress/utils/e2eUsers'; |
| 14 | +import { retryableBefore } from '#~/__tests__/cypress/cypress/utils/retryableHooks'; |
| 15 | +import { projectListPage, projectDetails } from '#~/__tests__/cypress/cypress/pages/projects'; |
| 16 | +import { generateTestUUID } from '#~/__tests__/cypress/cypress/utils/uuidGenerator'; |
| 17 | +import type { |
| 18 | + DataScienceProjectData, |
| 19 | + PVCLoaderPodReplacements, |
| 20 | +} from '#~/__tests__/cypress/cypress/types'; |
| 21 | +import { |
| 22 | + clusterStorage, |
| 23 | + addClusterStorageModal, |
| 24 | +} from '#~/__tests__/cypress/cypress/pages/clusterStorage'; |
| 25 | +import { createS3LoaderPod } from '#~/__tests__/cypress/cypress/utils/oc_commands/pvcLoaderPod'; |
| 26 | +import { waitForPodReady } from '#~/__tests__/cypress/cypress/utils/oc_commands/baseCommands'; |
| 27 | + |
| 28 | +let testData: DataScienceProjectData; |
| 29 | +let projectName: string; |
| 30 | +let modelName: string; |
| 31 | +let modelFilePath: string; |
| 32 | +let pvStorageName: string; |
| 33 | +const awsBucket = 'BUCKET_1' as const; |
| 34 | +const awsAccessKeyId = AWS_BUCKETS.AWS_ACCESS_KEY_ID; |
| 35 | +const awsSecretAccessKey = AWS_BUCKETS.AWS_SECRET_ACCESS_KEY; |
| 36 | +const awsBucketName = AWS_BUCKETS.BUCKET_1.NAME; |
| 37 | +const awsBucketEndpoint = AWS_BUCKETS.BUCKET_1.ENDPOINT; |
| 38 | +const awsBucketRegion = AWS_BUCKETS.BUCKET_1.REGION; |
| 39 | +const podName = 'pvc-loader-pod'; |
| 40 | +const uuid = generateTestUUID(); |
| 41 | + |
| 42 | +describe('Verify a model can be deployed from a PVC', () => { |
| 43 | + retryableBefore(() => { |
| 44 | + Cypress.on('uncaught:exception', (err) => { |
| 45 | + if (err.message.includes('Error: secrets "ds-pipeline-config" already exists')) { |
| 46 | + return false; |
| 47 | + } |
| 48 | + return true; |
| 49 | + }); |
| 50 | + return loadDSPFixture('e2e/dataScienceProjects/testModelPvcDeployment.yaml').then( |
| 51 | + (fixtureData: DataScienceProjectData) => { |
| 52 | + testData = fixtureData; |
| 53 | + projectName = `${testData.projectResourceName}-${uuid}`; |
| 54 | + modelName = testData.singleModelName; |
| 55 | + modelFilePath = testData.modelOpenVinoExamplePath; |
| 56 | + pvStorageName = testData.pvStorageName; |
| 57 | + |
| 58 | + if (!projectName) { |
| 59 | + throw new Error('Project name is undefined or empty in the loaded fixture'); |
| 60 | + } |
| 61 | + // Create a Project for pipelines |
| 62 | + provisionProjectForModelServing(projectName, awsBucket); |
| 63 | + }, |
| 64 | + ); |
| 65 | + }); |
| 66 | + after(() => { |
| 67 | + // Delete provisioned Project |
| 68 | + deleteOpenShiftProject(projectName, { wait: false, ignoreNotFound: true }); |
| 69 | + }); |
| 70 | + it( |
| 71 | + 'should deploy a model from a PVC', |
| 72 | + { tags: ['@Smoke', '@SmokeSet3', '@Dashboard', '@ModelServing'] }, |
| 73 | + () => { |
| 74 | + cy.step('log into application with ${HTPASSWD_CLUSTER_ADMIN_USER.USERNAME}'); |
| 75 | + cy.visitWithLogin('/', HTPASSWD_CLUSTER_ADMIN_USER); |
| 76 | + |
| 77 | + // Navigate to the project |
| 78 | + cy.step('Navigate to the project'); |
| 79 | + projectListPage.visit(); |
| 80 | + projectListPage.filterProjectByName(projectName); |
| 81 | + projectListPage.findProjectLink(projectName).click(); |
| 82 | + |
| 83 | + // Navigate to cluster storage page |
| 84 | + cy.step('Navigate to cluster storage page'); |
| 85 | + projectDetails.findSectionTab('cluster-storages').click(); |
| 86 | + clusterStorage.findCreateButton().click(); |
| 87 | + |
| 88 | + // Enter cluster storage details |
| 89 | + cy.step('Enter cluster storage details'); |
| 90 | + addClusterStorageModal.findNameInput().type(pvStorageName); |
| 91 | + |
| 92 | + addClusterStorageModal.findModelStorageRadio().click(); |
| 93 | + addClusterStorageModal.findModelPathInput().type(modelFilePath); |
| 94 | + addClusterStorageModal.findModelNameInput().type(modelName); |
| 95 | + |
| 96 | + addClusterStorageModal.findSubmitButton().click({ force: true }); |
| 97 | + |
| 98 | + // Verify the cluster storage is created |
| 99 | + const pvcRow = clusterStorage.getClusterStorageRow(pvStorageName); |
| 100 | + pvcRow.find().should('exist'); |
| 101 | + pvcRow.findStorageTypeColumn().should('contain', 'Model storage'); |
| 102 | + |
| 103 | + pvcRow.findConnectedResources().should('contain', modelName); |
| 104 | + |
| 105 | + const pvcReplacements: PVCLoaderPodReplacements = { |
| 106 | + NAMESPACE: projectName, |
| 107 | + PVC_NAME: pvStorageName, |
| 108 | + AWS_S3_BUCKET: awsBucketName, |
| 109 | + AWS_S3_ENDPOINT: awsBucketEndpoint, |
| 110 | + AWS_ACCESS_KEY_ID: awsAccessKeyId, |
| 111 | + AWS_SECRET_ACCESS_KEY: awsSecretAccessKey, |
| 112 | + AWS_DEFAULT_REGION: awsBucketRegion, |
| 113 | + POD_NAME: podName, |
| 114 | + MODEL_PATH: modelFilePath, |
| 115 | + }; |
| 116 | + |
| 117 | + // Create pod to mount the PVC |
| 118 | + cy.step('Create pod to mount the PVC'); |
| 119 | + createS3LoaderPod(pvcReplacements); |
| 120 | + |
| 121 | + // Verify the pod is ready |
| 122 | + cy.step('Verify the pod is ready'); |
| 123 | + waitForPodReady(podName, projectName); |
| 124 | + |
| 125 | + // Verify the S3 copy completed successfully |
| 126 | + cy.step('Verify S3 copy completed'); |
| 127 | + verifyS3CopyCompleted(podName, projectName); |
| 128 | + |
| 129 | + // Deploy the model |
| 130 | + cy.step('Deploy the model'); |
| 131 | + projectDetails.findSectionTab('model-server').click(); |
| 132 | + modelServingGlobal.findSingleServingModelButton().click(); |
| 133 | + modelServingGlobal.findDeployModelButton().click(); |
| 134 | + inferenceServiceModal.findModelNameInput().type(modelName); |
| 135 | + inferenceServiceModal.findServingRuntimeTemplateSearchSelector().click(); |
| 136 | + inferenceServiceModal.findGlobalScopedTemplateOption('OpenVINO Model Server').click(); |
| 137 | + inferenceServiceModal.findModelFrameworkSelect().click(); |
| 138 | + inferenceServiceModal.findOpenVinoIROpSet13().click(); |
| 139 | + // There's only one PVC so it's automatically selected |
| 140 | + inferenceServiceModal.findExistingPVCConnectionOption().click(); |
| 141 | + inferenceServiceModal.findSubmitButton().should('be.enabled').click(); |
| 142 | + inferenceServiceModal.shouldBeOpen(false); |
| 143 | + |
| 144 | + //Verify the model created and is running |
| 145 | + cy.step('Verify that the Model is running'); |
| 146 | + checkInferenceServiceState(testData.singleModelName, projectName, { |
| 147 | + checkReady: true, |
| 148 | + checkLatestDeploymentReady: true, |
| 149 | + }); |
| 150 | + }, |
| 151 | + ); |
| 152 | +}); |
0 commit comments