'use strict';
const assert = require('assert');
/**
* @global
* @typedef {Object | Array | string | number | null | boolean} jsonType
*
*/
/* Specs are created from a JSON description.*/
/**
* @global
* @typedef {Object} resourcesType
* @property {Array.<number>=} cpus The compute resource available in virtual
* cores. The array has three entries: first, trusted app, second, untrusted
* within the incubation period, and third, untrusted and not in incubation.
* @property {Array.<number>=} memory The memory resource available in
* megabytes. See `cpus` for the array description.
* @property {Array.<number>=} storage The ephemeral storage in megabytes. See
* `cpus` for the array description.
* @property {Array.<number>=} egress The maximum egress bandwith in megabytes
* per second. See `cpus` for the array description.
*/
/**
* @global
* @typedef {Object} poolType
* @property {Array.<string>} poolKey The key to select a node pool. See
* `cpus` for the array description.
* @property {Array.<string>} poolValue The value to select a node pool. See
* `cpus` for the array description.
* @property {Array.<boolean>} isGvisor Whether the node pool enables gvisor.
* See `cpus` for the array description.
*/
/**
* @global
* @typedef {Object} redisSpecType
* @property {string} templateFile The mustache template to patch.
* @property {string} k8SNamespace The namespace for the service.
* @property {string} image The docker image for Redis.
* @property {poolType} nodePool The node pool.
* @property {resourcesType} request The resources requested.
* @property {resourcesType} limit A hard limit on the resources consumed.
* Limits are only active if `isUntrusted` is true.
* @property {number} updateRatio Update resources every `updateRatio`
* incremental number of app processes.
* @property {resourcesType} deltaRequest Incremental resources requested per
* extra `updateRatio` processes.
* @property {resourcesType} deltaLimit Incremental hard limit on the resources
* consumed per extra `updateRatio` processes.
* @property {Array.<number>} dedicatedVolumeSize Size in gigabytes of the
*dedicated volume. See `cpus` for the array description.
* @property {Array.<number>} deltaDedicatedVolumeSize Incremental size
* increase in gigabytes of the dedicated volume per extra `updateRatio` app
* processes.See `cpus` for the array description.
* @property {number} maxNFSInstances The maximum number of app instances
* before we create a dedicated volume.
*
*/
/**
* @global
* @typedef {Object} appSpecType
* @property {string} templateFile The mustache template to patch.
* @property {string} k8SNamespace The namespace for the service.
* @property {poolType} nodePool The node pool.
* @property {resourcesType} request The resources requested.
* @property {resourcesType} limit A hard limit on the resources consumed.
* @property {number} maxInstances The maximum number of app processes.
* @property {Array.<jsonType>} args The arguments to the node.js process.
*
*/
/**
* @global
* @typedef {Object} deploymentSpecType
* @property {boolean} useK8SConfig Whether to use the k8s config file from the
* kubelet client.
* @property {number} refreshInterval Time between Kubernetes status polling in
* msec.
* @property {string} appSuffix The suffix for the app name, e.g., `cafjs.com`.
* @property {boolean} isUntrusted Whether the deployment should be trusted.
* @property {boolean} isIncubator Whether the deployment is in incubation mode.
* @property {boolean} isDeployer Whether it is the `Deployer` app.
* @property {boolean} isPeople Whether it is the `People` app.
* @property {boolean} isAccounts Whether it is the `Accounts` app.
* @property {Object<string, number>} plans The threshold in CAs for an extra
* application process.
* @property {number} ratioIncubator The portion of a full process assigned to
* incubator mode.
* @property {redisSpecType} redis The spec for the redis backend.
* @property {appSpecType} app The spec for the app processes.
*
*/
/* Props customize the mustache templates.*/
/**
* @global
* @typedef {Object} redisPropsType
* @property {string} id
* @property {string} k8SNamespace
* @property {string} touch Modify to trigger reset.
* @property {string} timestamp Over time multiple instances of a service have
* the same `id`, and by adding `timestamp` we can identify their volumes.
* @property {string} image
* @property {number} cpus In millicores.
* @property {number} memory In megabytes.
* @property {number} memoryLimit In megabytes.
* @property {number} cpusLimit In millicores.
* @property {string} poolKey
* @property {string} poolValue
* @property {number} dedicatedVolumeSize The disk size in gigabytes.
* @property {boolean} isDedicatedVolume True if it has exclusive access to a
* persistent volume.
* @property {boolean} isUntrusted
*
*/
/**
* @global
* @typedef {Object} appPropsType
*
* @property {string} id // duplicate
* @property {string} k8SNamespace The app namespace.
* @property {number} instances The number of processes.
* @property {string} touch Modify to trigger reset.
* @property {string} appPublisher
* @property {string} appLocalName
* @property {string} appSuffix
* @property {boolean} isDeployer
* @property {boolean} isAccounts
* @property {boolean} isPeople
* @property {boolean} isUntrusted // duplicate
* @property {boolean} isIncubator
* @property {boolean} isGvisor Whether to use a sandbox.
* @property {string} redisNamespace The redis namespace.
* @property {string} image
* @property {string} args The JSON serialized array with arguments to node.
* @property {number} cpus In millicores.
* @property {number} memory In megabytes.
* @property {number} memoryLimit In megabytes.
* @property {number} cpusLimit In millicores.
* @property {number} storage In megabytes.
* @property {number} storageLimit In megabytes.
* @property {number} egressLimit In megabytes/sec
* @property {string} poolKey
* @property {string} poolValue
* @property {boolean} isCDN Whether to change current CDN settings.
* @property {string=} appCDN The base url for a CDN service.
* @property {string=} appSubdirCDN A CDN subdir for cache invalidation.
* @property {string=} props JSON serialized metadata of current deployment
* (type before serialization is `deploymentPropsType`).
* @property {Array.<envPropertiesType>=} envProps A list of properties to set.
*
*/
/**
* @global
* @typedef {Object} deploymentPropsType
* @property {string} version The schema version for this metadata.
* @property {appPropsType=} app Props for the nodejs app.
* @property {redisPropsType=} redis Props for the redis backend.
* @property {number} numberOfCAs The number of active CAs.
* @property {string} plan The strategy to flex resources.
*/
/* Patched props are a subset of props that can be modified. */
/**
* @global
* @typedef {Object} appPatchedPropsType
* @property {number} cpus In millicores.
* @property {number} memory In megabytes.
* @property {number} memoryLimit In megabytes.
* @property {number} cpusLimit In millicores.
* @property {number} storage In megabytes.
* @property {number} storageLimit In megabytes.
* @property {number} egressLimit In megabytes/sec
* @property {string} image Docker image.
* @property {string} poolKey
* @property {string} poolValue
* @property {boolean} isGvisor Whether to use a sandbox.
* @property {boolean} isIncubator
* @property {number} instances The number of processes.
* @property {string=} props JSON serialized metadata of current deployment
*/
/**
* @global
* @typedef {Object} redisPatchedPropsType
* @property {number} cpus In millicores.
* @property {number} memory In megabytes.
* @property {number} memoryLimit In megabytes.
* @property {number} cpusLimit In millicores.
* @property {string} poolKey
* @property {string} poolValue
* @property {boolean} isGvisor Whether to use a sandbox.
* @property {number} dedicatedVolumeSize The disk size in gigabytes.
*/
/**
* @global
* @typedef {Object} patchedPropsType
* @property {string} plan The strategy to flex resources.
* @property {number} numberOfCAs The number of active CAs.
* @property {appPatchedPropsType=} app Props for the nodejs app.
* @property {redisPatchedPropsType=} redis Props for the redis backend.
*/
/* Options are derived from user inputs */
/**
* @global
* @typedef {Object} updateOptionsType
* @property {string} id
* @property {string} plan The strategy to flex resources.
* @property {number} numberOfCAs The number of active CAs.
* @property {deploymentPropsType} currentProps The deployment
* properties currently used.
*/
/**
* @global
* @typedef {Object} cdnType
* @property {string} appCDN The URL from the CDN provider.
* @property {string} appSubdirCDN A subdirectory adding versioning to
* help cache invalidation.
*/
/**
* @global
* @typedef {Object} envPropertiesType
* @property {string} key The key of the environment property.
* @property {string} value The value of the environment property.
*/
/**
* @global
* @typedef {Object} createOptionsType
* @property {string} id
* @property {string} plan The strategy to flex resources.
* @property {string} image The docker image for the app.
* @property {string=} timestamp An optional tag to identify a previous redis
* service instance. This allows redis to restart from a previous state.
* @property {boolean} isUntrusted
* @property {cdnType=} cdn An optional CDN
* configuration that overrides the one in the image.
* @property {Array.<envPropertiesType>=} envProps A list of properties to set.
*/
/**
* @global
* @typedef {Object} deleteOptionsType
* @property {string} id
* @property {boolean} keepData Do not delete the volume.
* @property {string} timestamp Over time multiple instances of a service have
* the same `id`, and by adding `timestamp` we can identify their volumes.
*/
/**
* @global
* @typedef {Object} changeImageOptionsType
* @property {string} id
* @property {string} image The new Docker image for the app.
* @property {deploymentPropsType} currentProps The deployment
* properties currently used.
*/
/**
* @global
* @typedef {Object} statType
* @property {string} id An identifier for the app
* @property {number} tasksRunning The number of app processes running.
* @property {deploymentPropsType} props The properties of the app.
* @property {string} version An instance version for this app.
*/
const checkBasicType = (type, spec, name, opt) => {
const res = spec[name];
if ((opt && res) || !opt) {
assert.equal(typeof res, type, `'${name}' is not a ${type}`);
}
};
const checkArray = (type, spec, name, opt, size) => {
const res = spec[name];
if ((opt && res) || !opt) {
assert.ok(Array.isArray(res), `'${name}' is not an array`);
if (typeof size === 'number') {
assert.equal(res.length, size, `Wrong length for array '${name}'`);
}
res.forEach((x) => {
if (type === 'json') {
try {
JSON.stringify(x);
} catch (ex) {
throw new Error(`'${name}' is not an array of JSON types`);
}
} else {
assert.equal(typeof x, type,
`'${name}' is not an array of ${type}`);
}
});
}
};
const checkArrayJSON = (spec, name, opt, size) =>
checkArray('json', spec, name, opt, size);
const checkArrayString = (spec, name, opt, size) =>
checkArray('string', spec, name, opt, size);
const checkArrayBoolean = (spec, name, opt, size) =>
checkArray('boolean', spec, name, opt, size);
const checkArrayNumber = (spec, name, opt, size) =>
checkArray('number', spec, name, opt, size);
const checkString = (spec, name, opt) => checkBasicType('string', spec, name,
opt);
const checkNumber = (spec, name, opt) => checkBasicType('number', spec, name,
opt);
const checkBoolean = (spec, name, opt) => checkBasicType('boolean', spec, name,
opt);
const checkObject = (spec, name, opt) => checkBasicType('object', spec, name,
opt);
const checkResources = function(spec) {
checkArrayNumber(spec, 'cpus', false, 3);
checkArrayNumber(spec, 'memory', false, 3);
checkArrayNumber(spec, 'storage', true, 3);
checkArrayNumber(spec, 'egress', true, 3);
};
const checkPool = function(spec) {
checkArrayString(spec, 'poolKey');
checkArrayString(spec, 'poolValue');
checkArrayBoolean(spec, 'isGvisor');
};
const checkRedisSpec = function(spec) {
checkString(spec, 'templateFile');
checkString(spec, 'k8SNamespace');
checkString(spec, 'image');
checkPool(spec.nodePool);
checkResources(spec.request);
checkResources(spec.limit);
checkResources(spec.deltaRequest);
checkResources(spec.deltaLimit);
checkNumber(spec, 'updateRatio');
checkArrayNumber(spec, 'dedicatedVolumeSize');
checkArrayNumber(spec, 'deltaDedicatedVolumeSize');
checkNumber(spec, 'maxNFSInstances');
};
const checkAppSpec = function(spec) {
checkString(spec, 'templateFile');
checkString(spec, 'k8SNamespace');
checkPool(spec.nodePool);
checkResources(spec.request);
checkResources(spec.limit);
checkNumber(spec, 'maxInstances');
checkArrayJSON(spec, 'args');
};
exports.checkSpec = function(spec) {
checkBoolean(spec, 'useK8SConfig');
checkNumber(spec, 'refreshInterval');
checkString(spec, 'appSuffix');
checkBoolean(spec, 'isUntrusted');
checkBoolean(spec, 'isIncubator');
checkBoolean(spec, 'isDeployer');
checkBoolean(spec, 'isPeople');
checkBoolean(spec, 'isAccounts');
checkObject(spec, 'plans');
checkNumber(spec, 'ratioIncubator');
checkRedisSpec(spec.redis);
checkAppSpec(spec.app);
};