diff --git a/.env.example b/.env.example index 24f8744..ea74e20 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +PROV_APP_DEPLOY_CFG=ods-provisioning-app +PROV_APP_PROJECT=ods PROV_APP_USER=openshift PROV_APP_PASSWORD=openshift ATLASSIAN_USER=openshift @@ -11,9 +13,12 @@ OPENSHIFT_PROJECT=edpp OPENSHIFT_PUBLIC_HOST=.ocp.odsbox.lan OPENSHIFT_CLUSTER=https://ocp.odsbox.lan:8443/ SIMULATE=false +OPENSHIFT_TOKEN=THE_TOKEN # values provided by ods-configuration/ods-core.env if present JIRA_URL=http://jira.odsbox.lan:8080/ NEXUS_URL=https://nexus-ods.ocp.odsbox.lan NEXUS_USERNAME=admin NEXUS_PASSWORD=openshift BITBUCKET_URL=http://bitbucket.odsbox.lan:7990 +BITBUCKET_BASE_BRANCH=master +QUICKSTARTERS_CONFIGMAP=quickstarters.properties diff --git a/README.md b/README.md index 1bc679e..7d69add 100644 --- a/README.md +++ b/README.md @@ -9,28 +9,31 @@ You can have a look at the `build.gradle` and `src/test/resources/GebConfig.groo ## Usage You need to set several environment variables in order to make this work, as it is intended to use in a container / pod lately. -| Variable | Value | Description | -|-----------------------|----------------------------------|------------------------------------------------------------------------------------------------------------------| -| PROV_APP_USER | openshift | Provisioning app user name | -| PROV_APP_PASSWORD | openshift | Provisioning app password | -| ATLASSIAN_USER | openshift | Atlassian user name | -| ATLASSIAN_PASSWORD | openshift | Atlassian password | -| JENKINS_USER | developer | Jenkins user name | -| JENKINS_PASSWORD | any | Jenkins password | -| OPENSHIFT_USER | developer | Openshift user name | -| OPENSHIFT_PASSWORD | any | Openshift password | -| PROV_APP_NAME | openshift | Name of the deployment of the provisioning app | -| JIRA_URL | http://jira.odsbox.lan:8080/ | Url of Jira instance related with the prov app | -| OPENSHIFT_PROJECT | edpp | project identifier for prov app in the preliminary tests(jira tests) | -| OPENSHIFT_PUBLIC_HOST | .ocp.odsbox.lan | host where we can locate the prov app | -| OPENSHIFT_CLUSTER | https://ocp.odsbox.lan:8443/ | URL of the Openshift Cluster | -| BITBUCKET_URL | http://bitbucket.odsbox.lan:7990 | Url of Bitbucket instance | -| SIMULATE | false | Specify (true or false) if we skip the creation of project, components, etc | -| NO_NEXUS | true | In case you want to use the public repositories, if it set to true it is not needed to set the next 3 properties | -| NEXUS_URL | https://nexus-ods.ocp.odsbox.lan | The nexus url if we want to use an specific nexus instance | -| NEXUS_USERNAME | admin | The nexus user | -| NEXUS_PASSWORD | openshift | The nexus password | - +| Variable | Value | Description | +|------------------------|----------------------------------|----------------------------------------------------------------------------- | +| PROV_APP_USER | openshift | Provisioning app user name | +| PROV_APP_PASSWORD | openshift | Provisioning app password | +| ATLASSIAN_USER | openshift | Atlassian user name | +| ATLASSIAN_PASSWORD | openshift | Atlassian password | +| JENKINS_USER | developer | Jenkins user name | +| JENKINS_PASSWORD | any | Jenkins password | +| OPENSHIFT_USER | developer | Openshift user name | +| OPENSHIFT_PASSWORD | any | Openshift password | +| PROV_APP_NAME | openshift | Name of the deployment of the provisioning app | +| JIRA_URL | http://jira.odsbox.lan:8080/ | Url of Jira instance related with the prov app | +| OPENSHIFT_PROJECT | edpp | project identifier for prov app in the preliminary tests(jira tests) | +| OPENSHIFT_PUBLIC_HOST | .ocp.odsbox.lan | host where we can locate the prov app | +| OPENSHIFT_CLUSTER | https://ocp.odsbox.lan:8443/ | URL of the Openshift Cluster | +| BITBUCKET_URL | http://bitbucket.odsbox.lan:7990 | Url of Bitbucket instance | +| SIMULATE | false | Specify (true or false) if we skip the creation of project, components, etc | +| NO_NEXUS | true | In case you want to use the public repositories, if it set to true it is not needed to set the next 3 properties | +| NEXUS_URL | https://nexus-ods.ocp.odsbox.lan | The nexus url if we want to use an specific nexus instance | +| NEXUS_USERNAME | admin | The nexus user | +| NEXUS_PASSWORD | openshift | The nexus password | +| BITBUCKET_BASE_BRANCH | master | The branck used as base branch for the QS(useful when you are testing the box)| +| QUICKSTARTERS_CONFIGMAP| quickstarters.properties | The configmap name where the qs of the provisioning app gets its configuration| +| OPENSHIFT_TOKEN | the_token | Token to login in Openshift to be use instead of the openshift user for specific tasks | + ### Environment variables setup To get the information needed to run the tests there are 3 steps that override the previous one: 1. If the test project is co-located with the ods-configuration folder, the process will retrieve the values that exists in the ods-core.env file to obtain the urls of Openshift, Jira, Bitbucket. diff --git a/build.gradle b/build.gradle index 012d7c0..d932d05 100644 --- a/build.gradle +++ b/build.gradle @@ -17,13 +17,18 @@ ext { chromeDriverVersion = '83.0.4103.39' geckoDriverVersion = '0.26.0' htmlunitVersion = "2.36.0" + openshiftClientVersion = '9.0.0.Final' // Environment variable needed to run the tests dotEnvFile = './.env' odsConfigFile = '../ods-configuration/ods-core.env' environmentVariables = [ + PROV_APP_DEPLOY_CFG : '', + PROV_APP_PROJECT : '', PROV_APP_USER : '', PROV_APP_PASSWORD : '', + BITBUCKET_BASE_BRANCH : '', + QUICKSTARTERS_CONFIGMAP : '', ATLASSIAN_USER : '', ATLASSIAN_PASSWORD : '', JENKINS_USER : '', @@ -107,6 +112,9 @@ dependencies { testCompile "org.eclipse.jgit:org.eclipse.jgit:5.7.0.202003110725-r" testCompile "org.yaml:snakeyaml:1.26" + // Openshift client + testImplementation "com.openshift:openshift-restclient-java:$openshiftClientVersion" + } diff --git a/src/test/groovy/org/ods/e2e/ODSSpec.groovy b/src/test/groovy/org/ods/e2e/ODSSpec.groovy index 7e22a88..7aa2373 100644 --- a/src/test/groovy/org/ods/e2e/ODSSpec.groovy +++ b/src/test/groovy/org/ods/e2e/ODSSpec.groovy @@ -1,14 +1,16 @@ package org.ods.e2e - import org.ods.e2e.bitbucket.pages.DashboardPage import org.ods.e2e.bitbucket.pages.LoginPage import org.ods.e2e.bitbucket.pages.ProjectPage import org.ods.e2e.bitbucket.pages.RepositoryPage import org.ods.e2e.jenkins.pages.JenkinsConsoleParametrizedBuildPage import org.ods.e2e.jenkins.pages.JenkinsJobFolderPage +import org.ods.e2e.jenkins.pages.JenkinsLoginPage +import org.ods.e2e.openshift.client.OpenShiftClient +import org.ods.e2e.openshift.client.OpenShiftHelper import org.ods.e2e.openshift.pages.ConsoleDeploymentsPage -import org.ods.e2e.openshift.pages.ConsoleProjectsPage +import org.ods.e2e.openshift.pages.ConsoleResourcesConfigMaps import org.ods.e2e.openshift.pages.PodsPage import org.ods.e2e.provapp.pages.HomePage import org.ods.e2e.provapp.pages.ProvAppLoginPage @@ -20,6 +22,13 @@ import org.yaml.snakeyaml.Yaml class ODSSpec extends BaseSpec { + def static OPENDEVSTACK = 'OPENDEVSTACK' + def static E2E_TEST_BRANCH = 'e2e-test-branch' + def static E2E_TEST_FILE = 'e2e-tests.txt' + def static E2E_TEST_QUICKSTARTER = 'e2e-test-quickstarter' + def static E2E_TEST_COMPONENT = 'test-component' + def static openshifHelper = new OpenShiftHelper() + def static projects = [ default: [ name : 'E2E Test Project', @@ -79,6 +88,7 @@ class ODSSpec extends BaseSpec { println 'Tests setup' } + /** * Test Objective: * The purpose of this test case is to present a level of evidences that the use of Provisioning Application, @@ -103,7 +113,8 @@ class ODSSpec extends BaseSpec { // STEP 1: Login to provisioning application with administrator privileges // Result: Login works, within a provisioning and history links // ------------------------------------------------------------------------------------------------------------- - given: 'We are logged in the provissioning app' + given: 'We are logged in the provisioning app' + def openshifHelper = new OpenShiftHelper() def project = projects.default baseUrl = baseUrlProvisioningApp @@ -135,7 +146,9 @@ class ODSSpec extends BaseSpec { and: 'Get the next key' def nextId = getNextId(project.key) project.name = String.format("$project.name - %02d", nextId) - project.key = String.format("$project.key%02d", nextId) + if (!simulate) { + project.key = String.format("${project.key}%02d", nextId) + } and: 'We open the project creation form' provisionOptionChooser.doSelectCreateNewProject() @@ -158,7 +171,7 @@ class ODSSpec extends BaseSpec { projectCreateForm.doStartProvision() // wait until project is created - waitFor { + waitFor('verySlow') { $(".modal-dialog").css("display") != "hidden" $("#resProject.alert-success") $("#resButton").text() == "Close" @@ -177,7 +190,9 @@ class ODSSpec extends BaseSpec { simulate ? true : $("#resProject.alert-success").size() == 1 report("step 4 - project has been created") - $("#resButton").click() + if (!simulate) { + $("#resButton").click() + } // ------------------------------------------------------------------------------------------------------------- @@ -199,6 +214,9 @@ class ODSSpec extends BaseSpec { then: 'We are in the project page' currentUrl.endsWith("projects/${project.key}/") + if (!simulate) { + $("h2.page-panel-content-header")?.text()?.contains('Repositories') + } report("step 5 - project in bitbucket") // ------------------------------------------------------------------------------------------------------------- @@ -206,41 +224,19 @@ class ODSSpec extends BaseSpec { // Result: Find a running Jenkins deployment – click on it and verify that the image used comes from the // CD namespace // ------------------------------------------------------------------------------------------------------------- - when: 'Visit Openshift' - baseUrl = baseUrlOpenshift - - and: 'and login in Openshift' - doOpenshiftLoginProcess() + when: 'Check if we have a Jenkins instance running' + baseUrl = getJenkinsBaseUrl(project.key) - then: "Visit all project page and check for the projects" - waitFor('mediumSlow') { - to ConsoleProjectsPage - findProjects(project.key).size() > 0 - findProjects(project.key).contains(project.key.toLowerCase() + '-cd') + and: + waitFor('extremelySlow') { + openshifHelper.existsNamespace(project.key.toLowerCase() + '-cd') } - - when: - 'Visit pods page' - to PodsPage, project.key.toLowerCase() + '-cd' - sleep(5000) - then: - waitFor('verySlow') { - getPods().find { pod -> - pod.name.startsWith('jenkins') && - pod.status == 'Running' && - !pod.isDeployPod && - pod.containersReady == '1/1' - } - - getPods().find { pod -> - pod.name.startsWith('webhook') && - pod.status == 'Running' && - !pod.isDeployPod && - pod.containersReady == '1/1' - } + waitFor('extremelySlow') { + doJenkinsLoginProcess() } + report("step 6 - existing jenkins instance for the project") // ------------------------------------------------------------------------------------------------------------- @@ -324,7 +320,7 @@ class ODSSpec extends BaseSpec { baseUrl = getJenkinsBaseUrl(project.key) and: - doJenkinsLoginProcess() + to JenkinsLoginPage then: 'The project folder exists' assert $("#job_${project.key.toLowerCase()}-cd") @@ -333,6 +329,11 @@ class ODSSpec extends BaseSpec { 'Visit the jobs' to JenkinsJobFolderPage, project.key + and: + if (activateAutorefreshLink) { + activateAutorefreshLink.click() + } + and: project.components.each { component -> component.jobs = getComponentJobs(project.key, component.componentId) @@ -341,11 +342,11 @@ class ODSSpec extends BaseSpec { then: 'The component startup jobs finished succesfully' waitFor('verySlow') { - project.components.each { + project.components.every { component -> - getComponentJobs(project.key, component.componentId).jobs.find { + getComponentJobs(project.key, component.componentId).find { job -> job.value.odsStartupComponentJob && job.value.success - } + } != null } } @@ -367,17 +368,271 @@ class ODSSpec extends BaseSpec { and: 'Checks that exists jobs that are not qs startup jobs for the components' waitFor('verySlow') { - project.components.each { + project.components.every { component -> - getComponentJobs(project.key, component.componentId).jobs.find { + getComponentJobs(project.key, component.componentId).find { job -> !job.value.odsStartupComponentJob && job.value.success - } + } != null } } report("step 12 - build job of the component") } + /** + * Test Objective: To provide evidences that a new boilerplate software updates can be easily added to ODS, + * and be available for use. + * + * Prerequisites: Be logged in as Provisioning Application administrator, have a OpenShift project, have access + * to the console/terminal logs of Jenkins, Nexus and SonarQube and have administrator access to the Bitbucket + * repositories including the OpenDevStack one. + */ + def "FT_01_002"() { + // STEP 1: Go to Bitbucket ODS project – into repository ods-quickstarters + // Result: Project and repository available + given: + baseUrl = baseUrlBitbucket + + when: + to LoginPage + + and: 'we do login' + doLogin() + + then: 'we are at the Dashboard' + at DashboardPage + + when: 'Visit project' + to RepositoryPage, OPENDEVSTACK, 'ods-quickstarters' + + then: 'Project and repository available' + currentUrl.endsWith('OPENDEVSTACK/repos/ods-quickstarters/browse') + report('step 1 - Project and repository available') + + // STEP 2: Pick one quickstarter repository and create a branch from master – add, and commit a file into /files + // Result: Repository with master branch found, branch modified and committed/pushed + when: 'Checkout project' + def quickstarter = 'fe-angular' + def gitRepository = GitUtil.cloneRepository(OPENDEVSTACK, 'ods-quickstarters', baseBranchBitbucket, false) + + and: 'Create a branch' + GitUtil.checkout(gitRepository, E2E_TEST_BRANCH, true) + + and: 'Add a file into /files' + def directory = gitRepository.repository.getWorkTree() + def filesPath = "$quickstarter/files".toString() + def testFilePath = "$filesPath/$E2E_TEST_FILE" + new File("$directory/$testFilePath").text = 'Test file for FT_01_002' + + and: 'Commit and push file' + GitUtil.add(gitRepository, testFilePath) + GitUtil.commitAddAll(gitRepository, 'Added test file') + GitUtil.push(gitRepository) + + then: 'Push successful if no exception' + true + report("step 2 - Repository with master branch found, branch modified and committed/pushed.") + + // STEP 3: Go to Openshift prov-test project and open available config maps + // Result: Configuration maps available – namely application.properties + when: 'Visit Openshift' + baseUrl = baseUrlOpenshift + + and: 'and login in Openshift' + doOpenshiftLoginProcess() + + and: 'Visit the config maps of the provisioning application' + to ConsoleResourcesConfigMaps, provisioningAppProject + + and: 'Retrieve the configMaps' + def configMaps = getConfigMaps() + + then: + configMaps.findAll { configMap -> configMap.name == quickstartersConfigMap }.size() == 1 + report("step 3 - Configuration maps available – namely application.properties") + + // STEP 4: In the application.properties config map copy the configuration lines from the quickstarter + // you pick from the step 2 + // Result: Existing Quickstarter / boilerplace configuration available + + when: 'Read configMap' + def client = OpenShiftClient.connect(provisioningAppProject) + def configMap = client.getConfigMap(quickstartersConfigMap) + def configMapData = configMap.getData() + + and: 'Read the properties' + def propertyFile = configMapData.properties + def propertyBackup = propertyFile + def properties = new Properties() + properties.load(new StringReader(propertyFile)) + + and: 'Obtain existing properties' + def tmpProps = properties.findAll { + key, value -> + key.startsWith("jenkinspipeline.quickstarter.$quickstarter.") + } + + then: + !tmpProps.isEmpty() + report("step 4 - Existing Quickstarter / boilerplace configuration available") + + // STEP 5: Replace the key with a name of your choice and add the branch identifier and Jenkins file path as + // documented in the application.properties – click save + // Result: Config map can be saved + + when: 'Add quickstarter properties' + tmpProps = tmpProps.collectEntries { + key, value -> + def concreteProperty = key.substring("jenkinspipeline.quickstarter.$quickstarter.".length()) + def newKey = "jenkinspipeline.quickstarter.${E2E_TEST_QUICKSTARTER}.${concreteProperty}".toString() + [(newKey): value] + } + properties.putAll(tmpProps) + properties.setProperty("jenkinspipeline.quickstarter.${E2E_TEST_QUICKSTARTER}.branch".toString(), E2E_TEST_BRANCH) + properties.setProperty("jenkinspipeline.quickstarter.${E2E_TEST_QUICKSTARTER}.jenkinsfile".toString(), + "$quickstarter/Jenkinsfile".toString()) + + and: 'Update config map' + def sw = new StringWriter() + properties.store(sw, null) + propertyFile = sw.toString() + configMapData.properties = propertyFile + client.modifyConfigMap(configMap, configMapData) + + and: 'Save modified config map' + configMap = client.update(configMap) + + and: 'Retrieve the config map again' + configMapData = configMap.getData() + def newContent = configMapData.get('properties') + + then: 'We correctly retrieve the updated data' + newContent == propertyFile + report("step 5 - Config map can be saved") + + // STEP 6: From the console or thru OC cli - redeploy the provision application in prov-test + // Result: New deployment of provision app shown in console and new pod available + + when: 'Get deployments' + def lastVersion = client.getLastDeploymentVersion(provisioningAppDeployCfg) + + and: 'Redeploy the provisioning app' + client.deploy(provisioningAppDeployCfg) + + and: 'Wait for deployment' + def newVersion = client.waitForDeployment(provisioningAppDeployCfg, lastVersion) + baseUrl = baseUrlProvisioningApp + waitFor('verySlow') { + to ProvAppLoginPage + } + + then: 'New deployment exists' + newVersion > lastVersion + report("step 6 - New deployment of provision app shown in console and new pod available.") + + // STEP 7: Login and pick modify existing project / and locate the new quickstarter + // Result: New quickstarter available in the list in provision application + + when: 'We are logged in the provissioning app' + doLoginProcess() + + and: 'We are in the provisioning page' + def project = projects.default + to ProvisionPage + + and: 'We have selected modify project' + provisionOptionChooser.doSelectModifyProject() + projectModifyForm.doSelectProject(project.key) + + // ------------------------------------------------------------------------------------------------------------- + // STEP 8: Click on the Quickstarter dropdown list and select a boilerplate “Frontend implemented with Vue JS” + // Result: The component ID is listed: fe-vue + // ------------------------------------------------------------------------------------------------------------- + and: + projectModifyForm.doAddQuickStarter(E2E_TEST_QUICKSTARTER, E2E_TEST_COMPONENT) + projectModifyForm.addQuickStarterButton.click() + report("step 8 - add quickstarter") + + // ------------------------------------------------------------------------------------------------------------- + // STEP 9: Click on Start Provision + // Result: Message the provision is in progress. + // Another message (screen) appears with the links of: + // Jenkins, Bitbucket, Project link, Provisioning jobs, and others. + // ------------------------------------------------------------------------------------------------------------- + and: + if (!simulate) { + projectModifyForm.doStartProvision() + sleep(15000) + waitFor('extremelySlow') { + $(".modal-dialog").css("display") != "hidden" + $("#resProject.alert-success") + $("#resButton").text() == "Close" + } + report('Status after Quickstarters Addition') + } + + then: 'Quickstarter was added' + simulate ? true : $("#resProject.alert-success") + report("step 9 - quick starter provisioned") + + // ------------------------------------------------------------------------------------------------------------- + // STEP 10: Go to your project’s Jenkins and locate the provision job of the component + // with the name you provided. + // Result: Instance can be found and green (successful) + // ------------------------------------------------------------------------------------------------------------- + // ------------------------------------------------------------------------------------------------------------- + // We first need to check that the Jenkins jobs had finalized properly for the release manager + // Before continuing with the steps of the test + // ------------------------------------------------------------------------------------------------------------- + when: 'visit Jenkins' + baseUrl = getJenkinsBaseUrl(project.key) + + and: + doJenkinsLoginProcess() + + then: 'The project folder exists' + assert $("#job_${project.key.toLowerCase()}-cd") + + when: 'Retrieve the jobs related with the Release Manager deploy' + to JenkinsJobFolderPage, project.key + if (activateAutorefreshLink) { + activateAutorefreshLink.click() + } + + then: 'The component startup job finished succesfully' + waitFor('verySlow') { + getComponentJobs(project.key, E2E_TEST_COMPONENT).find { + job -> job.value.odsStartupComponentJob && job.value.success + } + } + report("step 10 - provision job of the component") + + // STEP 11: Go to bitbucket, locate the new repository and locate the file added in step 2 + // Result: Repository and file available + + when: 'Grab evidences of adding files from bitbucket' + gitRepository = GitUtil.cloneRepository(project.key, E2E_TEST_COMPONENT, baseBranchBitbucket) + directory = gitRepository.repository.getWorkTree() + testFilePath = E2E_TEST_FILE + + then: 'Test file exists' + new File("$directory/$testFilePath").text == 'Test file for FT_01_002' + report("step 11 - Repository and file available.") + + cleanup: 'Restore original contents of the config map' + configMapData.put('properties', propertyBackup) + client.modifyConfigMap(configMap, configMapData) + client.update(configMap) + client.deploy(provisioningAppDeployCfg) + GitUtil.deleteBranch(gitRepository, E2E_TEST_BRANCH, true) + client.waitForDeployment(provisioningAppDeployCfg, newVersion) + baseUrl = baseUrlProvisioningApp + waitFor('verySlow') { + to ProvAppLoginPage + } + + } + /** * Test Objective: * The purpose of this test case is to demonstrate the functionality to orchestrate multiple repositories with the @@ -499,11 +754,15 @@ class ODSSpec extends BaseSpec { } } - and: 'Wait if the job is still running' + + // After adding the new component some changes are introduced to the configuration of the jenkins pod + // so the pod is restarted. + and: 'Wait if the job is still running or jenkins is still running' waitFor('verySlow') { - getComponentJobs(project.key, releaseManagerComponent.componentId).find { - job -> job.value.odsStartupComponentJob && job.value.success - } + $("body > div >h1").text()?.toLowerCase()?.contains('application is not available') != null || + getComponentJobs(project.key, releaseManagerComponent.componentId).find { + job -> job.value.odsStartupComponentJob && job.value.success + } != null } // ------------------------------------------------------------------------------------------------------------- @@ -543,7 +802,8 @@ class ODSSpec extends BaseSpec { // Result: Repository cloned, metadata.yml is available // ------------------------------------------------------------------------------------------------------------- when: - def repositoryFolder = GitUtil.cloneRepository(projects.default.key, releaseManagerComponent.componentId) + def gitRepository = GitUtil.cloneRepository(projects.default.key, releaseManagerComponent.componentId) + def repositoryFolder = gitRepository.repository.getWorkTree() and: DumperOptions options = new DumperOptions() @@ -580,25 +840,35 @@ class ODSSpec extends BaseSpec { metadataRepositories = metadataYml.getAt('repositories') } def component = project.components.first() - metadataRepositories.putAt(metadataRepositories.size, [ - id : component.componentId.toLowerCase(), - name: "$project.key-$component.componentId".toLowerCase(), type: 'ods']) + if (metadataRepositories.size() == 0 || metadataRepositories.findAll { it -> it.id == component.componentId.toLowerCase() }.size() == 0) { + metadataRepositories.putAt(metadataRepositories.size, [ + id : component.componentId.toLowerCase(), + name: "$project.key-$component.componentId".toLowerCase(), type: 'ods']) + } + and: 'Save metada.yml' parser.dump(metadataYml, new FileWriter("$repositoryFolder/metadata.yml")) and: 'Commit the file' - GitUtil.commitAddAll('New component added') + GitUtil.commitAddAll(gitRepository, 'New component added') and: 'Push it to the repository' - GitUtil.push('origin') + GitUtil.push(gitRepository, 'origin') // ------------------------------------------------------------------------------------------------------------- // 3.2 Trigger Jenkins build of the release manager. // ------------------------------------------------------------------------------------------------------------- - and: + and: 'Go to jenkins' baseUrl = getJenkinsBaseUrl(project.key) + and: 'Login again in jenkins as it has been rebooted' + if ($("body > div >h1").text()?.toLowerCase()?.contains('application is not available') != null) { + waitFor('extremelySlow') { + doJenkinsLoginProcess() + } + } + def parameters = [environment: 'dev', version: 'WIP',] to JenkinsConsoleParametrizedBuildPage, project.key, releaseManagerPipelineJob fillData(parameters) diff --git a/src/test/groovy/org/ods/e2e/bitbucket/pages/RepositoryPage.groovy b/src/test/groovy/org/ods/e2e/bitbucket/pages/RepositoryPage.groovy index c2fd2d6..68a0e94 100644 --- a/src/test/groovy/org/ods/e2e/bitbucket/pages/RepositoryPage.groovy +++ b/src/test/groovy/org/ods/e2e/bitbucket/pages/RepositoryPage.groovy @@ -25,7 +25,7 @@ class RepositoryPage extends Page { repository = entry } else { if (entry instanceof String) { - file += file + "/$entry" + file += "/$entry" } else if (entry instanceof LinkedHashMap) { entry.each { it -> params += "$it.key=$it.value" diff --git a/src/test/groovy/org/ods/e2e/jenkins/pages/JenkinsConsoleParametrizedBuildPage.groovy b/src/test/groovy/org/ods/e2e/jenkins/pages/JenkinsConsoleParametrizedBuildPage.groovy index 868f492..b2176a1 100644 --- a/src/test/groovy/org/ods/e2e/jenkins/pages/JenkinsConsoleParametrizedBuildPage.groovy +++ b/src/test/groovy/org/ods/e2e/jenkins/pages/JenkinsConsoleParametrizedBuildPage.groovy @@ -4,7 +4,7 @@ import geb.Page class JenkinsConsoleParametrizedBuildPage extends Page { - static url = '/job/' + static url = '/job' /** * Adapt the url to get to the jenkins job/jobfolder page * https://jenkins-url/job/$project$-cd/job/$job$/build?delay=0sec diff --git a/src/test/groovy/org/ods/e2e/openshift/client/OpenShiftClient.groovy b/src/test/groovy/org/ods/e2e/openshift/client/OpenShiftClient.groovy new file mode 100644 index 0000000..8c3e899 --- /dev/null +++ b/src/test/groovy/org/ods/e2e/openshift/client/OpenShiftClient.groovy @@ -0,0 +1,162 @@ +package org.ods.e2e.openshift.client + +import com.openshift.internal.util.JBossDmrExtentions +import com.openshift.restclient.ClientBuilder +import com.openshift.restclient.IClient +import com.openshift.restclient.IOpenShiftWatchListener +import com.openshift.restclient.ResourceKind +import com.openshift.restclient.capability.resources.IDeploymentTriggerable +import com.openshift.restclient.model.IConfigMap +import com.openshift.restclient.model.IDeploymentConfig +import com.openshift.restclient.model.IResource +import org.ods.e2e.util.SpecHelper + +import java.sql.Timestamp +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class OpenShiftClient { + private static applicationProperties = new SpecHelper().getApplicationProperties() + private static builder = new ClientBuilder(applicationProperties['config.openshift.url']) + .withUserName(applicationProperties['config.openshift.user.name']) + .withPassword(applicationProperties['config.openshift.user.password']) + + private OpenShiftClient(client) { + this.client = client + } + + private final IClient client + private String project + + def static connect() { + return new OpenShiftClient(builder.build()) + } + + def static connect(project) { + return connect().project(project) + } + + def project(project) { + this.project = project + return this + } + + def getConfigMap(name) { + return getConfigMap(name, project) + } + + def getConfigMap(name, project) { + if (!name) { + throw new IllegalArgumentException('You must specify a name') + } + if (!project) { + throw new IllegalArgumentException('You must specify a project') + } + + return client. get(ResourceKind.CONFIG_MAP, name, project) + } + + def update(configMap) { + return client.update(configMap) + } + + def deploy(name) { + deploy(name, project) + } + + def deploy(name, project) { + def deployment = getDeploymentConfig(name, project) + def triggerable = deployment.getCapability(IDeploymentTriggerable.class) + triggerable.force = true + triggerable.latest = true + triggerable.trigger() + } + + def getLastDeploymentVersion(deploymentConfig) { + return getLastDeploymentVersion(deploymentConfig, project) + } + + def getLastDeploymentVersion(deploymentConfig, project) { + def deployment = getDeploymentConfig(deploymentConfig, project) + return deployment.latestVersionNumber + } + + def getDeploymentConfig(name) { + return getDeploymentConfig(name, project) + } + + def getDeploymentConfig(name, project) { + if (!name) { + throw new IllegalArgumentException('You must specify a DeploymentConfig name') + } + if (!project) { + throw new IllegalArgumentException('You must specify a project') + } + + return client. get(ResourceKind.DEPLOYMENT_CONFIG, name, project) + } + + def waitForDeployment(name, lastVersion) { + return waitForDeployment(name, lastVersion, project) + } + + def waitForDeployment(name, lastVersion, project) { + def listener = new IOpenShiftWatchListener.OpenShiftWatchListenerAdapter() { + private CountDownLatch latch = new CountDownLatch(1) + private int newVersion = 0 + private boolean deleted = false + private boolean ready = false + + @Override + void received(IResource resource, IOpenShiftWatchListener.ChangeType change) { + def matcher = resource.name =~ /$name-(\d+)-.*/ + if (resource.kind == ResourceKind.POD && matcher.matches()) { + def version = Integer.parseInt(matcher.group(1)) + switch (change) { + case IOpenShiftWatchListener.ChangeType.ADDED: + if (version > newVersion) { + newVersion = version + ready = resource.isReady() + } + break + case IOpenShiftWatchListener.ChangeType.DELETED: + if (version == lastVersion) { + deleted = true + } + break + case IOpenShiftWatchListener.ChangeType.MODIFIED: + if (version == newVersion) { + ready = resource.isReady() + } + break + } + } + if (deleted && ready && newVersion > lastVersion) { + latch.countDown() + } + } + + def await(timeout, timeUnit) { + return latch.await(timeout, timeUnit) + } + + def getNewVersion() { + return newVersion + } + } + def completed + def watcher = client.watch(project, listener, ResourceKind.POD) + try { + completed = listener.await(5, TimeUnit.MINUTES) + } finally { + watcher.stop() + } + return completed ? listener.getNewVersion() : 0 + } + + def modifyConfigMap(configMap, map) { + JBossDmrExtentions.set(configMap.getNode(), configMap.getPropertyKeys(), 'data', map) + return configMap + } + +} diff --git a/src/test/groovy/org/ods/e2e/openshift/client/OpenShiftHelper.groovy b/src/test/groovy/org/ods/e2e/openshift/client/OpenShiftHelper.groovy new file mode 100644 index 0000000..02613a9 --- /dev/null +++ b/src/test/groovy/org/ods/e2e/openshift/client/OpenShiftHelper.groovy @@ -0,0 +1,57 @@ +package org.ods.e2e.openshift.client + + +import kong.unirest.Unirest +import org.ods.e2e.util.SpecHelper + +class OpenShiftHelper { + def specHelper = new SpecHelper() + + def existsNamespace(String projectKey) { + def namespaceFound = false + if (!projectKey?.trim()) { + throw new IllegalArgumentException("Error: unable to get namespace $projectKey.") + } + + Unirest.config().verifySsl(false); + String openshiftUrl = specHelper.applicationProperties."config.openshift.url" + openshiftUrl = openshiftUrl.endsWith('/') ? openshiftUrl.substring(0, openshiftUrl.size() - 1) : openshiftUrl + def response = Unirest.get("$openshiftUrl/api/v1/namespaces/{projectKey}") + .routeParam("projectKey", projectKey.toLowerCase()) + .header("Accept", "application/json") + .header("Authorization", "Bearer " + specHelper.applicationProperties."config.openshift.token") + .asString() + + response.ifFailure { + def message = "Error: unable to get the namaspace. Openshift responded with code: '${response.getStatus()}' and message: '${response.getBody()}'." + println message + } + response.ifSuccess { + namespaceFound = true + } + return namespaceFound + } + + def watchPods(projectKey, pod) { + if (!projectKey?.trim()) { + throw new IllegalArgumentException("Error: unable to get namespace $projectKey.") + } + + Unirest.config().verifySsl(false); + String openshiftUrl = specHelper.applicationProperties."config.openshift.url" + openshiftUrl = openshiftUrl.endsWith('/') ? openshiftUrl.substring(0, openshiftUrl.size() - 1) : openshiftUrl + def response = Unirest.get("$openshiftUrl/api/v1/watch/namespaces/{projectKey}/pods") + .routeParam("projectKey", projectKey.toLowerCase()) + .header("Accept", "application/json") + .header("Authorization", "Bearer " + specHelper.applicationProperties."config.openshift.token") + .asString() + + response.ifFailure { + def message = "Error: unable to get the namaspace. Openshift responded with code: '${response.getStatus()}' and message: '${response.getBody()}'." + println message + return false + } + + return true + } +} diff --git a/src/test/groovy/org/ods/e2e/openshift/pages/ConsoleResourcesConfigMaps.groovy b/src/test/groovy/org/ods/e2e/openshift/pages/ConsoleResourcesConfigMaps.groovy new file mode 100644 index 0000000..903063c --- /dev/null +++ b/src/test/groovy/org/ods/e2e/openshift/pages/ConsoleResourcesConfigMaps.groovy @@ -0,0 +1,36 @@ +package org.ods.e2e.openshift.pages + +import geb.Page + +class ConsoleResourcesConfigMaps extends Page { + + def project + + static at = { browser.currentUrl.contains("/console/project/$project/browse/config-maps") } + + static content = { + configMapRows(wait: true, required: true) { $('table tr.ng-scope') } + } + + /** + * Adapt the url to get to the repository page + * https://openshift-url/console/project/PROJECT/browse/config-maps + * @param args must contain 1 args + * projectName - Name of the project + */ + String convertToPath(Object[] args) { + project = args[0] + args ? "/console/project/$project/browse/config-maps/" : '' + } + + /** + * Get the configmaps that exists in the project + * @return + */ + def getConfigMaps() { + configMapRows.collect { it -> + [name: it.find('td > a', 0).text(), + date: it.find('td > span', 0).text()] + } + } +} diff --git a/src/test/groovy/org/ods/e2e/openshift/pages/OpenShiftLoginPage.groovy b/src/test/groovy/org/ods/e2e/openshift/pages/OpenShiftLoginPage.groovy index a2c2814..3ae778d 100644 --- a/src/test/groovy/org/ods/e2e/openshift/pages/OpenShiftLoginPage.groovy +++ b/src/test/groovy/org/ods/e2e/openshift/pages/OpenShiftLoginPage.groovy @@ -20,4 +20,11 @@ class OpenShiftLoginPage extends Page { loginButton.click() sleep(3000) } + + def doJenkinsLogin() { + username.value(applicationProperties."config.jenkins.user.name") + password.value(applicationProperties."config.jenkins.user.password") + loginButton.click() + sleep(3000) + } } diff --git a/src/test/groovy/org/ods/e2e/util/BaseSpec.groovy b/src/test/groovy/org/ods/e2e/util/BaseSpec.groovy index 60aabe5..24856fe 100644 --- a/src/test/groovy/org/ods/e2e/util/BaseSpec.groovy +++ b/src/test/groovy/org/ods/e2e/util/BaseSpec.groovy @@ -17,13 +17,17 @@ class BaseSpec extends GebReportingSpec { static JavascriptExecutor js def baseUrlProvisioningApp + def provisioningAppProject + def provisioningAppDeployCfg def baseUrlJira def baseUrlBitbucket + def baseBranchBitbucket def baseUrlJenkins def baseUrlOpenshift def openshiftPublichost def simulate def extraLoginPage + def quickstartersConfigMap def setup() { driver.manage().window().setSize(new Dimension(1600, 1024)) @@ -32,8 +36,14 @@ class BaseSpec extends GebReportingSpec { js = (JavascriptExecutor) driver openshiftPublichost = removeLastSlash(applicationProperties."config.openshift.publichost") baseUrlProvisioningApp = removeLastSlash(applicationProperties."config.provisioning.url") + provisioningAppDeployCfg = applicationProperties."config.provisioning-app.deployCfg" + provisioningAppProject = applicationProperties."config.provisioning-app.project" baseUrlJira = removeLastSlash(applicationProperties."config.atlassian.jira.url") baseUrlBitbucket = removeLastSlash(applicationProperties."config.atlassian.bitbucket.url") + baseBranchBitbucket = applicationProperties."config.atlassian.bitbucket.branch" + if (!baseBranchBitbucket) { + baseBranchBitbucket = null + } baseUrlJenkins = removeLastSlash(applicationProperties."config.jenkins.url") baseUrlOpenshift = removeLastSlash(applicationProperties."config.openshift.url") simulate = applicationProperties."config.simulate".toUpperCase() == 'TRUE' @@ -42,6 +52,10 @@ class BaseSpec extends GebReportingSpec { extraLoginPage = System.getenv("OCP_LOGIN_SELECTOR_PAGE")?.toUpperCase() == 'TRUE' ? true : false + quickstartersConfigMap = applicationProperties."config.openshift.quickstarters.configMap" + if (!quickstartersConfigMap) { + quickstartersConfigMap = 'quickstarters.properties' + } } def removeLastSlash(String str) { @@ -89,13 +103,15 @@ class BaseSpec extends GebReportingSpec { */ def doJenkinsLoginProcess() { via JenkinsLoginPage - loginButton.click() + if ($("a.btn.btn-lg.btn-primary").size() > 0) { + loginButton.click() + } if (extraLoginPage) { at(new JenkinsLoginSelectorPage()) ldapLink.click() } at OpenShiftLoginPage - doLogin() + doJenkinsLogin() if ($('input', name: 'approve')) { $('input', name: 'approve').click() } diff --git a/src/test/groovy/org/ods/e2e/util/GitUtil.groovy b/src/test/groovy/org/ods/e2e/util/GitUtil.groovy index 486d637..41101f1 100644 --- a/src/test/groovy/org/ods/e2e/util/GitUtil.groovy +++ b/src/test/groovy/org/ods/e2e/util/GitUtil.groovy @@ -1,8 +1,6 @@ package org.ods.e2e.util - import org.eclipse.jgit.api.Git -import org.eclipse.jgit.transport.URIish import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider import javax.net.ssl.X509TrustManager @@ -34,57 +32,86 @@ class GitUtil { static username = applicationProperties."config.atlassian.user.name" static password = applicationProperties."config.atlassian.user.password" - // Git repository used in the tests - static Git gitRepository - /** * Clone a repository in a temporary folder. * it ignores the ssl certificate for wrong certificates. * @param project the project key * @param repository the repository - * @return the repository location + * @return the repository */ - static cloneRepository(project, repository) { + static cloneRepository(project, repository, branch = null, isODSComponentRepo = true) { def gitUrl = baseUrlBitbucket.endsWith('/') ? baseUrlBitbucket : baseUrlBitbucket + '/' - def repositoryUrl = "${gitUrl}scm/$project/$project-${repository}.git".toLowerCase() - println "GitUtil: Cloning repository $repositoryUrl" + def repositoryUrl + if (isODSComponentRepo) { + repositoryUrl = "${gitUrl}scm/$project/$project-${repository}.git".toLowerCase() + } else { + repositoryUrl = "${gitUrl}scm/$project/${repository}.git".toLowerCase() + } File localPath = Files.createTempDirectory("${repository}-").toFile() - - gitRepository = Git.init().setDirectory(localPath).call() - def config = gitRepository.getRepository().getConfig() - config.setBoolean("http", null, "sslVerify", false) - config.save() - gitRepository.remoteAdd().setName('origin').setUri(new URIish(repositoryUrl)).call() - - gitRepository.pull() + def gitRepository = Git.cloneRepository() + .setDirectory(localPath) .setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password)) - .setRemote('origin') - .setRemoteBranchName('master') - .call() - - return localPath + .setURI(repositoryUrl) + .setBranch(branch).call() + gitRepository.close() + return gitRepository } /** * Commit all modified files + * @param gitRepository The git repository * @param message The commit message */ - static commitAddAll(message = 'new commit') { + static commitAddAll(gitRepository, message = 'new commit') { println "GitUil: Commit $message" gitRepository.commit().setMessage(message).setAll(true).call() } /** * Push commits to a repository + * @param gitRepository The git repository * @param remote The remote repository * @return */ - static push(remote = 'origin') { - println "GitUil: Push to $remote" + static push(gitRepository, remote = 'origin') { gitRepository.push() .setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password)) .setRemote(remote) .call() } + + /** + * Checkout an specific branch. + * @param gitRepository The git repository. + * @param branch The branch name. + * @param createBranch If you want to create the branch if it does not exists. + * @return + */ + static checkout(gitRepository, branch, createBranch = false) { + gitRepository.checkout() + .setName(branch) + .setCreateBranch(createBranch) + .call() + } + + /** + * Add files to the repository. + * @param gitRepository The repository. + * @param files The files. + */ + static add(gitRepository, files) { + gitRepository + .add() + .addFilepattern(files) + .call() + } + + static deleteBranch(Git gitRepository, branch, remote = false) { + def branches = [branch] + if (remote) { + branches << 'origin/' + branch + } + gitRepository.branchDelete().setBranchNames(branches as String[]).call() + } } diff --git a/src/test/groovy/org/ods/e2e/util/SpecHelper.groovy b/src/test/groovy/org/ods/e2e/util/SpecHelper.groovy index c612acf..c9c71b5 100644 --- a/src/test/groovy/org/ods/e2e/util/SpecHelper.groovy +++ b/src/test/groovy/org/ods/e2e/util/SpecHelper.groovy @@ -3,6 +3,7 @@ package org.ods.e2e.util import groovy.json.JsonSlurperClassic import kong.unirest.Unirest +import java.util.regex.Matcher import java.util.regex.Pattern class SpecHelper { @@ -26,7 +27,7 @@ class SpecHelper { println "ERROR: Missing properties in configuration $match[0]" raiseError = true } else { - value = value.replaceAll(Pattern.quote(nameToReplace), valueToReplace) + value = value.replaceAll(Pattern.quote(nameToReplace), Matcher.quoteReplacement(valueToReplace)) properties[key] = value } } diff --git a/src/test/resources/GebConfig.groovy b/src/test/resources/GebConfig.groovy index b8f7e11..b9fb09c 100644 --- a/src/test/resources/GebConfig.groovy +++ b/src/test/resources/GebConfig.groovy @@ -22,7 +22,7 @@ waiting { presets { extremelySlow { timeout = 3600 - retryInterval = 2 + retryInterval = 5 } verySlow { timeout = 600 diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 1a32fbd..260da8c 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -8,6 +8,7 @@ # OPENSHIFT_PUBLIC_HOST: Public host to access jenkins or the provisioning app # OPENSHIFT_CLUSTER: The OpenShift instance master instance. # BITBUCKET_URL: The url of Bitbucket +# BITBUCKET_BASE_BRANCH: The initial branch when cloning. HEAD if not specified. # JIRA_URL: The url of Jira # SIMULATE: Will help us during the develop of the test to simulate the creation of different artifacts # without creating them @@ -17,6 +18,8 @@ config.project.key=${OPENSHIFT_PROJECT} config.simulate=${SIMULATE} # Provisioning App +config.provisioning-app.deployCfg=${PROV_APP_DEPLOY_CFG} +config.provisioning-app.project=${PROV_APP_PROJECT} config.provisioning-app.user.name=${PROV_APP_USER} config.provisioning-app.user.password=${PROV_APP_PASSWORD} config.provisioning.url=https://${PROV_APP_NAME}${OPENSHIFT_PUBLIC_HOST} @@ -26,6 +29,7 @@ config.atlassian.user.name=${ATLASSIAN_USER} config.atlassian.user.password=${ATLASSIAN_PASSWORD} config.atlassian.jira.url=${JIRA_URL} config.atlassian.bitbucket.url=${BITBUCKET_URL} +config.atlassian.bitbucket.branch=${BITBUCKET_BASE_BRANCH} ## Jenkins config.jenkins.user.name=${JENKINS_USER} @@ -37,6 +41,8 @@ config.openshift.publichost=${OPENSHIFT_PUBLIC_HOST} config.openshift.user.name=${OPENSHIFT_USER} config.openshift.user.password=${OPENSHIFT_PASSWORD} config.openshift.url=${OPENSHIFT_CLUSTER} +config.openshift.quickstarters.configMap=${QUICKSTARTERS_CONFIGMAP} +config.openshift.token=${OPENSHIFT_TOKEN} ### Reports dir config.reports.dir=build/e2e-geb-reports diff --git a/src/test/resources/k8stypes.properties b/src/test/resources/k8stypes.properties new file mode 100644 index 0000000..056b515 --- /dev/null +++ b/src/test/resources/k8stypes.properties @@ -0,0 +1 @@ +com.openshift.internal.restclient.model.deploy.DeploymentRequest=.DeploymentRequest \ No newline at end of file