mirror of
https://github.com/Azure/k8s-deploy.git
synced 2026-07-01 01:39:26 +08:00
Add timeout to the rollout status (#425)
* Added timeout to the rollout status and tests for it * Fixed integration test errors * Fix for blue green integration test * Probable fix for integration errors * No jobs run error fixed * Changed timeout to file level constant * Added parsing logic for timeout * Made tests more concise * implemented timeout validation check in an extracted utils mod * Changed function name to parseDuration * Removed timeout parameter from getResource --------- Co-authored-by: David Gamero <david340804@gmail.com> Co-authored-by: Suneha Bose <123775811+bosesuneha@users.noreply.github.com>
This commit is contained in:
@@ -19,6 +19,19 @@ import {ExecOutput} from '@actions/exec'
|
||||
jest.mock('../../types/kubectl')
|
||||
|
||||
const kubectl = new Kubectl('')
|
||||
const TEST_TIMEOUT = '60s'
|
||||
|
||||
// Test constants to follow DRY principle
|
||||
const EXPECTED_GREEN_OBJECTS = [
|
||||
{name: 'nginx-service-green', kind: 'Service'},
|
||||
{name: 'nginx-deployment-green', kind: 'Deployment'}
|
||||
]
|
||||
|
||||
const MOCK_EXEC_OUTPUT = {
|
||||
exitCode: 0,
|
||||
stderr: '',
|
||||
stdout: ''
|
||||
} as ExecOutput
|
||||
|
||||
describe('bluegreenhelper functions', () => {
|
||||
let testObjects
|
||||
@@ -40,28 +53,56 @@ describe('bluegreenhelper functions', () => {
|
||||
[].concat(
|
||||
testObjects.deploymentEntityList,
|
||||
testObjects.serviceEntityList
|
||||
)
|
||||
),
|
||||
TEST_TIMEOUT
|
||||
)
|
||||
|
||||
expect(value).toHaveLength(2)
|
||||
expect(value).toContainEqual({
|
||||
name: 'nginx-service-green',
|
||||
kind: 'Service'
|
||||
expect(value).toHaveLength(EXPECTED_GREEN_OBJECTS.length)
|
||||
EXPECTED_GREEN_OBJECTS.forEach((expectedObject) => {
|
||||
expect(value).toContainEqual(expectedObject)
|
||||
})
|
||||
expect(value).toContainEqual({
|
||||
name: 'nginx-deployment-green',
|
||||
kind: 'Deployment'
|
||||
})
|
||||
|
||||
test('handles timeout when deleting objects', async () => {
|
||||
// Mock deleteObjects to prevent actual execution
|
||||
const deleteSpy = jest
|
||||
.spyOn(kubectl, 'delete')
|
||||
.mockResolvedValue(MOCK_EXEC_OUTPUT)
|
||||
|
||||
await bgHelper.deleteObjects(
|
||||
kubectl,
|
||||
EXPECTED_GREEN_OBJECTS,
|
||||
TEST_TIMEOUT
|
||||
)
|
||||
|
||||
// Verify kubectl.delete is called with timeout for each object in deleteList
|
||||
expect(deleteSpy).toHaveBeenCalledTimes(EXPECTED_GREEN_OBJECTS.length)
|
||||
EXPECTED_GREEN_OBJECTS.forEach(({name, kind}) => {
|
||||
expect(deleteSpy).toHaveBeenCalledWith(
|
||||
[kind, name],
|
||||
undefined,
|
||||
TEST_TIMEOUT
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test('parses objects correctly from one file (getManifestObjects)', () => {
|
||||
expect(testObjects.deploymentEntityList[0].kind).toBe('Deployment')
|
||||
expect(testObjects.serviceEntityList[0].kind).toBe('Service')
|
||||
expect(testObjects.ingressEntityList[0].kind).toBe('Ingress')
|
||||
const expectedTypes = [
|
||||
{
|
||||
list: testObjects.deploymentEntityList,
|
||||
kind: 'Deployment',
|
||||
selectorApp: 'nginx'
|
||||
},
|
||||
{list: testObjects.serviceEntityList, kind: 'Service'},
|
||||
{list: testObjects.ingressEntityList, kind: 'Ingress'}
|
||||
]
|
||||
|
||||
expect(
|
||||
testObjects.deploymentEntityList[0].spec.selector.matchLabels.app
|
||||
).toBe('nginx')
|
||||
expectedTypes.forEach(({list, kind, selectorApp}) => {
|
||||
expect(list[0].kind).toBe(kind)
|
||||
if (selectorApp) {
|
||||
expect(list[0].spec.selector.matchLabels.app).toBe(selectorApp)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('parses other kinds of objects (getManifestObjects)', () => {
|
||||
@@ -102,23 +143,24 @@ describe('bluegreenhelper functions', () => {
|
||||
})
|
||||
|
||||
test('correctly makes new blue green object (getNewBlueGreenObject and addBlueGreenLabelsAndAnnotations)', () => {
|
||||
const modifiedDeployment = getNewBlueGreenObject(
|
||||
testObjects.deploymentEntityList[0],
|
||||
GREEN_LABEL_VALUE
|
||||
)
|
||||
const testCases = [
|
||||
{
|
||||
object: testObjects.deploymentEntityList[0],
|
||||
expectedName: 'nginx-deployment-green'
|
||||
},
|
||||
{
|
||||
object: testObjects.serviceEntityList[0],
|
||||
expectedName: 'nginx-service-green'
|
||||
}
|
||||
]
|
||||
|
||||
expect(modifiedDeployment.metadata.name).toBe('nginx-deployment-green')
|
||||
expect(modifiedDeployment.metadata.labels['k8s.deploy.color']).toBe(
|
||||
'green'
|
||||
)
|
||||
|
||||
const modifiedSvc = getNewBlueGreenObject(
|
||||
testObjects.serviceEntityList[0],
|
||||
GREEN_LABEL_VALUE
|
||||
)
|
||||
|
||||
expect(modifiedSvc.metadata.name).toBe('nginx-service-green')
|
||||
expect(modifiedSvc.metadata.labels['k8s.deploy.color']).toBe('green')
|
||||
testCases.forEach(({object, expectedName}) => {
|
||||
const modifiedObject = getNewBlueGreenObject(object, GREEN_LABEL_VALUE)
|
||||
expect(modifiedObject.metadata.name).toBe(expectedName)
|
||||
expect(modifiedObject.metadata.labels['k8s.deploy.color']).toBe(
|
||||
'green'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test('correctly fetches k8s objects', async () => {
|
||||
@@ -140,24 +182,41 @@ describe('bluegreenhelper functions', () => {
|
||||
})
|
||||
|
||||
test('exits when fails to fetch k8s objects', async () => {
|
||||
const mockExecOutput = {
|
||||
stdout: 'this should not matter',
|
||||
exitCode: 0,
|
||||
stderr: 'this is a fake error'
|
||||
} as ExecOutput
|
||||
jest
|
||||
.spyOn(kubectl, 'getResource')
|
||||
.mockImplementation(() => Promise.resolve(mockExecOutput))
|
||||
let fetched = await fetchResource(
|
||||
kubectl,
|
||||
'nginx-deployment',
|
||||
'Deployment'
|
||||
)
|
||||
expect(fetched).toBe(null)
|
||||
const errorTestCases = [
|
||||
{
|
||||
description: 'with stderr error',
|
||||
mockOutput: {
|
||||
stdout: 'this should not matter',
|
||||
exitCode: 0,
|
||||
stderr: 'this is a fake error'
|
||||
} as ExecOutput,
|
||||
mockImplementation: () => Promise.resolve
|
||||
},
|
||||
{
|
||||
description: 'with undefined implementation',
|
||||
mockOutput: null,
|
||||
mockImplementation: () => undefined
|
||||
}
|
||||
]
|
||||
|
||||
jest.spyOn(kubectl, 'getResource').mockImplementation()
|
||||
fetched = await fetchResource(kubectl, 'nginx-deployment', 'Deployment')
|
||||
expect(fetched).toBe(null)
|
||||
for (const testCase of errorTestCases) {
|
||||
const spy = jest.spyOn(kubectl, 'getResource')
|
||||
|
||||
if (testCase.mockOutput) {
|
||||
spy.mockImplementation(() => Promise.resolve(testCase.mockOutput))
|
||||
} else {
|
||||
spy.mockImplementation()
|
||||
}
|
||||
|
||||
const fetched = await fetchResource(
|
||||
kubectl,
|
||||
'nginx-deployment',
|
||||
'Deployment'
|
||||
)
|
||||
expect(fetched).toBe(null)
|
||||
|
||||
spy.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
test('returns null when fetch fails to unset k8s objects', async () => {
|
||||
|
||||
@@ -32,7 +32,8 @@ export const STABLE_SUFFIX = '-stable'
|
||||
|
||||
export async function deleteGreenObjects(
|
||||
kubectl: Kubectl,
|
||||
toDelete: K8sObject[]
|
||||
toDelete: K8sObject[],
|
||||
timeout?: string
|
||||
): Promise<K8sDeleteObject[]> {
|
||||
// const resourcesToDelete: K8sDeleteObject[] = []
|
||||
const resourcesToDelete: K8sDeleteObject[] = toDelete.map((obj) => {
|
||||
@@ -45,18 +46,23 @@ export async function deleteGreenObjects(
|
||||
|
||||
core.debug(`deleting green objects: ${JSON.stringify(resourcesToDelete)}`)
|
||||
|
||||
await deleteObjects(kubectl, resourcesToDelete)
|
||||
await deleteObjects(kubectl, resourcesToDelete, timeout)
|
||||
return resourcesToDelete
|
||||
}
|
||||
|
||||
export async function deleteObjects(
|
||||
kubectl: Kubectl,
|
||||
deleteList: K8sDeleteObject[]
|
||||
deleteList: K8sDeleteObject[],
|
||||
timeout?: string
|
||||
) {
|
||||
// delete services and deployments
|
||||
for (const delObject of deleteList) {
|
||||
try {
|
||||
const result = await kubectl.delete([delObject.kind, delObject.name])
|
||||
const result = await kubectl.delete(
|
||||
[delObject.kind, delObject.name],
|
||||
delObject.namespace,
|
||||
timeout
|
||||
)
|
||||
checkForErrors([result])
|
||||
} catch (ex) {
|
||||
core.debug(`failed to delete object ${delObject.name}: ${ex}`)
|
||||
@@ -141,7 +147,8 @@ export function isServiceRouted(
|
||||
export async function deployWithLabel(
|
||||
kubectl: Kubectl,
|
||||
deploymentObjectList: any[],
|
||||
nextLabel: string
|
||||
nextLabel: string,
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
const newObjectsList = deploymentObjectList.map((inputObject) =>
|
||||
getNewBlueGreenObject(inputObject, nextLabel)
|
||||
@@ -150,7 +157,7 @@ export async function deployWithLabel(
|
||||
core.debug(
|
||||
`objects deployed with label are ${JSON.stringify(newObjectsList)}`
|
||||
)
|
||||
const deployResult = await deployObjects(kubectl, newObjectsList)
|
||||
const deployResult = await deployObjects(kubectl, newObjectsList, timeout)
|
||||
return {deployResult, objects: newObjectsList}
|
||||
}
|
||||
|
||||
@@ -267,15 +274,26 @@ export async function fetchResource(
|
||||
|
||||
export async function deployObjects(
|
||||
kubectl: Kubectl,
|
||||
objectsList: any[]
|
||||
objectsList: any[],
|
||||
timeout?: string
|
||||
): Promise<DeployResult> {
|
||||
// Handle empty objects list gracefully to prevent "Configuration paths must exist" error
|
||||
if (!objectsList || objectsList.length === 0) {
|
||||
core.debug('No objects to deploy, skipping kubectl apply')
|
||||
return {
|
||||
execResult: {exitCode: 0, stdout: '', stderr: ''},
|
||||
manifestFiles: []
|
||||
}
|
||||
}
|
||||
|
||||
const manifestFiles = fileHelper.writeObjectsToFile(objectsList)
|
||||
const forceDeployment = core.getInput('force').toLowerCase() === 'true'
|
||||
const serverSideApply = core.getInput('server-side').toLowerCase() === 'true'
|
||||
const execResult = await kubectl.apply(
|
||||
manifestFiles,
|
||||
forceDeployment,
|
||||
serverSideApply
|
||||
serverSideApply,
|
||||
timeout
|
||||
)
|
||||
|
||||
return {execResult, manifestFiles}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import {getManifestObjects} from './blueGreenHelper'
|
||||
import {BlueGreenDeployment} from '../../types/blueGreenTypes'
|
||||
import {deployBlueGreen, deployBlueGreenIngress} from './deploy'
|
||||
import {
|
||||
deployBlueGreen,
|
||||
deployBlueGreenIngress,
|
||||
deployBlueGreenService,
|
||||
deployBlueGreenSMI
|
||||
} from './deploy'
|
||||
import * as routeTester from './route'
|
||||
import {Kubectl} from '../../types/kubectl'
|
||||
import {RouteStrategy} from '../../types/routeStrategy'
|
||||
import * as TSutils from '../../utilities/trafficSplitUtils'
|
||||
import * as bgHelper from './blueGreenHelper'
|
||||
import * as smiHelper from './smiBlueGreenHelper'
|
||||
|
||||
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
|
||||
|
||||
@@ -73,3 +80,259 @@ describe('deploy tests', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Timeout tests
|
||||
describe('deploy timeout tests', () => {
|
||||
let testObjects
|
||||
beforeEach(() => {
|
||||
//@ts-ignore
|
||||
Kubectl.mockClear()
|
||||
testObjects = getManifestObjects(ingressFilepath)
|
||||
})
|
||||
|
||||
test('deployBlueGreen with timeout passes to strategy functions', async () => {
|
||||
const kubectl = new Kubectl('')
|
||||
const timeout = '300s'
|
||||
|
||||
const mockBgDeployment: BlueGreenDeployment = {
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
}
|
||||
|
||||
// Mock the helper functions that are actually called
|
||||
const deployWithLabelSpy = jest
|
||||
.spyOn(bgHelper, 'deployWithLabel')
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
const setupSMISpy = jest
|
||||
.spyOn(smiHelper, 'setupSMI')
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
const routeSpy = jest
|
||||
.spyOn(routeTester, 'routeBlueGreenForDeploy')
|
||||
.mockResolvedValue(mockBgDeployment)
|
||||
|
||||
// Test INGRESS strategy
|
||||
await deployBlueGreen(
|
||||
kubectl,
|
||||
ingressFilepath,
|
||||
RouteStrategy.INGRESS,
|
||||
timeout
|
||||
)
|
||||
expect(deployWithLabelSpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
expect.any(String),
|
||||
timeout
|
||||
)
|
||||
|
||||
// Test SERVICE strategy
|
||||
deployWithLabelSpy.mockClear()
|
||||
deployObjectsSpy.mockClear()
|
||||
await deployBlueGreen(
|
||||
kubectl,
|
||||
ingressFilepath,
|
||||
RouteStrategy.SERVICE,
|
||||
timeout
|
||||
)
|
||||
expect(deployWithLabelSpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
expect.any(String),
|
||||
timeout
|
||||
)
|
||||
|
||||
// Test SMI strategy
|
||||
deployWithLabelSpy.mockClear()
|
||||
setupSMISpy.mockClear()
|
||||
await deployBlueGreen(
|
||||
kubectl,
|
||||
ingressFilepath,
|
||||
RouteStrategy.SMI,
|
||||
timeout
|
||||
)
|
||||
expect(setupSMISpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
|
||||
deployWithLabelSpy.mockRestore()
|
||||
deployObjectsSpy.mockRestore()
|
||||
setupSMISpy.mockRestore()
|
||||
routeSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('deployBlueGreenIngress with timeout', async () => {
|
||||
const kubectl = new Kubectl('')
|
||||
const timeout = '240s'
|
||||
|
||||
// Mock the dependencies
|
||||
const deployWithLabelSpy = jest
|
||||
.spyOn(bgHelper, 'deployWithLabel')
|
||||
.mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
})
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
await deployBlueGreenIngress(kubectl, ingressFilepath, timeout)
|
||||
|
||||
// Verify deployWithLabel was called with timeout
|
||||
expect(deployWithLabelSpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
expect.any(String),
|
||||
timeout
|
||||
)
|
||||
|
||||
// Verify deployObjects was called with timeout
|
||||
expect(deployObjectsSpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
|
||||
deployWithLabelSpy.mockRestore()
|
||||
deployObjectsSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('deployBlueGreenService with timeout', async () => {
|
||||
const kubectl = new Kubectl('')
|
||||
const timeout = '180s'
|
||||
|
||||
// Mock the dependencies
|
||||
const deployWithLabelSpy = jest
|
||||
.spyOn(bgHelper, 'deployWithLabel')
|
||||
.mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
})
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
await deployBlueGreenService(kubectl, ingressFilepath, timeout)
|
||||
|
||||
// Verify deployWithLabel was called with timeout
|
||||
expect(deployWithLabelSpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
expect.any(String),
|
||||
timeout
|
||||
)
|
||||
|
||||
// Verify deployObjects was called with timeout
|
||||
expect(deployObjectsSpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
|
||||
deployWithLabelSpy.mockRestore()
|
||||
deployObjectsSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('deployBlueGreenSMI with timeout', async () => {
|
||||
const kubectl = new Kubectl('')
|
||||
const timeout = '360s'
|
||||
|
||||
// Mock the dependencies
|
||||
const setupSMISpy = jest.spyOn(smiHelper, 'setupSMI').mockResolvedValue({
|
||||
objects: [],
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
}
|
||||
})
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
const deployWithLabelSpy = jest
|
||||
.spyOn(bgHelper, 'deployWithLabel')
|
||||
.mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
})
|
||||
|
||||
await deployBlueGreenSMI(kubectl, ingressFilepath, timeout)
|
||||
|
||||
// Verify setupSMI was called with timeout
|
||||
expect(setupSMISpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
|
||||
// Verify deployObjects was called with timeout
|
||||
expect(deployObjectsSpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
|
||||
setupSMISpy.mockRestore()
|
||||
deployObjectsSpy.mockRestore()
|
||||
deployWithLabelSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('deploy functions without timeout should pass undefined', async () => {
|
||||
const kubectl = new Kubectl('')
|
||||
|
||||
const deployWithLabelSpy = jest
|
||||
.spyOn(bgHelper, 'deployWithLabel')
|
||||
.mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
})
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
await deployBlueGreenIngress(kubectl, ingressFilepath)
|
||||
|
||||
// Verify deployWithLabel was called with undefined timeout
|
||||
expect(deployWithLabelSpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
expect.any(String),
|
||||
undefined
|
||||
)
|
||||
|
||||
deployWithLabelSpy.mockRestore()
|
||||
deployObjectsSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -22,16 +22,17 @@ import {DeployResult} from '../../types/deployResult'
|
||||
export async function deployBlueGreen(
|
||||
kubectl: Kubectl,
|
||||
files: string[],
|
||||
routeStrategy: RouteStrategy
|
||||
routeStrategy: RouteStrategy,
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
const blueGreenDeployment = await (async () => {
|
||||
switch (routeStrategy) {
|
||||
case RouteStrategy.INGRESS:
|
||||
return await deployBlueGreenIngress(kubectl, files)
|
||||
return await deployBlueGreenIngress(kubectl, files, timeout)
|
||||
case RouteStrategy.SMI:
|
||||
return await deployBlueGreenSMI(kubectl, files)
|
||||
return await deployBlueGreenSMI(kubectl, files, timeout)
|
||||
default:
|
||||
return await deployBlueGreenService(kubectl, files)
|
||||
return await deployBlueGreenService(kubectl, files, timeout)
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -39,7 +40,8 @@ export async function deployBlueGreen(
|
||||
const routeDeployment = await routeBlueGreenForDeploy(
|
||||
kubectl,
|
||||
files,
|
||||
routeStrategy
|
||||
routeStrategy,
|
||||
timeout
|
||||
)
|
||||
core.endGroup()
|
||||
|
||||
@@ -52,7 +54,8 @@ export async function deployBlueGreen(
|
||||
|
||||
export async function deployBlueGreenSMI(
|
||||
kubectl: Kubectl,
|
||||
filePaths: string[]
|
||||
filePaths: string[],
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
// get all kubernetes objects defined in manifest files
|
||||
const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths)
|
||||
@@ -67,20 +70,23 @@ export async function deployBlueGreenSMI(
|
||||
|
||||
const otherObjDeployment: DeployResult = await deployObjects(
|
||||
kubectl,
|
||||
newObjectsList
|
||||
newObjectsList,
|
||||
timeout
|
||||
)
|
||||
|
||||
// make extraservices and trafficsplit
|
||||
const smiAndSvcDeployment = await setupSMI(
|
||||
kubectl,
|
||||
manifestObjects.serviceEntityList
|
||||
manifestObjects.serviceEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
// create new deloyments
|
||||
const blueGreenDeployment: BlueGreenDeployment = await deployWithLabel(
|
||||
kubectl,
|
||||
manifestObjects.deploymentEntityList,
|
||||
GREEN_LABEL_VALUE
|
||||
GREEN_LABEL_VALUE,
|
||||
timeout
|
||||
)
|
||||
|
||||
blueGreenDeployment.objects.push(...newObjectsList)
|
||||
@@ -98,7 +104,8 @@ export async function deployBlueGreenSMI(
|
||||
|
||||
export async function deployBlueGreenIngress(
|
||||
kubectl: Kubectl,
|
||||
filePaths: string[]
|
||||
filePaths: string[],
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
// get all kubernetes objects defined in manifest files
|
||||
const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths)
|
||||
@@ -111,14 +118,15 @@ export async function deployBlueGreenIngress(
|
||||
const workloadDeployment: BlueGreenDeployment = await deployWithLabel(
|
||||
kubectl,
|
||||
servicesAndDeployments,
|
||||
GREEN_LABEL_VALUE
|
||||
GREEN_LABEL_VALUE,
|
||||
timeout
|
||||
)
|
||||
|
||||
const otherObjects = [].concat(
|
||||
manifestObjects.otherObjects,
|
||||
manifestObjects.unroutedServiceEntityList
|
||||
)
|
||||
await deployObjects(kubectl, otherObjects)
|
||||
await deployObjects(kubectl, otherObjects, timeout)
|
||||
core.debug(
|
||||
`new objects after processing services and other objects: \n
|
||||
${JSON.stringify(servicesAndDeployments)}`
|
||||
@@ -132,7 +140,8 @@ export async function deployBlueGreenIngress(
|
||||
|
||||
export async function deployBlueGreenService(
|
||||
kubectl: Kubectl,
|
||||
filePaths: string[]
|
||||
filePaths: string[],
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
const manifestObjects: BlueGreenManifests = getManifestObjects(filePaths)
|
||||
|
||||
@@ -140,7 +149,8 @@ export async function deployBlueGreenService(
|
||||
const blueGreenDeployment: BlueGreenDeployment = await deployWithLabel(
|
||||
kubectl,
|
||||
manifestObjects.deploymentEntityList,
|
||||
GREEN_LABEL_VALUE
|
||||
GREEN_LABEL_VALUE,
|
||||
timeout
|
||||
)
|
||||
|
||||
// create other non deployment and non service entities
|
||||
@@ -150,7 +160,7 @@ export async function deployBlueGreenService(
|
||||
manifestObjects.unroutedServiceEntityList
|
||||
)
|
||||
|
||||
await deployObjects(kubectl, newObjectsList)
|
||||
await deployObjects(kubectl, newObjectsList, timeout)
|
||||
// returning deployment details to check for rollout stability
|
||||
return {
|
||||
deployResult: blueGreenDeployment.deployResult,
|
||||
|
||||
@@ -156,3 +156,135 @@ describe('promote tests', () => {
|
||||
expect(promoteBlueGreenSMI(kubectl, testObjects)).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
// Timeout tests
|
||||
describe('promote timeout tests', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
Kubectl.mockClear()
|
||||
testObjects = getManifestObjects(ingressFilepath)
|
||||
})
|
||||
|
||||
const mockDeployWithLabel = () =>
|
||||
jest.spyOn(bgHelper, 'deployWithLabel').mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: []
|
||||
})
|
||||
|
||||
const setupFetchResource = (
|
||||
kind: string,
|
||||
name: string,
|
||||
labelValue: string
|
||||
) => {
|
||||
const mockLabels = new Map<string, string>()
|
||||
mockLabels[bgHelper.BLUE_GREEN_VERSION_LABEL] = labelValue
|
||||
|
||||
jest.spyOn(bgHelper, 'fetchResource').mockResolvedValue({
|
||||
kind,
|
||||
spec: {},
|
||||
metadata: {labels: mockLabels, name}
|
||||
})
|
||||
}
|
||||
|
||||
test.each([
|
||||
{
|
||||
name: 'promoteBlueGreenIngress with timeout',
|
||||
fn: promoteBlueGreenIngress,
|
||||
kind: 'Ingress',
|
||||
resourceName: 'nginx-ingress-green',
|
||||
timeout: '300s',
|
||||
setup: () =>
|
||||
setupFetchResource(
|
||||
'Ingress',
|
||||
'nginx-ingress-green',
|
||||
bgHelper.GREEN_LABEL_VALUE
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'promoteBlueGreenService with timeout',
|
||||
fn: promoteBlueGreenService,
|
||||
kind: 'Service',
|
||||
resourceName: 'nginx-service-green',
|
||||
timeout: '240s',
|
||||
setup: () => {
|
||||
setupFetchResource(
|
||||
'Service',
|
||||
'nginx-service-green',
|
||||
bgHelper.GREEN_LABEL_VALUE
|
||||
)
|
||||
jest
|
||||
.spyOn(servicesTester, 'validateServicesState')
|
||||
.mockResolvedValue(true)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'promoteBlueGreenSMI with timeout',
|
||||
fn: promoteBlueGreenSMI,
|
||||
kind: 'TrafficSplit',
|
||||
resourceName: 'nginx-service-trafficsplit',
|
||||
timeout: '180s',
|
||||
setup: () => {
|
||||
const mockTsObject: TrafficSplitObject = {
|
||||
apiVersion: 'v1alpha3',
|
||||
kind: TRAFFIC_SPLIT_OBJECT,
|
||||
metadata: {
|
||||
name: 'nginx-service-trafficsplit',
|
||||
labels: new Map<string, string>(),
|
||||
annotations: new Map<string, string>()
|
||||
},
|
||||
spec: {
|
||||
service: 'nginx-service',
|
||||
backends: [
|
||||
{service: 'nginx-service-stable', weight: MIN_VAL},
|
||||
{service: 'nginx-service-green', weight: MAX_VAL}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
jest
|
||||
.spyOn(bgHelper, 'fetchResource')
|
||||
.mockResolvedValue(mockTsObject)
|
||||
jest
|
||||
.spyOn(smiTester, 'validateTrafficSplitsState')
|
||||
.mockResolvedValue(true)
|
||||
}
|
||||
}
|
||||
])('$name', async ({fn, timeout, setup}) => {
|
||||
setup()
|
||||
const deployWithLabelSpy = mockDeployWithLabel()
|
||||
|
||||
await fn(kubectl, testObjects, timeout)
|
||||
|
||||
expect(deployWithLabelSpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
bgHelper.NONE_LABEL_VALUE,
|
||||
timeout
|
||||
)
|
||||
|
||||
deployWithLabelSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('promote functions without timeout should pass undefined', async () => {
|
||||
setupFetchResource(
|
||||
'Ingress',
|
||||
'nginx-ingress-green',
|
||||
bgHelper.GREEN_LABEL_VALUE
|
||||
)
|
||||
const deployWithLabelSpy = mockDeployWithLabel()
|
||||
|
||||
await promoteBlueGreenIngress(kubectl, testObjects)
|
||||
|
||||
expect(deployWithLabelSpy).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
expect.any(Array),
|
||||
bgHelper.NONE_LABEL_VALUE,
|
||||
undefined
|
||||
)
|
||||
|
||||
deployWithLabelSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,7 +11,8 @@ import {validateTrafficSplitsState} from './smiBlueGreenHelper'
|
||||
|
||||
export async function promoteBlueGreenIngress(
|
||||
kubectl: Kubectl,
|
||||
manifestObjects
|
||||
manifestObjects,
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
//checking if anything to promote
|
||||
const {areValid, invalidIngresses} = await validateIngresses(
|
||||
@@ -32,7 +33,8 @@ export async function promoteBlueGreenIngress(
|
||||
manifestObjects.deploymentEntityList,
|
||||
manifestObjects.serviceEntityList
|
||||
),
|
||||
NONE_LABEL_VALUE
|
||||
NONE_LABEL_VALUE,
|
||||
timeout
|
||||
)
|
||||
|
||||
// create stable services with new configuration
|
||||
@@ -41,7 +43,8 @@ export async function promoteBlueGreenIngress(
|
||||
|
||||
export async function promoteBlueGreenService(
|
||||
kubectl: Kubectl,
|
||||
manifestObjects
|
||||
manifestObjects,
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
// checking if services are in the right state ie. targeting green deployments
|
||||
if (
|
||||
@@ -54,13 +57,15 @@ export async function promoteBlueGreenService(
|
||||
return await deployWithLabel(
|
||||
kubectl,
|
||||
manifestObjects.deploymentEntityList,
|
||||
NONE_LABEL_VALUE
|
||||
NONE_LABEL_VALUE,
|
||||
timeout
|
||||
)
|
||||
}
|
||||
|
||||
export async function promoteBlueGreenSMI(
|
||||
kubectl: Kubectl,
|
||||
manifestObjects
|
||||
manifestObjects,
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
// checking if there is something to promote
|
||||
if (
|
||||
@@ -76,6 +81,7 @@ export async function promoteBlueGreenSMI(
|
||||
return await deployWithLabel(
|
||||
kubectl,
|
||||
manifestObjects.deploymentEntityList,
|
||||
NONE_LABEL_VALUE
|
||||
NONE_LABEL_VALUE,
|
||||
timeout
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,9 +8,13 @@ import {
|
||||
rejectBlueGreenService,
|
||||
rejectBlueGreenSMI
|
||||
} from './reject'
|
||||
import * as bgHelper from './blueGreenHelper'
|
||||
import * as routeHelper from './route'
|
||||
|
||||
const ingressFilepath = ['test/unit/manifests/test-ingress-new.yml']
|
||||
const kubectl = new Kubectl('')
|
||||
const TEST_TIMEOUT_SHORT = '60s'
|
||||
const TEST_TIMEOUT_LONG = '120s'
|
||||
|
||||
jest.mock('../../types/kubectl')
|
||||
|
||||
@@ -43,15 +47,140 @@ describe('reject tests', () => {
|
||||
expect(bgDeployment.objects[0].metadata.name).toBe('nginx-ingress')
|
||||
})
|
||||
|
||||
test('reject blue/green service', async () => {
|
||||
const value = await rejectBlueGreenService(kubectl, testObjects)
|
||||
test('reject blue/green ingress with timeout', async () => {
|
||||
// Mock routeBlueGreenIngressUnchanged and deleteGreenObjects
|
||||
jest
|
||||
.spyOn(routeHelper, 'routeBlueGreenIngressUnchanged')
|
||||
.mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {stdout: '', stderr: '', exitCode: 0},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: [
|
||||
{
|
||||
kind: 'Ingress',
|
||||
metadata: {
|
||||
name: 'nginx-ingress',
|
||||
labels: new Map<string, string>()
|
||||
},
|
||||
spec: {}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
jest.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue([
|
||||
{name: 'nginx-service-green', kind: 'Service'},
|
||||
{name: 'nginx-deployment-green', kind: 'Deployment'}
|
||||
])
|
||||
|
||||
const value = await rejectBlueGreenIngress(
|
||||
kubectl,
|
||||
testObjects,
|
||||
TEST_TIMEOUT_LONG
|
||||
)
|
||||
|
||||
const bgDeployment = value.routeResult
|
||||
const deleteResult = value.deleteResult
|
||||
|
||||
expect(deleteResult).toHaveLength(2)
|
||||
for (const obj of deleteResult) {
|
||||
if (obj.kind === 'Service') {
|
||||
expect(obj.name).toBe('nginx-service-green')
|
||||
}
|
||||
if (obj.kind === 'Deployment') {
|
||||
expect(obj.name).toBe('nginx-deployment-green')
|
||||
}
|
||||
}
|
||||
|
||||
expect(bgDeployment.objects).toHaveLength(1)
|
||||
expect(bgDeployment.objects[0].metadata.name).toBe('nginx-ingress')
|
||||
|
||||
// Verify deleteGreenObjects is called with timeout
|
||||
expect(bgHelper.deleteGreenObjects).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
[].concat(
|
||||
testObjects.deploymentEntityList,
|
||||
testObjects.serviceEntityList
|
||||
),
|
||||
TEST_TIMEOUT_LONG
|
||||
)
|
||||
expect(routeHelper.routeBlueGreenIngressUnchanged).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
testObjects.serviceNameMap,
|
||||
testObjects.ingressEntityList,
|
||||
TEST_TIMEOUT_LONG
|
||||
)
|
||||
})
|
||||
|
||||
test('reject blue/green service', async () => {
|
||||
jest.spyOn(bgHelper, 'deleteGreenObjects').mockResolvedValue([
|
||||
{name: 'nginx-service-green', kind: 'Service'},
|
||||
{name: 'nginx-deployment-green', kind: 'Deployment'}
|
||||
])
|
||||
|
||||
const value = await rejectBlueGreenService(
|
||||
kubectl,
|
||||
testObjects,
|
||||
TEST_TIMEOUT_SHORT
|
||||
)
|
||||
|
||||
const deleteResult = value.deleteResult
|
||||
|
||||
expect(deleteResult).toHaveLength(2)
|
||||
expect(deleteResult).toContainEqual({
|
||||
name: 'nginx-service-green',
|
||||
kind: 'Service'
|
||||
})
|
||||
expect(deleteResult).toContainEqual({
|
||||
name: 'nginx-deployment-green',
|
||||
kind: 'Deployment'
|
||||
})
|
||||
})
|
||||
|
||||
test('reject blue/green service with timeout', async () => {
|
||||
// Mock routeBlueGreenService and deleteGreenObjects
|
||||
jest.spyOn(routeHelper, 'routeBlueGreenService').mockResolvedValue({
|
||||
deployResult: {
|
||||
execResult: {stdout: '', stderr: '', exitCode: 0},
|
||||
manifestFiles: []
|
||||
},
|
||||
objects: [
|
||||
{
|
||||
kind: 'Service',
|
||||
metadata: {
|
||||
name: 'nginx-service',
|
||||
labels: new Map<string, string>()
|
||||
},
|
||||
spec: {}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
jest
|
||||
.spyOn(bgHelper, 'deleteGreenObjects')
|
||||
.mockResolvedValue([
|
||||
{name: 'nginx-deployment-green', kind: 'Deployment'}
|
||||
])
|
||||
|
||||
const value = await rejectBlueGreenService(
|
||||
kubectl,
|
||||
testObjects,
|
||||
TEST_TIMEOUT_LONG
|
||||
)
|
||||
|
||||
const bgDeployment = value.routeResult
|
||||
const deleteResult = value.deleteResult
|
||||
|
||||
// Verify deleteGreenObjects is called with timeout
|
||||
expect(bgHelper.deleteGreenObjects).toHaveBeenCalledWith(
|
||||
kubectl,
|
||||
testObjects.deploymentEntityList,
|
||||
TEST_TIMEOUT_LONG
|
||||
)
|
||||
|
||||
// Assertions for routeResult and deleteResult
|
||||
expect(deleteResult).toHaveLength(1)
|
||||
expect(deleteResult[0].name).toBe('nginx-deployment-green')
|
||||
|
||||
expect(bgDeployment.objects).toHaveLength(1)
|
||||
expect(bgDeployment.objects[0].metadata.name).toBe('nginx-service')
|
||||
})
|
||||
|
||||
@@ -12,14 +12,16 @@ import {routeBlueGreenIngressUnchanged, routeBlueGreenService} from './route'
|
||||
|
||||
export async function rejectBlueGreenIngress(
|
||||
kubectl: Kubectl,
|
||||
manifestObjects: BlueGreenManifests
|
||||
manifestObjects: BlueGreenManifests,
|
||||
timeout?: string
|
||||
): Promise<BlueGreenRejectResult> {
|
||||
// get all kubernetes objects defined in manifest files
|
||||
// route ingress to stables services
|
||||
const routeResult = await routeBlueGreenIngressUnchanged(
|
||||
kubectl,
|
||||
manifestObjects.serviceNameMap,
|
||||
manifestObjects.ingressEntityList
|
||||
manifestObjects.ingressEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
// delete green services and deployments
|
||||
@@ -28,7 +30,8 @@ export async function rejectBlueGreenIngress(
|
||||
[].concat(
|
||||
manifestObjects.deploymentEntityList,
|
||||
manifestObjects.serviceEntityList
|
||||
)
|
||||
),
|
||||
timeout
|
||||
)
|
||||
|
||||
return {routeResult, deleteResult}
|
||||
@@ -36,19 +39,22 @@ export async function rejectBlueGreenIngress(
|
||||
|
||||
export async function rejectBlueGreenService(
|
||||
kubectl: Kubectl,
|
||||
manifestObjects: BlueGreenManifests
|
||||
manifestObjects: BlueGreenManifests,
|
||||
timeout?: string
|
||||
): Promise<BlueGreenRejectResult> {
|
||||
// route to stable objects
|
||||
const routeResult = await routeBlueGreenService(
|
||||
kubectl,
|
||||
NONE_LABEL_VALUE,
|
||||
manifestObjects.serviceEntityList
|
||||
manifestObjects.serviceEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
// delete new deployments with green suffix
|
||||
const deleteResult = await deleteGreenObjects(
|
||||
kubectl,
|
||||
manifestObjects.deploymentEntityList
|
||||
manifestObjects.deploymentEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
return {routeResult, deleteResult}
|
||||
@@ -56,25 +62,29 @@ export async function rejectBlueGreenService(
|
||||
|
||||
export async function rejectBlueGreenSMI(
|
||||
kubectl: Kubectl,
|
||||
manifestObjects: BlueGreenManifests
|
||||
manifestObjects: BlueGreenManifests,
|
||||
timeout?: string
|
||||
): Promise<BlueGreenRejectResult> {
|
||||
// route trafficsplit to stable deployments
|
||||
const routeResult = await routeBlueGreenSMI(
|
||||
kubectl,
|
||||
NONE_LABEL_VALUE,
|
||||
manifestObjects.serviceEntityList
|
||||
manifestObjects.serviceEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
// delete rejected new bluegreen deployments
|
||||
const deletedObjects = await deleteGreenObjects(
|
||||
kubectl,
|
||||
manifestObjects.deploymentEntityList
|
||||
manifestObjects.deploymentEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
// delete trafficsplit and extra services
|
||||
const cleanupResult = await cleanupSMI(
|
||||
kubectl,
|
||||
manifestObjects.serviceEntityList
|
||||
manifestObjects.serviceEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
return {routeResult, deleteResult: [].concat(deletedObjects, cleanupResult)}
|
||||
|
||||
@@ -16,7 +16,9 @@ import {
|
||||
import {
|
||||
routeBlueGreenIngress,
|
||||
routeBlueGreenService,
|
||||
routeBlueGreenForDeploy
|
||||
routeBlueGreenForDeploy,
|
||||
routeBlueGreenSMI,
|
||||
routeBlueGreenIngressUnchanged
|
||||
} from './route'
|
||||
|
||||
jest.mock('../../types/kubectl')
|
||||
@@ -117,3 +119,141 @@ describe('route function tests', () => {
|
||||
).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
// Timeout tests
|
||||
describe('route timeout tests', () => {
|
||||
let testObjects: BlueGreenManifests
|
||||
beforeEach(() => {
|
||||
//@ts-ignore
|
||||
Kubectl.mockClear()
|
||||
|
||||
testObjects = getManifestObjects(ingressFilepath)
|
||||
jest
|
||||
.spyOn(fileHelper, 'writeObjectsToFile')
|
||||
.mockImplementationOnce(() => [''])
|
||||
})
|
||||
|
||||
test('routeBlueGreenService with timeout', async () => {
|
||||
const timeout = '240s'
|
||||
|
||||
// Mock deployObjects to capture timeout parameter
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(require('./blueGreenHelper'), 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
const value = await routeBlueGreenService(
|
||||
kc,
|
||||
GREEN_LABEL_VALUE,
|
||||
testObjects.serviceEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
expect(deployObjectsSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
expect(value.objects).toHaveLength(1)
|
||||
|
||||
deployObjectsSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('routeBlueGreenSMI with timeout', async () => {
|
||||
const timeout = '300s'
|
||||
|
||||
jest
|
||||
.spyOn(TSutils, 'getTrafficSplitAPIVersion')
|
||||
.mockImplementation(() => Promise.resolve('v1alpha3'))
|
||||
|
||||
// Mock deployObjects and createTrafficSplitObject to capture timeout parameter
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(require('./blueGreenHelper'), 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
const createTrafficSplitSpy = jest
|
||||
.spyOn(require('./smiBlueGreenHelper'), 'createTrafficSplitObject')
|
||||
.mockResolvedValue({
|
||||
metadata: {name: 'nginx-service-trafficsplit'},
|
||||
spec: {backends: []}
|
||||
})
|
||||
|
||||
const value = await routeBlueGreenSMI(
|
||||
kc,
|
||||
GREEN_LABEL_VALUE,
|
||||
testObjects.serviceEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
expect(createTrafficSplitSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
'nginx-service',
|
||||
GREEN_LABEL_VALUE,
|
||||
timeout
|
||||
)
|
||||
expect(deployObjectsSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
|
||||
deployObjectsSpy.mockRestore()
|
||||
createTrafficSplitSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('routeBlueGreenIngressUnchanged with timeout', async () => {
|
||||
const timeout = '180s'
|
||||
|
||||
// Mock deployObjects to capture timeout parameter
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(require('./blueGreenHelper'), 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
const value = await routeBlueGreenIngressUnchanged(
|
||||
kc,
|
||||
testObjects.serviceNameMap,
|
||||
testObjects.ingressEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
expect(deployObjectsSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
|
||||
deployObjectsSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('route functions without timeout should pass undefined', async () => {
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(require('./blueGreenHelper'), 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
execResult: {exitCode: 0, stderr: '', stdout: ''},
|
||||
manifestFiles: []
|
||||
})
|
||||
|
||||
// Test routeBlueGreenService without timeout
|
||||
await routeBlueGreenService(
|
||||
kc,
|
||||
GREEN_LABEL_VALUE,
|
||||
testObjects.serviceEntityList
|
||||
)
|
||||
|
||||
expect(deployObjectsSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
expect.any(Array),
|
||||
undefined
|
||||
)
|
||||
|
||||
deployObjectsSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -25,7 +25,8 @@ import {getBufferTime} from '../../inputUtils'
|
||||
export async function routeBlueGreenForDeploy(
|
||||
kubectl: Kubectl,
|
||||
inputManifestFiles: string[],
|
||||
routeStrategy: RouteStrategy
|
||||
routeStrategy: RouteStrategy,
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
// sleep for buffer time
|
||||
const bufferTime: number = getBufferTime()
|
||||
@@ -47,19 +48,22 @@ export async function routeBlueGreenForDeploy(
|
||||
return await routeBlueGreenIngress(
|
||||
kubectl,
|
||||
manifestObjects.serviceNameMap,
|
||||
manifestObjects.ingressEntityList
|
||||
manifestObjects.ingressEntityList,
|
||||
timeout
|
||||
)
|
||||
} else if (routeStrategy == RouteStrategy.SMI) {
|
||||
return await routeBlueGreenSMI(
|
||||
kubectl,
|
||||
GREEN_LABEL_VALUE,
|
||||
manifestObjects.serviceEntityList
|
||||
manifestObjects.serviceEntityList,
|
||||
timeout
|
||||
)
|
||||
} else {
|
||||
return await routeBlueGreenService(
|
||||
kubectl,
|
||||
GREEN_LABEL_VALUE,
|
||||
manifestObjects.serviceEntityList
|
||||
manifestObjects.serviceEntityList,
|
||||
timeout
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -67,7 +71,8 @@ export async function routeBlueGreenForDeploy(
|
||||
export async function routeBlueGreenIngress(
|
||||
kubectl: Kubectl,
|
||||
serviceNameMap: Map<string, string>,
|
||||
ingressEntityList: any[]
|
||||
ingressEntityList: any[],
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
// const newObjectsList = []
|
||||
const newObjectsList: K8sObject[] = ingressEntityList.map((obj) => {
|
||||
@@ -84,7 +89,7 @@ export async function routeBlueGreenIngress(
|
||||
}
|
||||
})
|
||||
|
||||
const deployResult = await deployObjects(kubectl, newObjectsList)
|
||||
const deployResult = await deployObjects(kubectl, newObjectsList, timeout)
|
||||
|
||||
return {deployResult, objects: newObjectsList}
|
||||
}
|
||||
@@ -92,26 +97,28 @@ export async function routeBlueGreenIngress(
|
||||
export async function routeBlueGreenIngressUnchanged(
|
||||
kubectl: Kubectl,
|
||||
serviceNameMap: Map<string, string>,
|
||||
ingressEntityList: any[]
|
||||
ingressEntityList: any[],
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
const objects = ingressEntityList.filter((ingress) =>
|
||||
isIngressRouted(ingress, serviceNameMap)
|
||||
)
|
||||
|
||||
const deployResult = await deployObjects(kubectl, objects)
|
||||
const deployResult = await deployObjects(kubectl, objects, timeout)
|
||||
return {deployResult, objects}
|
||||
}
|
||||
|
||||
export async function routeBlueGreenService(
|
||||
kubectl: Kubectl,
|
||||
nextLabel: string,
|
||||
serviceEntityList: any[]
|
||||
serviceEntityList: any[],
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
const objects = serviceEntityList.map((serviceObject) =>
|
||||
getUpdatedBlueGreenService(serviceObject, nextLabel)
|
||||
)
|
||||
|
||||
const deployResult = await deployObjects(kubectl, objects)
|
||||
const deployResult = await deployObjects(kubectl, objects, timeout)
|
||||
|
||||
return {deployResult, objects}
|
||||
}
|
||||
@@ -119,7 +126,8 @@ export async function routeBlueGreenService(
|
||||
export async function routeBlueGreenSMI(
|
||||
kubectl: Kubectl,
|
||||
nextLabel: string,
|
||||
serviceEntityList: any[]
|
||||
serviceEntityList: any[],
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
// let tsObjects: TrafficSplitObject[] = []
|
||||
|
||||
@@ -128,14 +136,15 @@ export async function routeBlueGreenSMI(
|
||||
const tsObject: TrafficSplitObject = await createTrafficSplitObject(
|
||||
kubectl,
|
||||
serviceObject.metadata.name,
|
||||
nextLabel
|
||||
nextLabel,
|
||||
timeout
|
||||
)
|
||||
|
||||
return tsObject
|
||||
})
|
||||
)
|
||||
|
||||
const deployResult = await deployObjects(kubectl, tsObjects)
|
||||
const deployResult = await deployObjects(kubectl, tsObjects, timeout)
|
||||
|
||||
return {deployResult, objects: tsObjects}
|
||||
}
|
||||
|
||||
@@ -197,4 +197,191 @@ describe('SMI Helper tests', () => {
|
||||
expect(deleteObjects[0].name).toBe('nginx-service-green')
|
||||
expect(deleteObjects[0].kind).toBe('Service')
|
||||
})
|
||||
|
||||
// Timeout-specific tests
|
||||
test('setupSMI with timeout test', async () => {
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
result: 'success'
|
||||
} as any)
|
||||
|
||||
const timeout = '300s'
|
||||
const smiResults = await setupSMI(
|
||||
kc,
|
||||
testObjects.serviceEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
// Verify deployObjects was called with timeout
|
||||
expect(deployObjectsSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
|
||||
expect(smiResults.objects).toBeDefined()
|
||||
expect(smiResults.deployResult).toBeDefined()
|
||||
|
||||
deployObjectsSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('createTrafficSplitObject with timeout test', async () => {
|
||||
const deleteObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deleteObjects')
|
||||
.mockResolvedValue()
|
||||
|
||||
const timeout = '180s'
|
||||
const tsObject = await createTrafficSplitObject(
|
||||
kc,
|
||||
testObjects.serviceEntityList[0].metadata.name,
|
||||
NONE_LABEL_VALUE,
|
||||
timeout
|
||||
)
|
||||
|
||||
// Verify deleteObjects was called with timeout
|
||||
expect(deleteObjectsSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'nginx-service-trafficsplit',
|
||||
kind: TRAFFIC_SPLIT_OBJECT
|
||||
})
|
||||
]),
|
||||
timeout
|
||||
)
|
||||
|
||||
expect(tsObject.metadata.name).toBe('nginx-service-trafficsplit')
|
||||
expect(tsObject.spec.backends).toHaveLength(2)
|
||||
|
||||
deleteObjectsSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('createTrafficSplitObject with GREEN_LABEL_VALUE and timeout test', async () => {
|
||||
const deleteObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deleteObjects')
|
||||
.mockResolvedValue()
|
||||
|
||||
const timeout = '240s'
|
||||
const tsObject = await createTrafficSplitObject(
|
||||
kc,
|
||||
testObjects.serviceEntityList[0].metadata.name,
|
||||
GREEN_LABEL_VALUE,
|
||||
timeout
|
||||
)
|
||||
|
||||
// Verify deleteObjects was called with timeout
|
||||
expect(deleteObjectsSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
expect.any(Array),
|
||||
timeout
|
||||
)
|
||||
|
||||
// Verify weights are correct for green deployment
|
||||
for (const be of tsObject.spec.backends) {
|
||||
if (be.service === 'nginx-service-stable') {
|
||||
expect(be.weight).toBe(MIN_VAL)
|
||||
}
|
||||
if (be.service === 'nginx-service-green') {
|
||||
expect(be.weight).toBe(MAX_VAL)
|
||||
}
|
||||
}
|
||||
|
||||
deleteObjectsSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('cleanupSMI with timeout test', async () => {
|
||||
const deleteObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deleteObjects')
|
||||
.mockResolvedValue()
|
||||
|
||||
const timeout = '120s'
|
||||
const deleteObjects = await cleanupSMI(
|
||||
kc,
|
||||
testObjects.serviceEntityList,
|
||||
timeout
|
||||
)
|
||||
|
||||
// Verify deleteObjects was called with timeout
|
||||
expect(deleteObjectsSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'nginx-service-green',
|
||||
kind: 'Service'
|
||||
})
|
||||
]),
|
||||
timeout
|
||||
)
|
||||
|
||||
expect(deleteObjects).toHaveLength(1)
|
||||
expect(deleteObjects[0].name).toBe('nginx-service-green')
|
||||
expect(deleteObjects[0].kind).toBe('Service')
|
||||
|
||||
deleteObjectsSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('setupSMI without timeout test', async () => {
|
||||
const deployObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deployObjects')
|
||||
.mockResolvedValue({
|
||||
result: 'success'
|
||||
} as any)
|
||||
|
||||
const smiResults = await setupSMI(kc, testObjects.serviceEntityList)
|
||||
|
||||
// Verify deployObjects was called without timeout (undefined)
|
||||
expect(deployObjectsSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
expect.any(Array),
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(smiResults.objects).toBeDefined()
|
||||
expect(smiResults.deployResult).toBeDefined()
|
||||
|
||||
deployObjectsSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('createTrafficSplitObject without timeout test', async () => {
|
||||
const deleteObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deleteObjects')
|
||||
.mockResolvedValue()
|
||||
|
||||
const tsObject = await createTrafficSplitObject(
|
||||
kc,
|
||||
testObjects.serviceEntityList[0].metadata.name,
|
||||
NONE_LABEL_VALUE
|
||||
)
|
||||
|
||||
// Verify deleteObjects was called without timeout (undefined)
|
||||
expect(deleteObjectsSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
expect.any(Array),
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(tsObject.metadata.name).toBe('nginx-service-trafficsplit')
|
||||
|
||||
deleteObjectsSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('cleanupSMI without timeout test', async () => {
|
||||
const deleteObjectsSpy = jest
|
||||
.spyOn(bgHelper, 'deleteObjects')
|
||||
.mockResolvedValue()
|
||||
|
||||
const deleteObjects = await cleanupSMI(kc, testObjects.serviceEntityList)
|
||||
|
||||
// Verify deleteObjects was called without timeout (undefined)
|
||||
expect(deleteObjectsSpy).toHaveBeenCalledWith(
|
||||
kc,
|
||||
expect.any(Array),
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(deleteObjects).toHaveLength(1)
|
||||
|
||||
deleteObjectsSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,7 +28,8 @@ export const MAX_VAL = 100
|
||||
|
||||
export async function setupSMI(
|
||||
kubectl: Kubectl,
|
||||
serviceEntityList: any[]
|
||||
serviceEntityList: any[],
|
||||
timeout?: string
|
||||
): Promise<BlueGreenDeployment> {
|
||||
const newObjectsList = []
|
||||
const trafficObjectList = []
|
||||
@@ -49,7 +50,8 @@ export async function setupSMI(
|
||||
const tsObject = await createTrafficSplitObject(
|
||||
kubectl,
|
||||
svc.metadata.name,
|
||||
NONE_LABEL_VALUE
|
||||
NONE_LABEL_VALUE,
|
||||
timeout
|
||||
)
|
||||
tsObjects.push(tsObject as TrafficSplitObject)
|
||||
}
|
||||
@@ -59,7 +61,8 @@ export async function setupSMI(
|
||||
// create services
|
||||
const smiDeploymentResult: DeployResult = await deployObjects(
|
||||
kubectl,
|
||||
objectsToDeploy
|
||||
objectsToDeploy,
|
||||
timeout
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -73,7 +76,8 @@ let trafficSplitAPIVersion = ''
|
||||
export async function createTrafficSplitObject(
|
||||
kubectl: Kubectl,
|
||||
name: string,
|
||||
nextLabel: string
|
||||
nextLabel: string,
|
||||
timeout?: string
|
||||
): Promise<TrafficSplitObject> {
|
||||
// cache traffic split api version
|
||||
if (!trafficSplitAPIVersion)
|
||||
@@ -112,6 +116,13 @@ export async function createTrafficSplitObject(
|
||||
}
|
||||
}
|
||||
|
||||
const deleteList: K8sDeleteObject[] = [
|
||||
{
|
||||
name: trafficSplitObject.metadata.name,
|
||||
kind: trafficSplitObject.kind
|
||||
}
|
||||
]
|
||||
await deleteObjects(kubectl, deleteList, timeout)
|
||||
return trafficSplitObject
|
||||
}
|
||||
|
||||
@@ -173,7 +184,8 @@ export async function validateTrafficSplitsState(
|
||||
|
||||
export async function cleanupSMI(
|
||||
kubectl: Kubectl,
|
||||
serviceEntityList: any[]
|
||||
serviceEntityList: any[],
|
||||
timeout?: string
|
||||
): Promise<K8sDeleteObject[]> {
|
||||
const deleteList: K8sDeleteObject[] = []
|
||||
|
||||
@@ -189,7 +201,7 @@ export async function cleanupSMI(
|
||||
})
|
||||
|
||||
// delete all objects
|
||||
await deleteObjects(kubectl, deleteList)
|
||||
await deleteObjects(kubectl, deleteList, timeout)
|
||||
|
||||
return deleteList
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user