Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
jhsware committed Dec 5, 2016
2 parents 6824be3 + cb1fa96 commit e2206d9
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 1 deletion.
56 changes: 56 additions & 0 deletions .gitignore
@@ -0,0 +1,56 @@
.DS_Store

# Sass compilation related files
public/css/*.css


# IDEA specific
.idea
*.iml

# VS Code specific
.vscode
jsconfig.json
typings/*
typings.json

# Eclipse specific
.jshintrc
.project
.settings/

# Settings
config/localSettings.js

# Logs
logs/*
*.log

# Dependency directory for NPM
node_modules

# Frontend files build on startup
public/js/ckeditor
public/js/dragula

# Startup scripts (sets logging)
dev.sh
dev.bat
debug.sh

# Key value store database
server/init/store.db

# KTH Style imported files
public/css/bootstrap
public/css/font-awesome
public/css/fonts
public/css/kth-style
public/img/kth-style

# Built at startup
bundles
public/js/app/config*.js

# Docker

3 changes: 3 additions & 0 deletions .travis.yml
@@ -0,0 +1,3 @@
language: node_js
node_js:
- "6.1"
21 changes: 21 additions & 0 deletions LICENSE
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2016 jhsware

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
61 changes: 60 additions & 1 deletion README.md
@@ -1,2 +1,61 @@
# kth-node-monitor
Monitor module for Node.js projects.
[![Build Status](https://travis-ci.org/jhsware/kth-node-monitor.svg?branch=master)](https://travis-ci.org/jhsware/kth-node-monitor)

Helper utilities for KTH/node-projects (temp location)


- no answer from _monitor
- slow response times
- circular dependencies
- service down

Circular dependecies
- on start up and REQUIRED dep is down
we add subsystems from ../init/api and a dependecy won't be checked
until the apiClient has been correctly initiated so we will be staged
by Netscaler

- running and REQUIRED dep goes down
we will report fail and be unstaged by Netscaler, when dep is started and
staged again we will report OK and we will be staged by Netscaler again

- running and REQUIRED dep goes up again

- if circular deps and roundtrip takes more than 1s
there will be an infinite call loop and the original caller will time out
and if REQUIRED will cause unstaging by Netscaler. Then all deps will be unstaged.
Services will need to be restarted in order to return OK and be staged by Netscaler


### Development Notes ###

If we have issues with recursive rependencies that resolve slowly we will need to implement one or both of the following:

LIFECYCLE JSON CALLS

PENDING -- starting shows pending for 30secs regardless of state of REQUIRED dependecies
to allow consuming services to start up, OR if REQUIRED dependencies have status pending
PENDING resolves to OK as text to please Netscaler
OK -- all REQUIRED dependencies are OK
ERROR -- at least one (1) REQUIRED dep is down

pass formData on requests

{
resolved: ['uri/to/service']
}

To handle recursive references we need:

Starting service:
- if required are OK OK | OK
- if required are PENDING OK | PENDING
- if required are ERROR or down OK | PENDING
After 30s:
- if required are OK OK | OK
- if required are PENDING OK | PENDING
- if required are ERROR ERROR | ERROR
Required goes down ERROR | ERROR
Required goes up again
- if required PENDING OK | PENDING
- if required OK OK | OK
2 changes: 2 additions & 0 deletions lib/index.js
@@ -0,0 +1,2 @@
module.exports.interfaces = require('./interfaces')
require('./utilities')
4 changes: 4 additions & 0 deletions lib/interfaces.js
@@ -0,0 +1,4 @@
'use strict'
const { createInterface } = require('component-registry')

module.exports.IHealthCheck = createInterface({ name: 'IHealthCheck' })
154 changes: 154 additions & 0 deletions lib/utilities.js
@@ -0,0 +1,154 @@
'use strict'

/**
* System controller support functions for /monitor
*/
const { safeGet } = require('safe-utils')
const registry = require('component-registry').globalRegistry
const { createUtility } = require('component-registry')
const Promise = require('bluebird')

const IHealthCheck = require('./interfaces').IHealthCheck

function _createApiStatusObj (key, statusCode, required, responseTime) {
var message
if (statusCode === 200) {
message = `API_STATUS: ${key} is OK` + (responseTime ? ` (${responseTime}ms)` : '') + (required ? ' (required)' : '')
} else {
message = `API_STATUS: ${key} responded with status ${statusCode}` + (responseTime ? ` (${responseTime}ms)` : '')
message += (required ? ' (WARNING! This API is required to be ok for system to report ok)' : ' (This API is NOT required to be ok for system to report ok)')
}

return {
key: key,
statusCode: statusCode,
required: required,
message: message,
responseTime: responseTime
}
}

const apiCallCache = {}
function _setCache (key, statusObj) {
apiCallCache[key] = { statusObj: statusObj, timestamp: Date.now() }
}
function _getCache (key) {
return apiCallCache[key]
}
createUtility({
implements: IHealthCheck,
name: 'kth-node-api',

status: function (endpoint, options) {
// Check that we haven't called this endpoint during the last 1000ms to avoid flooding monitor pages
// or feedback loops
const endpointBaseUri = safeGet(() => endpoint.config.proxyBasePath)

if (endpointBaseUri === undefined) {
// We couldn't resolve the endpoint
return Promise.resolve(_createApiStatusObj(endpoint.key, 400, options && options.required))
} else if (safeGet(() => (Date.now() - _getCache(endpointBaseUri).timestamp) < 1000)) {
// We got a hit in the cache
const statusObj = safeGet(() => _getCache(endpointBaseUri).statusObj)

// Adding cacheTimestamp props to returned object if result was taken from cache
const outp = Object.assign({}, statusObj, { cacheTimestamp: _getCache(endpointBaseUri).timestamp})
outp.message = outp.message + ' --- cached (' + (Date.now() - _getCache(endpointBaseUri).timestamp) + 'ms)'
return Promise.resolve(outp)
} else {
// We need to perform a request to api _monitor page
// TODO: Set header accepts: application/json
const t0 = Date.now()
return endpoint.client.getAsync({
uri: endpointBaseUri + '/_monitor'
}).then((data) => {
const statusObj = _createApiStatusObj(endpoint.key, data.statusCode, options && options.required, Date.now() - t0)
_setCache(endpointBaseUri, statusObj)
return Promise.resolve(statusObj)
})
.catch(() => {
const statusObj = _createApiStatusObj(endpoint.key, 503, options && options.required)
_setCache(endpointBaseUri, statusObj)
return Promise.resolve(statusObj)
})
}
}
}).registerWith(registry)

createUtility({
implements: IHealthCheck,
name: 'kth-node-ldap',

status: function (ldap, options) {
if (ldap.isOk()) {
return Promise.resolve(_createApiStatusObj('ldap', 200, options && options.required))
} else {
return Promise.resolve(_createApiStatusObj('ldap', 503, options && options.required))
}
}
}).registerWith(registry)

createUtility({
implements: IHealthCheck,
name: 'kth-node-mongodb',

status: function (db, options) {
if (db.isOk()) {
return Promise.resolve(_createApiStatusObj('mongodb', 200, options && options.required))
} else {
return Promise.resolve(_createApiStatusObj('mongodb', 503, options && options.required))
}
}
}).registerWith(registry)

createUtility({
implements: IHealthCheck,
name: 'kth-node-system-check',

status: function (localSystems, subSystems) {
// Handle if we don't have subsystems
subSystems = subSystems || [Promise.resolve(undefined)]

// Consolidate all results
return Promise.all(subSystems)
.then((results) => {
const outp = {}
results.forEach((status) => {
if (typeof status === 'object') {
outp[status.key] = status
}
})
return Promise.resolve(outp)
})
.then((subSystems) => {
return localSystems.then((result) => Promise.resolve({localSystems: result, subSystems: subSystems}))
})
.then((result) => {
const subSystems = result.subSystems
const localSystems = result.localSystems

var systemOk = Object.keys(subSystems).reduce((systemOk, apiKey) => {
return systemOk && (subSystems[apiKey].required ? subSystems[apiKey].statusCode === 200 : true)
}, localSystems.statusCode === 200)

return Promise.resolve({
statusCode: (systemOk ? 200 : 503),
message: (systemOk ? 'OK' : 'ERROR'),
subSystems: subSystems
})
})
},

renderJSON: function (systemHealth) {
return systemHealth
},

renderText: function (systemHealth) {
var outp = `APPLICATION_STATUS: ${systemHealth.message}` + '\n'
outp += Object.keys(systemHealth.subSystems).map((apiKey) => {
return systemHealth.subSystems[apiKey].message
}).join('\n')
return outp
}

}).registerWith(registry)
36 changes: 36 additions & 0 deletions package.json
@@ -0,0 +1,36 @@
{
"//": "JSHint configuration - http://jshint.com/docs/options/",
"name": "kth-node-monitor",
"version": "0.1.2",
"description": "Helper utilities for KTH/node-projects",
"main": "lib/index.js",
"author": {
"name": "Sebastian Ware",
"email": "sebastian@urbantalk.se",
"url": "https://github.com/jhsware"
},
"license": "MIT",
"repository": {
"type": "git",
"uri": "https://github.com/jhsware/kth-node-monitor"
},
"scripts": {
"test": "NODE_ENV=development node_modules/mocha/bin/mocha ./test/test-*.js ./test/**/test-*.js",
"debug-test": "NODE_ENV=development node_modules/mocha/bin/mocha --debug-brk --no-timeouts ./test/test-*.js ./test/**/test-*.js"
},
"peerDependencies": {
"component-registry": "^0.2.0",
"safe-utils": "^0.1.0",
"bluebird": "^3.4.6"
},
"devDependencies": {
"chai": "^3.5.0",
"mocha": "^3.1.2",
"component-registry": "^0.2.0",
"safe-utils": "^0.1.0",
"bluebird": "^3.4.6"
},
"jshintConfig": {
"maxerr": 5
}
}
44 changes: 44 additions & 0 deletions test/test-utilities.js
@@ -0,0 +1,44 @@
/* eslint-env mocha */
'use strict'

// Testing libraries
const expect = require('chai').expect

// My code
const Promise = require('bluebird')
const registry = require('component-registry').globalRegistry
require('../lib')
const { IHealthCheck } = require('../lib').interfaces

describe('Utilities', function () {
it('can be found', function () {
['kth-node-api', 'kth-node-ldap', 'kth-node-mongodb', 'kth-node-system-check'].forEach((name) => {
const util = registry.getUtility(IHealthCheck, name)
expect(util).not.to.equal(undefined)
})
})

it('kth-node-system-check writes OK on first line when ok', function (done) {
const systemHealthUtil = registry.getUtility(IHealthCheck, 'kth-node-system-check')
const localSystems = Promise.resolve({ statusCode: 200, message: 'OK' })

systemHealthUtil.status(localSystems)
.then((status) => {
const outp = systemHealthUtil.renderText(status)
expect(outp.split('\n')[0].indexOf('APPLICATION_STATUS: OK')).not.to.equal(-1)
done()
})
})

it('kth-node-system-check writes ERRROR on first line when not ok', function (done) {
const systemHealthUtil = registry.getUtility(IHealthCheck, 'kth-node-system-check')
const localSystems = Promise.resolve({ statusCode: 503, message: 'ERROR' })

systemHealthUtil.status(localSystems)
.then((status) => {
const outp = systemHealthUtil.renderText(status)
expect(outp.split('\n')[0].indexOf('APPLICATION_STATUS: ERROR')).not.to.equal(-1)
done()
})
})
})

0 comments on commit e2206d9

Please sign in to comment.