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

Probes 20240819 #1364

Merged
merged 5 commits into from
Aug 19, 2024
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
4 changes: 3 additions & 1 deletion app/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ router.get('/v1/health', (req, res)=>{
});
});

router.use('/kube', Kube);
router.use(createExpressLogger('razeedash-api/api'));

// Respond to /api/kube/[liveness|readiness|startup]
router.use('/kube', Kube);

router.use(asyncHandler(async (req, res, next) => {
const db = req.app.get('db');
req.db = db;
Expand Down
15 changes: 9 additions & 6 deletions app/routes/kube/kube.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,37 @@ const probeUtil = require('../../utils/probes');

const router = express.Router();

router.get('/startup', asyncHandler(async (req, res) => {
const startupHandler = asyncHandler(async (req, res) => {
try {
const payload = await probeUtil.getStartupPayload(req);
return res.status(200).send(payload);
}
catch (e) {
return res.status(503).send('service unavailable');
}
}));
});
router.get('/startup', startupHandler);

router.get('/readiness', asyncHandler(async (req, res) => {
const readinessHandler = asyncHandler(async (req, res) => {
try {
const payload = await probeUtil.getReadinessPayload(req);
return res.status(200).send(payload);
}
catch (e) {
return res.status(503).send('service unavailable');
}
}));
});
router.get('/readiness', readinessHandler);

router.get('/liveness', asyncHandler(async(req, res) => {
const livenessHandler = asyncHandler(async(req, res) => {
try {
const payload = await probeUtil.getLivenessPayload(req);
return res.status(200).send(payload);
}
catch (e) {
return res.status(503).send('service unavailable');
}
}));
});
router.get('/liveness', livenessHandler);

module.exports = router;
103 changes: 103 additions & 0 deletions app/routes/kube/kube.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* eslint-env node, mocha */
/**
* Copyright 2024 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const assert = require('assert');
const mongodb = require('mongo-mock');
var httpMocks = require('node-mocks-http');
const log = require('../../log').log;

const probeUtil = require('../../utils/probes');
const defaultProbe = require('../../utils/probes/probe-default.js');

const rewire = require('rewire');
let kube = rewire('./kube');
let db = {};

describe('probes', () => {

before(async function () {
mongodb.max_delay = 0;
const MongoClient = mongodb.MongoClient;
db = await MongoClient.connect('someconnectstring', {});
db.collection('orgs');
});

after(function () {
db.close();
});

describe('startupProbe', () => {
it('should pass the default startup probe after setStartupComplete is called', async () => {
const startupHandler = kube.__get__('startupHandler');

const request = httpMocks.createRequest({
method: 'GET',
url: '/startup',
params: {},
log: log
});
const response = httpMocks.createResponse();

// Default impl returns failure before 'setStartupComplete' is called
await startupHandler(request, response);
assert.equal(response.statusCode, 503);

defaultProbe.setStartupComplete(true);

// Default impl returns success after 'setStartupComplete' is called
await startupHandler(request, response);
assert.equal(response.statusCode, 200);
});

it('should fail if the custom startup probe fails', async () => {
const startupHandler = kube.__get__('startupHandler');

const request = httpMocks.createRequest({
method: 'GET',
url: '/startup',
params: {},
log: log
});
const response = httpMocks.createResponse();

// Note: default probe setStartupComplete has already been called by earlier test

probeUtil.setImpl('./probe-testFailure.js');
await startupHandler(request, response);

assert.equal(response.statusCode, 503);
});

it('should succeed if the custom startup probe succeeds', async () => {
const startupHandler = kube.__get__('startupHandler');

const request = httpMocks.createRequest({
method: 'GET',
url: '/startup',
params: {},
log: log
});
const response = httpMocks.createResponse();

// Note: default probe setStartupComplete has already been called by earlier test

probeUtil.setImpl('./probe-testSuccess.js');
await startupHandler(request, response);

assert.equal(response.statusCode, 200);
});
});
});
29 changes: 16 additions & 13 deletions app/utils/probes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

const PROBE_DEFAULT_IMPL = require( './probe-default.js' );
const PROBE_CUSTOM_IMPL = require( process.env.PROBE_IMPL || './probe-none.js' );
let PROBE_CUSTOM_IMPL = require( process.env.PROBE_IMPL || './probe-none.js' );

/*
Return an impl for each of the probe types:
Expand All @@ -26,29 +26,32 @@ Return an impl for each of the probe types:
Return the custom payload, or the default payload if there is none.
*/
const PROBE_IMPL = {
getStartupPayload: async function( context ) {
getStartupPayload: async function( req ) {
const method = 'getStartupPayload';
const defaultPayload = await PROBE_DEFAULT_IMPL[method](context);
if( !Object.prototype.hasOwnProperty.call(PROBE_CUSTOM_IMPL, method) ) {
return( PROBE_DEFAULT_IMPL[method](context) );
const defaultPayload = await PROBE_DEFAULT_IMPL[method](req);
if( Object.prototype.hasOwnProperty.call(PROBE_CUSTOM_IMPL, method) ) {
return( await PROBE_CUSTOM_IMPL[method](req) );
}
return defaultPayload;
},
getReadinessPayload: async function( context ) {
getReadinessPayload: async function( req ) {
const method = 'getReadinessPayload';
const defaultPayload = await PROBE_DEFAULT_IMPL[method](context);
if( !Object.prototype.hasOwnProperty.call(PROBE_CUSTOM_IMPL, method) ) {
return( PROBE_DEFAULT_IMPL[method](context) );
const defaultPayload = await PROBE_DEFAULT_IMPL[method](req);
if( Object.prototype.hasOwnProperty.call(PROBE_CUSTOM_IMPL, method) ) {
return( await PROBE_CUSTOM_IMPL[method](req) );
}
return defaultPayload;
},
getLivenessPayload: async function( context ) {
getLivenessPayload: async function( req ) {
const method = 'getLivenessPayload';
const defaultPayload = await PROBE_DEFAULT_IMPL[method](context);
if( !Object.prototype.hasOwnProperty.call(PROBE_CUSTOM_IMPL, method) ) {
return( PROBE_DEFAULT_IMPL[method](context) );
const defaultPayload = await PROBE_DEFAULT_IMPL[method](req);
if( Object.prototype.hasOwnProperty.call(PROBE_CUSTOM_IMPL, method) ) {
return( await PROBE_CUSTOM_IMPL[method](req) );
}
return defaultPayload;
},
setImpl: function( newImpl ) {
PROBE_CUSTOM_IMPL = require( newImpl || './probe-none.js' );
}
};

Expand Down
35 changes: 35 additions & 0 deletions app/utils/probes/probe-testFailure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright 2024 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


/*
This sample shows how the startup/liveness/readiness probes can be customized to always fail.
It is used by automated unit testing.
*/

async function getStartupPayload() {
throw new Error('probe failure for testing');
}

async function getReadinessPayload() {
throw new Error('probe failure for testing');
}

async function getLivenessPayload() {
throw new Error('probe failure for testing');
}

module.exports = { getLivenessPayload, getReadinessPayload, getStartupPayload };
35 changes: 35 additions & 0 deletions app/utils/probes/probe-testSuccess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright 2024 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


/*
This sample shows how the startup/liveness/readiness probes can be customized to always succeed.
It is used by automated unit testing.
*/

async function getStartupPayload() {
return('probe success for testing');
}

async function getReadinessPayload() {
return('probe success for testing');
}

async function getLivenessPayload() {
return('probe success for testing');
}

module.exports = { getLivenessPayload, getReadinessPayload, getStartupPayload };