Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Add Che update e2e test #1117

Merged
merged 3 commits into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/e2e-minikube-operator-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#
# Copyright (c) 2021 Red Hat, Inc.
# This program and the accompanying materials are made
# available under the terms of the Eclipse Public License 2.0
# which is available at https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# Red Hat, Inc. - initial API and implementation
name: Minikube E2E
on: pull_request
jobs:
minikube-e2e-update:
name: e2e update tests
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v1
- name: Provision minikube cluster
run: |
minikube start --memory=6000
minikube addons enable ingress
- name: Install chectl dependencies
run: yarn
- name: Run e2e update tests minikube
run: |
export PLATFORM=minikube
export INSTALLER=operator
yarn test --coverage=false --forceExit --testRegex=test/e2e/e2e-upgrade.test.ts
113 changes: 113 additions & 0 deletions test/e2e/e2e-upgrade.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*********************************************************************
* Copyright (c) 2021 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/

// tslint:disable: no-console

import { expect } from '@oclif/test'

import { CheGithubClient } from '../../src/api/github-client'
import { isKubernetesPlatformFamily } from '../../src/util'

import { DEVFILE_URL, E2eHelper, NAMESPACE, NIGHTLY } from './util'

const helper = new E2eHelper()
jest.setTimeout(1000000)

const binChectl = `${process.cwd()}/bin/run`

const PLATFORM = process.env.PLATFORM || 'minikube'

const INSTALLER = 'operator'

const UPDATE_CHE_TIMEOUT_MS = 5 * 60 * 1000
const WORKSPACE_START_TIMEOUT_MS = 5 * 60 * 1000
const CHE_VERSION_TIMEOUT_MS = 50 * 1000

describe('Test Che upgrade', () => {
let cheVersion: string

describe('Prepare latest stable Che', () => {
it(`Deploy Che using ${INSTALLER} installer and self signed certificates`, async () => {
// Retrieve latest stable Che version
const githubClient = new CheGithubClient()
const latestStableCheTag = (await githubClient.getTemplatesTagInfo(INSTALLER, 'latest'))!
cheVersion = latestStableCheTag.name

const deployCommand = `${binChectl} server:deploy --platform=${PLATFORM} --installer=${INSTALLER} --version=${cheVersion} --chenamespace=${NAMESPACE} --telemetry=off --che-operator-cr-patch-yaml=test/e2e/resources/cr-patch.yaml`
await helper.runCliCommand(deployCommand)

await helper.waitForVersionInCheCR(cheVersion, CHE_VERSION_TIMEOUT_MS)
})

it('Prepare test workspace', async () => {
await runLoginTest()

// Create
await helper.runCliCommand(binChectl, ['workspace:create', `--devfile=${DEVFILE_URL}`, '--telemetry=off', `-n ${NAMESPACE}`])
const workspaceId = await helper.getWorkspaceId()

// Start
await helper.runCliCommand(binChectl, ['workspace:start', workspaceId, `-n ${NAMESPACE}`, '--telemetry=off'])
await helper.waitWorkspaceStatus('RUNNING', WORKSPACE_START_TIMEOUT_MS)

// Stop
await helper.runCliCommand(binChectl, ['workspace:stop', workspaceId, `-n ${NAMESPACE}`, '--telemetry=off'])
const workspaceStatus = await helper.getWorkspaceStatus()
// The status could be STOPPING or STOPPED
expect(workspaceStatus).to.contain('STOP')
})
})

describe('Test Che update', () => {
it('Update Che to nightly version', async () => {
await helper.runCliCommand(binChectl, ['server:update', '-y', `-n ${NAMESPACE}`, '--telemetry=off'])
await helper.waitForCheServerImageTag(NIGHTLY, UPDATE_CHE_TIMEOUT_MS)
})

it('Check updated Che version', async () => {
await helper.waitForVersionInCheCR(NIGHTLY, CHE_VERSION_TIMEOUT_MS)
})
})

describe('Test updated Che', () => {
it('Start existing workspace after update', async () => {
// Relogin
await runLoginTest()

const workspaceId = await helper.getWorkspaceId()
await helper.runCliCommand(binChectl, ['workspace:start', workspaceId, `-n ${NAMESPACE}`, '--telemetry=off'])
await helper.waitWorkspaceStatus('RUNNING', WORKSPACE_START_TIMEOUT_MS)
})
})

describe('Test Che downgrade', () => {
it('Downgrade Che', async () => {
await helper.runCliCommand(binChectl, ['server:update', '-y', `--version=${cheVersion}`, `-n ${NAMESPACE}`, '--telemetry=off'])
await helper.waitForCheServerImageTag(cheVersion, UPDATE_CHE_TIMEOUT_MS)
})

it('Check downgraded Che version', async () => {
await helper.waitForVersionInCheCR(cheVersion, CHE_VERSION_TIMEOUT_MS)
})
})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should check that workspace starts after downgrade.... But up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use-case in the tests, at least my thoughts about it, is that downgrade is used if a user have completed upgrade, but something stopped to work due to a bug, but the user need the feature, so a step back is taken temporally and right after upgrade... So no need in such check.
If a used needs specific version of Che, it is possible to install it via version flag, no need to downgrade.

})

async function runLoginTest() {
let cheApiEndpoint: string
if (isKubernetesPlatformFamily(PLATFORM)) {
cheApiEndpoint = await helper.K8SHostname('che', NAMESPACE) + '/api'
} else {
cheApiEndpoint = await helper.OCHostname('che', NAMESPACE) + '/api'
}

const stdout = await helper.runCliCommand(binChectl, ['auth:login', cheApiEndpoint, '-u', 'admin', '-p', 'admin', '-n', `${NAMESPACE}`, '--telemetry=off'])
expect(stdout).to.contain('Successfully logged into')
}
77 changes: 77 additions & 0 deletions test/e2e/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ interface WorkspaceInfo {

const binChectl = `${process.cwd()}/bin/run`

export const DEVFILE_URL = 'https://raw.githubusercontent.com/eclipse/che-devfile-registry/master/devfiles/quarkus/devfile.yaml'

export const NAMESPACE = 'eclipse-che'
export const NIGHTLY = 'nightly'

//Utilities to help e2e tests
export class E2eHelper {
protected kubeHelper: KubeHelper
Expand All @@ -40,6 +45,28 @@ export class E2eHelper {
this.oc = new OpenShiftHelper()
}

async runCliCommand(command: string, args?: string[], printOutput = true): Promise<string> {
if (printOutput) {
// tslint:disable-next-line: no-console
console.log(`Running command: ${command} ${args ? args.join(' ') : ''}`)
}

const { exitCode, stdout, stderr } = await execa(command, args, { shell: true })

if (printOutput) {
// tslint:disable-next-line: no-console
console.log(stdout)
if (exitCode !== 0) {
// tslint:disable-next-line: no-console
console.log(stderr)
}
}

expect(exitCode).toEqual(0)

return stdout
}

// Return an array with all user workspaces
// async getAllWorkspaces(isOpenshiftPlatformFamily: string): Promise<chetypes.workspace.Workspace[]> {
private async getAllWorkspaces(): Promise<WorkspaceInfo[]> {
Expand Down Expand Up @@ -85,6 +112,20 @@ export class E2eHelper {
return workspaceStatus
}

async waitWorkspaceStatus(status: string, timeoutMs: number): Promise<boolean> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually wait methods return void or throw an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to wait for an event and then decide what to do: fail or continue. But maybe it is not needed for tests...

const delayMs = 1000 * 5

let totalTimeMs = 0
while (totalTimeMs < timeoutMs) {
if (await this.getWorkspaceStatus() === status) {
return true
}
await this.sleep(delayMs)
totalTimeMs += delayMs
}
return false
}

//Return a route from Openshift adding protocol
async OCHostname(ingressName: string, namespace: string): Promise<string> {
if (await this.oc.routeExist(ingressName, namespace)) {
Expand Down Expand Up @@ -112,4 +153,40 @@ export class E2eHelper {
// tslint:disable-next-line no-string-based-set-timeout
return new Promise(resolve => setTimeout(resolve, ms))
}

async waitForVersionInCheCR(version: string, timeoutMs: number): Promise<void> {
const delayMs = 5 * 1000

let totalTimeMs = 0
while (totalTimeMs < timeoutMs) {
const cheCR = await this.kubeHelper.getCheCluster(NAMESPACE)
if (cheCR && cheCR.status && cheCR.status.cheVersion === version) {
return
}
await this.sleep(delayMs)
totalTimeMs += delayMs
}
throw new Error(`Che CR version ${version} has not appeared in ${timeoutMs / 1000}s`)
}

async waitForCheServerImageTag(tag: string, timeoutMs: number): Promise<void> {
const delayMs = 5 * 1000
const chePodNameRegExp = new RegExp('che-[0-9a-f]+-.*')

let totalTimeMs = 0
while (totalTimeMs < timeoutMs) {
const pods = (await this.kubeHelper.listNamespacedPod(NAMESPACE)).items
const pod = pods.find((pod => pod.metadata && pod.metadata.name && pod.metadata.name.match(chePodNameRegExp)))
if (pod && pod.status && pod.status.containerStatuses && pod.status.containerStatuses[0].image) {
const imageTag = pod.status.containerStatuses[0].image.split(':')[1]
if (imageTag === tag) {
return
}
}
await this.sleep(delayMs)
totalTimeMs += delayMs
}
throw new Error(`Che server image tag ${tag} has not appeared in ${timeoutMs / 1000}s `)
}

}